maven学习笔记-定制一个Maven项目

首先,用 Maven Archetype 插件创建这个 simple weather 项目的基本轮廓。 运行下面的命令,创建新项目

mvn archetype:create \ 
    -DgroupId=org.sonatype.mavenbook.ch04 \ 
    -DartifactId=simple-weather \ 
    -DpackageName=org.sonatype.mavenbook \ 
    -Dversion=1.0 

   * 给 archetype:create 目标传入了 version 参数。它覆写了默认值 1.0-SNAPSHOT

添加组织,法律和开发人员信息

<project>
	[...]
	<!--项目信息-->
	<name>simple-weather</name>
	<url>http://www.sonatype.com</url>

	<!--法律信息-->
	<licenses>
		<license>
			<name>Apache 2</name>
			<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
			<distribution>repo</distribution>
			<comments>A business-friendly OSS license</comments>
		</license>
	</licenses>

	<!--组织信息-->
	<organization>
		<name>Sonatype</name>
		<url>http://www.sonatype.com</url>
	</organization>

	<!--开发人员信息-->
	<developers>
		<developer>
			<id>jason</id>
			<name>Jason Van Zyl</name>
			<email>[email protected]</email>
			<url>http://www.sonatype.com</url>
			<organization>Sonatype</organization>
			<organizationUrl>http://www.sonatype.com</organizationUrl>
			<roles>
				<role>developer</role>
			</roles>
			<timezone>-6</timezone>
		</developer>
	</developers>
	[...]
</project>

 

添加项目所需要的依赖

<project>
	[...]
	<!--定义依赖项-->
	<dependencies>
		<!--log4j-->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.14</version>
		</dependency>
		<!--dom4j-->
		<dependency>
			<groupId>dom4j</groupId>
			<artifactId>dom4j</artifactId>
			<version>1.6.1</version>
		</dependency>
		<!--jaxen-->
		<dependency>
			<groupId>jaxen</groupId>
			<artifactId>jaxen</artifactId>
			<version>1.1.1</version>
		</dependency>
		<!--velocity-->
		<dependency>
			<groupId>velocity</groupId>
			<artifactId>velocity</artifactId>
			<version>1.5</version>
		</dependency>
		<!--junit-->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	[...]
</project>

 

编写项目代码

Simple weather 应用程序完成以下三个任务:从 Yahoo! Weather 获取 XML 数据,解析 XML 数据,打印格式化的输出至标准输出。

Simple Weather 命令行应用程序包含五个 Java 类。

*org.sonatype.mavenbook.weather.Main

这个类包含了一个静态的 main() 函数,即系统的入口。

*org.sonatype.mavenbook.weather.Weather

Weather 类是个很简单的 Java Bean,它保存了天气报告的地点和其它一些关键元素,如气温和湿度。

*org.sonatype.mavenbook.weather.YahooRetriever

YahooRetriever 连接到 Yahoo! Weather 并且返回来自数据源数据的 InputStream

*org.sonatype.mavenbook.weather.YahooParser

YahooParser 解析来自 Yahoo! Weather 的 XML,返回 Weather 对象。

*org.sonatype.mavenbook.weather.WeatherFormatter

WeatherFormatter 接受 Weather 对象,创建 VelocityContext ,根据 Velocity 模板生成结果。

 

package org.sonatype.mavenbook.weather;


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; }
}

 Weather 类定义了一个简单的 bean ,用来存储由 Yahoo! Weather 数据源解析出来的天气信息。天气数据源提供了丰富的信息,从日出日落时间,到风速和风向。 为了让这个例子保持简单, Weather 模型对象只保存温度,湿度和当前天气情况的文字描述等信息。

 

package org.sonatype.mavenbook.weather;

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 = "02101";
        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( zip );

		// Parse Data
		Weather weather = new YahooParser().parse( dataIn );

		// Format (Print) Data
		System.out.print( new WeatherFormatter().format( weather ) );
	}

}

 

main() 函数通过获取 classpath 中的资源文件来配置 Log4J ,之后它试图从命令行读取邮政编码。 如果在读取邮政编码的时候抛出了异常,程序会设置默认邮政编码为 60202 。 一旦有了邮政编码,它初始化一个 Main 对象,调用该对象的 start() 方法。而 start() 方法会调用 YahooRetriever 来获取天气的 XML 数据。 YahooRetriever 返回一个 InputStreem ,传给 YahooParserYahooParser 解析 XML 数据并返回 Weather 对象。 最后, WeatherFormatter 接受一个 Weather 对象并返回一个格式化的 String ,打印到标准输出。
package org.sonatype.mavenbook.weather;

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(String 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();
	}

}
 这个简单的类打开一个连接到 Yahoo! Weather API 的 URLConnection 并返回一个 InputStream 。 我们还需要在该目录下创建文件 YahooParser.java 用以解析这个数据源。
package org.sonatype.mavenbook.weather;

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;
	}

}
  YahooParserparse() 方法接受一个 InputStrem 然后返回一个 Weather 对象。 为了完成这一目标,它需要用 Dom4J 来解析 XML 文档。因为我们对 Yahoo! Weather XML 命名空间的元素感兴趣,我们需要用 createXmlReader() 方法创建一个包含命名空间信息的 SAXReader 。 一旦我们创建了这个 reader 并且解析了文档,得到了返回的 org.dom4j.Document ,只需要简单的使用 XPath 表达式来获取需要的信息,而不是遍历所有的子元素。 本例中 Dom4J 提供了 XML 解析功能,而 Jaxen 提供了 XPath 功能。
package org.sonatype.mavenbook.weather;

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();		
	}
}
  WeatherFormatter 使用 Veloticy 来呈现一个模板。 format() 方法接受一个 Weather bean 然后返回格式化好的 Stringformat() 方法做的第一件事是从 classpath 载入名字为 output.vm 的 Velocity 模板。 然后我们创建一个 VelocityContext ,它需要一个 Weather 对象来填充。 一个 StringWriter被创建用来存放模板生成的结果数据。通过调用 Velocity.evaluate() ,给模板赋值,结果作为 String 返回。

 

添加资源

本项目依赖于两个 classpath 资源: Main 类通过 classpath 资源 log4j.preoperties 来配置 Log4J , WeatherFormatter 引用了一个在 classpath 中的名为 output.vm 的 Velocity 模板。这两个资源都需要在默认包中(或者 classpath 的根目录)。

为了添加这些资源,我们需要在项目的基础目录下创建一个新的目录—— src/main/resources。 由于任务 archetype:create 没有创建这个目录,我们需要通过在项目的基础目录下运行下面的命令来创建它:

log4j.properties

# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=INFO, CONSOLE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
#log4j.logger.org.apache.axis.enterprise=FATAL, 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}
*********************************

 

 

mvn install

这里发生一个错误,提示jvm1.3版本不支持泛型,所以需要手动将目标编译器
jvm版本换成1.5以上。

<project>
  [...]
  <build>
  <finalName>simple-weather</finalName>
  <plugins>
	  <plugin>
		<artifactId>maven-compiler-plugin</artifactId> 
			<configuration>
				<source>1.5</source> 
				<target>1.5</target> 
				<encoding>UTF-8</encoding> 
			</configuration>
	  </plugin>
  </plugins>
  </build>
</project>

 

Maven Exec 插件进行命令行测试
mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main

*想要查看 Exec 插件的完整描述,运行
mvn help:describe -Dplugin=exec -Dfull

 

浏览你的项目依赖
使用Exec是可以不安装依赖的情况下运行,想要知道程序都有哪些依赖则运行
mvn dependency:resolve
*开控制台打印所有依赖情况
*如果你想知道你项目的整个依赖树,你可以运行
mvn dependency:tree
*或者想要查看完整的依赖踪迹,包含那些因为冲突或者其它原因而被拒绝
引入的构件,打开 Maven 的调试标记运行
mvn install -X

 

编写单元测试代码

package org.sonatype.mavenbook.weather.yahoo;

import java.io.InputStream;

import junit.framework.TestCase;

import org.sonatype.mavenbook.weather.Weather;
import org.sonatype.mavenbook.weather.YahooParser;

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() );
	}
}

 

通过解析一个值已知的 XML 文档来测试 YahooParser 。 测试 XML 文档命名为 ny-weather.xml ,从 classpath 载入。我们将在Section 4.11, “添加单元测试资源”添加测试资源。 在我们这个 Maven 项目的目录布局中,文件 ny-weather.xml 可以从包含测试资源的目录—— /usr/local/hudson/hudson-home/jobs/maven-guide-zh-to-production/workspace/content-zh/src/test/resources ——中找到,路径为 org/sonatype/mavenbook/weather/yahoo/ny-weather.xml 。 该文件作为一个 InputStream 被读入,传给 YahooParser 的 parse() 方法。 parse() 方法返回一个 Weather 对象,该对象通过一系列 由 TestCase 定义的 assertEquals() 调用而被测试。

package org.sonatype.mavenbook.weather.yahoo;

import java.io.InputStream;

import org.apache.commons.io.IOUtils;

import org.sonatype.mavenbook.weather.Weather;
import org.sonatype.mavenbook.weather.WeatherFormatter;
import org.sonatype.mavenbook.weather.YahooParser;

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( IOUtils.toString( expected ), formattedResult );
	}
}

 

WeatherFormatterTest 首先调用 YahooParser 解析出 Weather 对象,然后用 WeatherFormatter 格式化这个对象。 我们的期望输出被存储在一个名为 format-expected.dat 的文件中,该文件存放在和 ny-weather.xml 同样的目录中。 要比较测试输出和期望输出,我们将期望输出作为 InputStream 读入,然后使用 Commons IO 的 IOUtils 类来把文件转化为 String 。 然后使用 assertEquals() 比较这个 String 和测试输出。

 

添加测试范围依赖

在类 WeatherFormatterTest 中我们用了一个来自于 Apache Commons IO 的工具—— IOUtils 类。 IOUtils 提供了许多很有帮助的静态方法,能帮助让很多工作摆脱繁琐的 I/O 操作。在这个单元测试中我们使用了 IOUtils.toString() 来复制 classpath 中资源 format.expected.dat 中的数据至 String。 不用 Commons IO 我们也能完成这件事情,但是那需要额外的六七行代码来处理像 InputStreamReaderStringWriter 这样的对象。我们使用 Commons IO 的主要原因是,能有理由添加对 Commons IO 的测试范围依赖。

<project>
	...
	<dependencies>
	...
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-io</artifactId>
			<version>1.3.2</version>
			<scope>test</scope>
		</dependency>
	...
	</dependencies>
</project>

 

 

添加单元测试资源

format-expected.dat

*********************************
 Current Weather Conditions for:
  New York, NY, US
  
 Temperature: 39
   Condition: Fair
    Humidity: 67
  Wind Chill: 39
*********************************

 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/*http://weather.yahoo.com/forecast/10002_f.html</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/*http://weather.yahoo.com/forecast/10002_f.html</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 />
<a href="http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/*http://weather.yahoo.com/forecast/10002_f.html">Full Forecast at Yahoo! Weather</a><BR/>
 (provided by The Weather Channel)<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><!-- p7.weather.re3.yahoo.com compressed/chunked Sat Nov 10 17:57:31 PST 2007 -->

 该文件包含了一个给 YahooParserTest 用的 XML 文档。有了这个文件,我们不用从 Yahoo! Weather 获取 XML 响应就能测试 YahooParser 了。

 

执行单元测试
mvn test

 

构建一个打包好的命令行应用程序
配置 Maven 装配描述符

<project>
	[...]
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-assembly-plugin</artifactId>
				<configuration>
					<descriptorRefs>
						<descriptorRef>jar-with-dependencies</descriptorRef>
					</descriptorRefs>
				</configuration>
			</plugin>
		</plugins>
	</build>
	[...]
</project>

 

装配命令
mvn assembly:assembly
*装配完成后就可以直接分发执行了。但是这个包将所有文件拆开后重新打成一个整体,不太标准。

 

执行
java -cp simple-weather-1.0-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 10002

你可能感兴趣的:(maven,log4j,xml,velocity,Yahoo)