들어가며
이 글은 아래 사이트의 내용을 기초로하여 만들어 졌다. 아래 사이트와 다른점은 한글이라는점과 이클립스가 들어갔다는 점이다. 아래에서 만드는 소스내용도 똑같으니 참고하기 바란다.
http://www.sonatype.com/books/maven-book/reference/public-book.html
여기서 만드는 예제는 yahoo 에서 제공하는 xml 을 이용하여 날씨를 얻어오는 예제이다.
물론 프로젝트 내용은 중요한것이 아니고 maven의 기능을 살펴보는데 초점을 맞추게 될것이다.
프로젝트 만들기
maven 프로젝트를 생성해서 아래와같이 입력하고 Next 버튼을 누른다.
기본적으로 test 를 위해 junit 은 필요할테니까 Add 를 클릭해서 junit 을 검색해서 추가한다.
단 scope 는 test 로 해야 한다는 것을 잊지 말자.
Dependency 추가
pom.xml 파일을 열면 아래와같은 화면이 나오는데 여기서 Dependencies 탭으로 가서 아래처럼 찾기 아이콘을 클릭한다.
이제 찾기 화면에서 아래 라이브러를 검색해서 추가한다. (단 여기서 log4j 는 이보다 최신버젼이 있지만 그 버젼을 선택할경우 오류가 나는 현상이 있어서 이전 버젼을 선택했다.) 추가한 다음에는 꼭 저장버튼을 클릭해서 적용을 하자.
log4j (1.2.14 )
dom4j
jaxen
velocity
프로그램 작성
프로그램 작성에 들어가기 전에 이클립스 셋팅을 해야한다. maven 프로젝트를 생성하면 기본적으로 자바 1.4 가 선택이 된다. 프로젝트의 Properties 로 들어가서 Java Compiler 메뉴로 가서 컴파일러 버젼을 최신으로 바꾼다음에 진행하도록 하자.
프로그램의 소스파일은 아래와 같다. 여기서 중요한 것은 소스의 내용이 아니기 때문에 소스설명은 생략하도록 하겠다.
net/cranix/Weather.java
net/cranix/Main.java
net/cranix/YahooRetriver.java
net/cranix/YahooParser.java
net/cranix/WeatherFormatter.java
<net/cranix/Weather.java>
package net.cranix;
public class Weather {
private String city;
private String region;
private String country;
private String condition;
private String temp;
private String chill;
private String humidity;
public Weather() {
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
public String getChill() {
return chill;
}
public void setChill(String chill) {
this.chill = chill;
}
public String getHumidity() {
return humidity;
}
public void setHumidity(String humidity) {
this.humidity = humidity;
}
}
<net/cranix/Main.java>
package net.cranix;
import java.io.InputStream;
import org.apache.log4j.PropertyConfigurator;
public class Main {
public static void main(String[] args) throws Exception {
// Configure Log4J
PropertyConfigurator.configure(Main.class.getClassLoader().getResource("log4j.properties"));
// Read the Zip Code from the Command-line (if none supplied, use 60202)
String zipcode = "60202";
try {
zipcode = args[0];
} catch( Exception e ) {}
// Start the program
new Main(zipcode).start();
}
private String zip;
public Main(String zip) {
this.zip = zip;
}
public void start() throws Exception {
// Retrieve Data
InputStream dataIn = new YahooRetriever().retrieve(Integer.valueOf(zip));
// Parse Data
Weather weather = new YahooParser().parse(dataIn);
// Format (Print) Data
System.out.print(new WeatherFormatter().format(weather));
}
}
<net/cranix/YahooRetriver.java>
package net.cranix;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import org.apache.log4j.Logger;
public class YahooRetriever {
private static Logger log = Logger.getLogger(YahooRetriever.class);
public InputStream retrieve(int zipcode) throws Exception {
log.info("Retrieving Weather Data");
String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode;
URLConnection conn = new URL(url).openConnection();
return conn.getInputStream();
}
}
<net/cranix/YahooParser.java>
package net.cranix;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.io.SAXReader;
public class YahooParser {
private static Logger log = Logger.getLogger(YahooParser.class);
public Weather parse(InputStream inputStream) throws Exception {
Weather weather = new Weather();
log.info("Creating XML Reader");
SAXReader xmlReader = createXmlReader();
Document doc = xmlReader.read(inputStream);
log.info("Parsing XML Response");
weather.setCity(doc.valueOf("/rss/channel/y:location/@city"));
weather.setRegion(doc.valueOf("/rss/channel/y:location/@region"));
weather.setCountry(doc.valueOf("/rss/channel/y:location/@country"));
weather.setCondition(doc.valueOf("/rss/channel/item/y:condition/@text"));
weather.setTemp(doc.valueOf("/rss/channel/item/y:condition/@temp"));
weather.setChill(doc.valueOf("/rss/channel/y:wind/@chill"));
weather.setHumidity(doc.valueOf("/rss/channel/y:atmosphere/@humidity"));
return weather;
}
private SAXReader createXmlReader() {
Map<String, String> uris = new HashMap<String, String>();
uris.put("y", "http://xml.weather.yahoo.com/ns/rss/1.0");
DocumentFactory factory = new DocumentFactory();
factory.setXPathNamespaceURIs(uris);
SAXReader xmlReader = new SAXReader();
xmlReader.setDocumentFactory(factory);
return xmlReader;
}
}
<net/cranix/WeatherFormatter.java>
package net.cranix;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import org.apache.log4j.Logger;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
public class WeatherFormatter {
private static Logger log = Logger.getLogger(WeatherFormatter.class);
public String format(Weather weather) throws Exception {
log.info("Formatting Weather Data");
Reader reader = new InputStreamReader(getClass().getClassLoader().getResourceAsStream("output.vm"));
VelocityContext context = new VelocityContext();
context.put("weather", weather);
StringWriter writer = new StringWriter();
Velocity.evaluate(context, writer, "", reader);
return writer.toString();
}
}
소스를 조금 설명하자면 dom4j 라이브러리로 yahoo 에서 제공하는 xml 파일을 읽어서 파싱한 다음 velocity 라이브러리로 출력을 하게 되는 것이다. 여기서 콘솔출력은 log4j 에 의해 이루어진다.
RESOURCE 추가
그러나 이렇게 소스만 만들어놨다고 제대로된 결과를 기대하기는 어렵다. 왜냐하면 log4j 같은 경우는 설정 파일이 필요하고 velocity 는 템플릿 파일이 필요하다 이런 resource 를 추가하는 방법을 알아보도록 하겠다.
모든 리소스들은 resource 디렉토리에 위치시키면 된다. 이 프로그램에서 필요한 리소스는 아래와 같다.
log4j.properties
output.vm
<log4j.properties>
# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=INFO, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=INFO
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %c{1} %x - %m%n
<output.vm>
*********************************
Current Weather Conditions for:
${weather.city}, ${weather.region}, ${weather.country}
Temperature: ${weather.temp}
Condition: ${weather.condition}
Humidity: ${weather.humidity}
Wind Chill: ${weather.chill}
*********************************
디렉토리의 형태는 아래와 같다.
실행하기
실행하기 위해서는 Run As-maven build 를 클릭한다음 아래와같이 셋팅하고 Run 을 클릭하자.
아래와 같은 결과가 나오면서 우리가 만든 프로그램이 제대로 실행되고 있는 것을 알 수 있다.
[INFO] Searching repository for plugin with prefix: 'exec'.
[INFO] Attempting to resolve a version for plugin: org.codehaus.mojo:exec-maven-plugin using meta-version: LATEST
[INFO] Using version: 1.1.1 of plugin: org.codehaus.mojo:exec-maven-plugin
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Unnamed - net.cranix:weather:jar:0.0.1-SNAPSHOT
[INFO]
[INFO] Id: net.cranix:weather:jar:0.0.1-SNAPSHOT
[INFO] task-segment: [exec:java]
[INFO] ------------------------------------------------------------------------
[INFO] [exec:java]
0 INFO YahooRetriever - Retrieving Weather Data
359 INFO YahooParser - Creating XML Reader
561 INFO YahooParser - Parsing XML Response
639 INFO WeatherFormatter - Formatting Weather Data
*********************************
Current Weather Conditions for:
Evanston, IL, US
Temperature: 70
Condition: Fair
Humidity: 73
Wind Chill: 70
*********************************
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Sun Aug 16 15:44:18 KST 2009
[INFO] Final Memory: 4M/12M
[INFO] ------------------------------------------------------------------------
Dependency 정보 확인하기
이렇게 maven 으로 프로젝트를 만들었다면 Dependency 정보를 쉽게 확인할 수 있다.
pom.xml 파일을 열어서 Dependency Graph 탭을 클릭하고 새로고침 버튼을 클릭하면 아래와 같이 그래프 형태로 표시되는 것을 볼 수 있다.
아래와 같이 Dependency Hierarchy 를 클릭해서 트리 형태로 볼 수도 있다.
테스트 소스 추가하기
여기서는 유닛 테스트를 위해 commons-io 라이브러리를 사용한다. pom.xml 파일을 열어서 dependency 탭으로 가서 commons-io 라이브러리를 검색해서 추가하자. 단 테스트를 위한 것이기 때문에 scope 를 test 로 설정하자.
테스트를 하기 위해서는 test 디렉토리에 테스트 파일이 위치해야 하며 여기서 사용할 테스트파일은 아래와 같다.
test/net/cranix/YahooParserTest.java
test/net/cranix/WeatherFormatterTest.java
<test/net/cranix/YahooParserTest.java>
package net.cranix;
import java.io.InputStream;
import junit.framework.TestCase;
public class YahooParserTest extends TestCase {
public YahooParserTest(String name) {
super(name);
}
public void testParser() throws Exception {
InputStream nyData = getClass().getClassLoader().getResourceAsStream(
"ny-weather.xml");
Weather weather = new YahooParser().parse(nyData);
assertEquals("New York", weather.getCity());
assertEquals("NY", weather.getRegion());
assertEquals("US", weather.getCountry());
assertEquals("39", weather.getTemp());
assertEquals("Fair", weather.getCondition());
assertEquals("39", weather.getChill());
assertEquals("67", weather.getHumidity());
}
}
<test/net/cranix/WeatherFormatterTest.java>
package net.cranix;
import java.io.InputStream;
import org.apache.commons.io.IOUtil;
import junit.framework.TestCase;
public class WeatherFormatterTest extends TestCase {
public WeatherFormatterTest(String name) {
super(name);
}
public void testFormat() throws Exception {
InputStream nyData = getClass().getClassLoader().getResourceAsStream("ny-weather.xml");
Weather weather = new YahooParser().parse(nyData);
String formattedResult = new WeatherFormatter().format(weather);
InputStream expected = getClass().getClassLoader().getResourceAsStream("format-expected.dat");
assertEquals(IOUtil.toString(expected).trim(), formattedResult.trim());
}
}
테스트 RESOURCE 추가하기
테스트를 위한 이 프로그램 역시 그냥은 실행되지 않는다. 리소스 파일을 추가해 줘야 하는데 테스트 리소스 파일은 test/resources 디렉토리에 위치해야 한다.
test/resources/format-expected.dat
test/resources/ny-weather.xml
<test/resources/format-expected.dat>
*********************************
Current Weather Conditions for:
New York, NY, US
Temperature: 39
Condition: Fair
Humidity: 67
Wind Chill: 39
*********************************
<test/resources/ny-weather.xml>
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
<channel>
<title>Yahoo! Weather - New York, NY</title>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link>
<description>Yahoo! Weather for New York, NY</description>
<language>en-us</language>
<lastBuildDate>Sat, 10 Nov 2007 8:51 pm EDT</lastBuildDate>
<ttl>60</ttl>
<yweather:location city="New York" region="NY" country="US" />
<yweather:units temperature="F" distance="mi" pressure="in" speed="mph" />
<yweather:wind chill="39" direction="0" speed="0" />
<yweather:atmosphere humidity="67" visibility="1609" pressure="30.18"
rising="1" />
<yweather:astronomy sunrise="6:36 am" sunset="4:43 pm" />
<image>
<title>Yahoo! Weather</title>
<width>142</width>
<height>18</height>
<link>http://weather.yahoo.com/</link>
<url>http://l.yimg.com/us.yimg.com/i/us/nws/th/main_142b.gif</url>
</image>
<item>
<title>Conditions for New York, NY at 8:51 pm EDT</title>
<geo:lat>40.67</geo:lat>
<geo:long>-73.94</geo:long>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/\</link>
<pubDate>Sat, 10 Nov 2007 8:51 pm EDT</pubDate>
<yweather:condition text="Fair" code="33" temp="39"
date="Sat, 10 Nov 2007 8:51 pm EDT" />
<description><![CDATA[
<img src="http://l.yimg.com/us.yimg.com/i/us/we/52/33.gif" /><br />
<b>Current Conditions:</b><br />
Fair, 39 F<BR /><BR />
<b>Forecast:</b><BR />
Sat - Partly Cloudy. High: 45 Low: 32<br />
Sun - Sunny. High: 50 Low: 38<br />
<br />
]]></description>
<yweather:forecast day="Sat" date="10 Nov 2007" low="32" high="45"
text="Partly Cloudy" code="29" />
<yweather:forecast day="Sun" date="11 Nov 2007" low="38" high="50"
text="Sunny" code="32" />
<guid isPermaLink="false">10002_2007_11_10_20_51_EDT</guid>
</item>
</channel>
</rss>
최종 디렉토리는 아래와 같다.
테스트 실행
이제 테스트 환경이 갖추어졌다.
Run As-maven test 를 실행하자. 아래와 같이 테스트가 되는 것을 볼 수 있다.
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Unnamed - net.cranix:weather:jar:0.0.1-SNAPSHOT
[INFO]
[INFO] Id: net.cranix:weather:jar:0.0.1-SNAPSHOT
[INFO] task-segment: [test]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [surefire:test]
[INFO] Surefire report directory: D:\cranix\work\workspace-wtp2\weather\target\surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running net.cranix.YahooParserTest
0 INFO YahooParser - Creating XML Reader
171 INFO YahooParser - Parsing XML Response
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.407 sec
Running net.cranix.WeatherFormatterTest
266 INFO YahooParser - Creating XML Reader
282 INFO YahooParser - Parsing XML Response
282 INFO WeatherFormatter - Formatting Weather Data
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.172 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Sun Aug 16 16:55:20 KST 2009
[INFO] Final Memory: 2M/6M
[INFO] ------------------------------------------------------------------------
Dependencies 까지 포함시켜서 패키징하기
이 과정을 assembly 과정 이라고 하는데 제대로 실행되도록 하려면 셋팅을 해야한다. pom.xml 파일을 열어서 아래 부분을 추가한다.
<project>
[...]
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
그 다음 Run As-maven assembly:assembly 를 실행한다. 아래와같은 화면이 나오면서 패키징을 하게된다.
…
[INFO] [jar:jar]
[INFO] Building jar: D:\cranix\work\workspace-wtp2\weather\target\weather-0.0.1-SNAPSHOT.jar
[INFO] [statemgmt:end-fork]
[INFO] Ending forked execution [fork id: 574707759]
[INFO] [assembly:assembly]
[INFO] Processing DependencySet (output=)
[INFO] Expanding: D:\cranix\work\maven\repository\log4j\log4j\1.2.14\log4j-1.2.14.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.1429370463.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\maven\dom4j\1.7-20060614\dom4j-1.7-20060614.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.1955766376.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\jaxme\jaxme-api\0.3\jaxme-api-0.3.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.165260721.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\jaxen\jaxen\1.1.1\jaxen-1.1.1.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.1217840646.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\dom4j\dom4j\1.6.1\dom4j-1.6.1.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.3570191.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\jdom\jdom\1.0\jdom-1.0.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.1685521809.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\xerces\xmlParserAPIs\2.6.2\xmlParserAPIs-2.6.2.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.305584397.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\xerces\xercesImpl\2.6.2\xercesImpl-2.6.2.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.1558042758.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\xom\xom\1.0\xom-1.0.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.2049563553.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\com\ibm\icu\icu4j\2.6.1\icu4j-2.6.1.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.796405737.tmp
[INFO] Expanding: D:\cranix\work\maven\repository\xalan\xalan\2.6.0\xalan-2.6.0.jar into C:\Users\cranix\AppData\Local\Temp\archived-file-set.1324133154.tmp
…
이 작업은 현재 종속된 모든 라이브러리를 하나의 jar 파일로 묶는 작업으로 시간이 조금 걸린다. 완료가 되면 target 디렉토리에 모든 종속된 라이브러리의 클래스가 포함된 weather-0.0.1-SNAPSHOT-jar-with-dependencies.jar 파일이 생성된 것을 볼 수있다.
마치며
왠만해서 pom.xml 파일을 직접 에디트 하지 않았으면 했지만 아직 m2eclipse 플러그인 에서는 비쥬얼하게 위와같이 assembly 설정을 할수있는 방법이 없는거 같다. 하여튼 마지막 assembly 과정은 maven 의 백미 라고 볼 수 있겠다.
RECENT COMMENT