들어가며

이 글은 아래 사이트의 내용을 기초로하여 만들어 졌다. 아래 사이트와 다른점은 한글이라는점과 이클립스가 들어갔다는 점이다. 아래에서 만드는 소스내용도 똑같으니 참고하기 바란다.

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 의 백미 라고 볼 수 있겠다.

신고
by cranix 2009.08.16 17:17

서론

얼마전 java 프로젝트를 하나 했었는데 거기서 maven 이라는 도구를 처음 봤다. 처음 느낀점은 상당히 복잡하다는 것이다. “이걸 구지 써야하나?” 라는 생각이 들정도 였다. 그러나 프로젝트를 진행하면서 이 도구는 잘만쓰면 상당히 유용할 것이라는 생각이 들었다. 그래서 이참에 메이븐이 무엇이고 그 사용법에대해 알아보려고 한다.

 

본론

maven 의 사전적 의미는 이디시어로 “지식의 축적” 을 의미한다고 한다. 이 프로젝트는 Jakarta Turbine 프로젝트에서 빌드과정을 간략히 하려는데서 출발했다. 우리는 프로젝트를 빌드하는데 표준화된 방법, 프로젝트의 확실한 정의, 프로젝트의 정보를 공개하는 쉬운 방법 그리고 프로젝트들간의 JAR 파일의 공유를 원했다. 그래서 maven 이 나왔고 현재는 모든 java 기반 프로젝트에 적용가능하다.

 

maven 의 첫번째 목표는 개발자가 개발산출물을 최단시간내에 이해할수 있도록 하는것이다.

목표를 이루기위해 다루는 영역은 다음과 같다

- 쉬운 빌드 과정
- 동일한 빌드 시스템 제공
- 양질의 프로젝트 정보 제공
- 모범사례에 대한 지침 제공
- 새로운 기능에 투명성 있는 마이그레이션 제공

 

결론

오늘은 메이븐 홈페이지에서 메이븐 소개를 그냥 해석해 봤다. 역시 소개부분이라 상당히 추상적이지만 maven 이 무엇을위해 만들어졌는지는 어느정도 감이 잡힌다. 다음번에는 메이븐과 이클립스를 연동하면서 실제로 프로젝트를 생성해보도록 하겠다.

신고
by cranix 2009.08.15 17:47
| 1 |