赞
踩
This chapter expands on the information introduced in Chapter 3, A Simple Maven Project. We’re going to create a simple project generated with the Maven Archetype plugin, add some dependencies, add some source code, and customize the project to suit our needs. By the end of this chapter, you will know how to start using Maven to create real projects.
We’ll be developing a useful program that interacts with a Yahoo Weather web service. Although you should be able to follow along with this chapter without the example source code, we recommend that you download a copy of the code to use as a reference. This chapter’s example project may be downloaded with the book’s example code at:
http://books.sonatype.com/mvnex-book/mvnex-examples.zip
Unzip this archive in any directory, and then go to the ch-custom/
directory. There you will see a directory named simple-weather/
, which contains the Maven project developed in this chapter.
Before we start customizing this project, let’s take a step back and talk about the Simple Weather project. What is it? It’s a contrived example, created to demonstrate some of the features of Maven. It is an application that is representative of the kind you might need to build. The Simple Weather application is a basic command-line-driven application that takes a zip code and retrieves some data from the Yahoo Weather RSS feed. It then parses the result and prints the result to standard output.
在开始自定义此项目之前,让我们退后一步,谈谈“Simple Weather”项目。 它是什么? 这是一个精心设计的示例,旨在演示Maven的某些功能。 它是代表您可能需要构建的那种应用程序。 Simple Weather应用程序是一个基本的命令行驱动的应用程序,它采用邮政编码并从Yahoo Weather RSS feed中检索一些数据。 然后,它解析结果并将结果打印到标准输出。
We chose this example for a number of reasons. First, it is straightforward. A user supplies input via the command line, the app takes that zip code, makes a request to Yahoo Weather, parses the result, and formats some simple data to the screen. This example is a simple main()
function and some supporting classes; there is no enterprise framework to introduce and explain, just XML parsing and some logging statements. Second, it gives us a good excuse to introduce some interesting libraries such as Velocity, Dom4J, and Log4J. Although this book is focused on Maven, we won’t shy away from an opportunity to introduce interesting utilities. Lastly, it is an example that can be introduced, developed, and deployed in a single chapter.
我们选择此示例的原因有很多。 首先,它很简单。 用户通过命令行提供输入,应用程序获取该邮政编码,向Yahoo Weather请求,解析结果,然后将一些简单的数据格式化到屏幕上。 这个例子是一个简单的main()函数和一些支持类。 没有要介绍和解释的企业框架,只有XML解析和一些日志记录语句。 其次,它为我们提供了很好的借口,介绍了一些有趣的库,例如Velocity,Dom4J和Log4J。 尽管本书侧重于Maven,但我们不会回避介绍有趣的实用程序的机会。 最后,这是一个可以在单个章节中介绍,开发和部署的示例。
Before you build this application, you should know something about the Yahoo Weather RSS feed. To start with, the service is made available under the following terms:
The feeds are provided free of charge for use by individuals and
nonprofit organizations for personal, noncommercial uses. We ask that
you provide attribution to Yahoo Weather in connection with your use
of the feeds.
In other words, if you are thinking of integrating these feeds into your commercial web site, think again—this feed is for personal, noncommercial use. The use we’re encouraging in this chapter is personal educational use. For more information about these terms of service, see the Yahoo Weather! API documentation here: http://developer.yahoo.com/weather/.
First, let’s use the Maven Archetype plugin to create a basic skeleton for the Simple Weather project. Execute the following command to create a new project, press enter to use the default maven-archetype-quickstart
and the latest version of the archetype, and then enter “Y” to confirm and generate the new project. Note that the number for the archetype will be different on your execution:
当前maven版本为:3.5.0
$ mvn archetype:generate -DgroupId=org.sonatype.mavenbook.custom -DartifactId=simple-weather -Dversion=1.0 [INFO] Preparing archetype:generate ... [INFO] [archetype:generate {execution: default-cli}] [INFO] Generating project in Interactive mode [INFO] No archetype defined. Using maven-archetype-quickstart \ (org.apache.maven.archetypes:maven-archetype-quickstart:1.0) Choose archetype: ... ... Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1213: Choose org.apache.maven.archetypes:maven-archetype-quickstart version: 1: 1.0-alpha-1 2: 1.0-alpha-2 3: 1.0-alpha-3 4: 1.0-alpha-4 5: 1.0 6: 1.1 7: 1.3 Choose a number: 7: 5 [INFO] Using property: groupId = org.sonatype.mavenbook.custom [INFO] Using property: artifactId = simple-weather [INFO] Using property: version = 1.0 [INFO] Using property: package = org.sonatype.mavenbook.custom Confirm properties configuration: groupId: org.sonatype.mavenbook.custom artifactId: simple-weather version: 1.0 package: org.sonatype.mavenbook.custom Y: : Y [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0 [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: basedir, Value: D:\maven [INFO] Parameter: package, Value: org.sonatype.mavenbook.custom [INFO] Parameter: groupId, Value: org.sonatype.mavenbook.custom [INFO] Parameter: artifactId, Value: simple-weather [INFO] Parameter: packageName, Value: org.sonatype.mavenbook.custom [INFO] Parameter: version, Value: 1.0 [INFO] project created from Old (1.x) Archetype in dir: D:\maven\simple-weather [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:21 min [INFO] Finished at: 2020-03-06T09:23:47+08:00 [INFO] Final Memory: 15M/216M [INFO] ------------------------------------------------------------------------
Once the Maven Archetype plugin creates the project, go into the simple-weather
directory and take a look at the pom.xml
file. You should see the XML document that’s shown in Initial POM for the simple-weather Project.
Initial POM for the simple-weather Project.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.custom</groupId> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <version>1.0</version> <name>simple-weather</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
Next, you will need to configure the Maven Compiler plugin to target Java 8. To do this, add the build
element to the initial POM as shown in POM for the simple-weather Project with Compiler Configuration.
POM for the simple-weather Project with Compiler Configuration.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.custom</groupId> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <version>1.0</version> <name>simple-weather</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
Notice that we passed in the version
parameter to the archetype:generate
goal. This overrides the default value of 1.0-SNAPSHOT
. In this project, we’re developing the 1.0
version of the simple-weather
project as you can see in the pom.xml
version
element.
Before we start writing code, let’s customize the project information a bit. We want to add some information about the project’s license, the organization, and a few of the developers associated with the project. This is all standard information you would expect to see in most projects. Adding Organizational, Legal, and Developer Information to the pom.xml shows the XML that supplies the organizational information, the licensing information, and the developer information.
Adding Organizational, Legal, and Developer Information to the pom.xml.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.custom</groupId> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <version>1.0</version> <name>simple-weather</name> <url>http://maven.apache.org</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>jason@maven.org</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> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
The Simple Weather application is going to have to complete the following three tasks: retrieve XML data from Yahoo Weather, parse the XML from Yahoo, and then print formatted output to standard output. To accomplish these tasks, we have to introduce some new dependencies to our project’s pom.xml
. To parse the XML response from Yahoo, we’re going to be using Dom4J and Jaxen, to format the output of this command-line program we are going to be using Velocity, and we will also need to add a dependency for Log4J which we will be using for logging. After we add these dependencies, our dependencies
element will look like the following example.
Adding Dom4J, Jaxen, Velocity, and Log4J as Dependencies.
<project> [...] <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> [...] </project>
As you can see above, we’ve added four more dependency
elements in addition to the existing element which was referencing the test
scoped dependency on JUnit. If you add these dependencies to the project’s pom.xml
file and then run mvn install
, you will see Maven downloading all of these dependencies and other transitive dependencies to your local Maven repository.
How did we find these dependencies? Did we just “know” the appropriate groupId
and artifactId
values? Some of the dependencies are so widely used (like Log4J) that you’ll just remember what the groupId
and artifactId
are every time you need to use them. Velocity, Dom4J, and Jaxen were all located using the searching capability on https://mvnrepository.com/. This is a public Sonatype Nexus instance which provides a search interface to various public Maven repositories, you can use it to search for dependencies. To test this for yourself, load https://mvnrepository.com/ and search for some commonly used libraries such as Hibernate or the Spring Framework. When you search for an artifact on this site, it will show you an artifactId
and all of the versions known to the central Maven repository. Clicking on the details for a specific version will load a page that contains the dependency element you’ll need to copy and paste into your own project’s pom.xml
. If you need to find a dependency, you’ll want to check out https://mvnrepository.com/, as you’ll often find that certain libraries have more than one groupId
. With this tool, you can make sense of the Maven repository.
The Simple Weather command-line application consists of five Java classes.
org.sonatype.mavenbook.weather.Main
The Main
class contains a static main()
method: the entry point for this system.
org.sonatype.mavenbook.weather.Weather
The Weather
class is a straightforward Java bean that holds the location of our weather report and some key facts, such as the temperature and humidity.
org.sonatype.mavenbook.weather.YahooRetriever
The YahooRetriever
class connects to Yahoo Weather and returns an InputStream
of the data from the feed.
org.sonatype.mavenbook.weather.YahooParser
The YahooParser
class parses the XML from Yahoo Weather and returns a Weather
object.
org.sonatype.mavenbook.weather.WeatherFormatter
The WeatherFormatter
class takes a Weather
object, creates a VelocityContext
, and evaluates a Velocity template.
Although we won’t dwell on the code here, we will provide all the necessary code for you to get the example working. We assume that most readers have downloaded the examples that accompany this book, but we’re also mindful of those who may wish to follow the example in this chapter step-by-step. The sections that follow list classes in the simple-weather
project. Each of these classes should be placed in the same package: org.sonatype.mavenbook.weather
.
Let’s remove the App
and the AppTest
classes created by archetype:generate
and add our new package. In a Maven project, all of a project’s source code is stored in src/main/java
. From the base directory of the new project, execute the following commands:
$ cd src/test/java/org/sonatype/mavenbook/custom
$ rm AppTest.java
$ cd ../../../../../../..
$ cd src/main/java/org/sonatype/mavenbook/custom
$ cd ..
$ rm App.java
$ mkdir weather
$ cd weather
This creates a new package named org.sonatype.mavenbook.weather
. Now we need to put some classes in this directory. Using your favorite text editor, create a new file named Weather.java
with the contents shown in Simple Weather’s Weather Model Object.
Simple Weather’s Weather Model Object.
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; } }
The Weather
class defines a simple bean that is used to hold the weather information parsed from the Yahoo Weather feed. This feed provides a wealth of information, from the sunrise and sunset times to the speed and direction of the wind. To keep this example as simple as possible, the Weather
model object keeps track of only the temperature, chill, humidity, and a textual description of current conditions.
Now, in the same directory, create a file named Main.java
. This Main
class will hold the static main()
method—the entry point for this example.
Simple Weather’s Main Class.
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 = "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( zip ); // Parse Data Weather weather = new YahooParser().parse( dataIn ); // Format (Print) Data System.out.print( new WeatherFormatter().format( weather ) ); } }
The main()
method shown above configures Log4J by retrieving a resource from the classpath. It then tries to read a zip code from the command line. If an exception is thrown while it is trying to read the zip code, the program will default to a zip code of 60202. Once it has a zip code, it instantiates an instance of Main
and calls the start()
method on an instance of Main
. The start()
method calls out to the YahooRetriever
to retrieve the weather XML. The YahooRetriever
returns an InputStream
which is then passed to the YahooParser
. The YahooParser
parses the Yahoo Weather XML and returns a Weather
object. Finally, the WeatherFormatter
takes a Weather
object and spits out a formatted String
which is printed to standard output.
Create a file named YahooRetriever.java
in the same directory with the contents shown in Simple Weather’s YahooRetriever Class.
备注:由于之前的网站已经无法访问,因此此处对代码进行修改,保证程序正常运行,在src/main/resources中添加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>
Simple Weather’s YahooRetriever Class.
package org.sonatype.mavenbook.weather; import org.apache.log4j.Logger; import java.io.InputStream; 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(); return getClass().getClassLoader() .getResourceAsStream("weather.xml"); } }
This simple class opens a URLConnection
to the Yahoo Weather API and returns an InputStream
. To create something to parse this feed, we’ll need to create the YahooParser.java
file in the same directory.
Simple Weather’s YahooParser Class.
package org.sonatype.mavenbook.weather; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.io.SAXReader; import java.io.InputStream; import java.util.HashMap; import java.util.Map; 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; } }
The YahooParser
is the most complex class in this example. We’re not going to dive into the details of Dom4J or Jaxen here, but the class deserves some explanation. YahooParser
's parse()
method takes an InputStream
and returns a Weather
object. To do this, it needs to parse an XML document with Dom4J. Since we’re interested in elements under the Yahoo Weather XML namespace, we need to create a namespace-aware SAXReader
in the createXmlReader()
method. Once we create this reader and parse the document, we get an org.dom4j.Document
object back. Instead of iterating through child elements, we simply address each piece of information we need using an XPath expression. Dom4J provides the XML parsing in this example, and Jaxen provides the XPath capabilities.
Once we’ve created a Weather
object, we need to format our output for human consumption. Create a file named WeatherFormatter.java
in the same directory as the other classes.
Simple Weather’s WeatherFormatter Class.
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(); } }
The WeatherFormatter
uses Velocity to render a template. The format()
method takes a Weather
bean and spits out a formatted String
. The first thing the format()
method does is load a Velocity template from the classpath named output.vm
. We then create a VelocityContext
which is populated with a single Weather
object named weather
. A StringWriter
is created to hold the results of the template merge. The template is evaluated with a call to Velocity.evaluate()
and the results are returned as a String
.
Before we can run this example, we’ll need to add some resources to our classpath.
This project depends on two classpath resources: the Main
class that configures Log4J with a classpath resource named log4j.properties
, and the WeatherFormatter
that references a Velocity template from the classpath named output.vm
. Both of these resources need to be in the default package (or the root of the classpath).
To add these resources, we’ll need to create a new directory from the base directory of the project: src/main/resources
. Since this directory was not created by the archetype:generate
task, we need to create it by executing the following commands from the project’s base directory:
$ cd src/main
$ mkdir resources
$ cd resources
Once the resources directory is created, we can add the two resources. First, add the log4j.properties
file in the resources
directory, as shown in Simple Weather’s Log4J Configuration File.
Simple Weather’s Log4J Configuration File.
# 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
This log4j.properties
file simply configures Log4J to print all log messages to standard output using a PatternLayout
. Lastly, we need to create the output.vm
, which is the Velocity template used to render the output of this command-line program. Create output.vm
in the resources
directory.
Simple Weather’s Output Velocity Template.
*********************************
Current Weather Conditions for:
${weather.city}, ${weather.region}, ${weather.country}
Temperature: ${weather.temp}
Condition: ${weather.condition}
Humidity: ${weather.humidity}
Wind Chill: ${weather.chill}
*********************************
This template contains a number of references to a variable named weather
, which is the Weather
bean that was passed to the WeatherFormatter
. The ${weather.temp}
syntax is shorthand for retrieving and displaying the value of the temp
bean property. Now that we have all of our project’s code in the right place, we can use Maven to run the example.
Using the Exec plugin from the Codehaus Mojo project, we can run the Main class:
$ mvn install ... [INFO] [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ simple-weather --- [INFO] Building jar: D:\maven\simple-weather\target\simple-weather-1.0.jar [INFO] [INFO] --- maven-install-plugin:2.4:install (default-install) @ simple-weather --- [INFO] Installing D:\maven\simple-weather\target\simple-weather-1.0.jar to d:\maven\repo\org\sonatype\mavenbook\custom\simple-weather\1.0\simple-weather-1.0.jar [INFO] Installing D:\maven\simple-weather\pom.xml to d:\maven\repo\org\sonatype\mavenbook\custom\simple-weather\1.0\simple-weather-1.0.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 5.930 s [INFO] Finished at: 2020-03-06T09:56:15+08:00 [INFO] Final Memory: 21M/311M [INFO] -- $ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main ... [INFO] [exec:java] [INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ simple-weather --- 1 INFO YahooRetriever - Retrieving Weather Data 6 INFO YahooParser - Creating XML Reader 172 INFO YahooParser - Parsing XML Response 244 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: New York, NY, US Temperature: 39 Condition: Fair Humidity: 67 Wind Chill: 39 ********************************* ...
We didn’t supply a command-line argument to the Main
class, so we ended up with the default zip code, 60202. To supply a zip code, we would use the -Dexec.args
argument and pass in a zip code:
$ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main -Dexec.args="70112" ... [INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ simple-weather --- 1 INFO YahooRetriever - Retrieving Weather Data 6 INFO YahooParser - Creating XML Reader 172 INFO YahooParser - Parsing XML Response 244 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: New York, NY, US Temperature: 39 Condition: Fair Humidity: 67 Wind Chill: 39 ********************************* ...
As you can see, we’ve successfully executed the Simple Weather command-line tool, retrieved some data from Yahoo Weather, parsed the result, and formatted the resulting data with Velocity. We achieved all of this without doing much more than writing our project’s source code and adding some minimal configuration to the pom.xml
. Notice that no “build process” was involved. We didn’t need to define how or where the Java compiler compiles our source to bytecode, and we didn’t need to instruct the build system how to locate the bytecode when we executed the example application. All we needed to do to include a few dependencies was locate the appropriate Maven coordinates.
The Exec plugin allows you to execute Java classes and other scripts. It is not a core Maven plugin, but it is available from the Mojo project hosted by Codehaus. For a full description of the Exec plugin, run:
$ mvn help:describe -Dplugin=exec -Dfull
This will list all of the goals that are available in the Maven Exec plugin. The Help plugin will also list all of the valid parameters for the Exec plugin. If you would like to customize the behavior of the Exec plugin you should use the documentation provided by help:describe
as a guide. Although the Exec plugin is useful, you shouldn’t rely on it as a way to execute your application outside of running tests during development. For a more robust solution, use the Maven Assembly plugin that is demonstrated in the section Section 4.13, “Building a Packaged Command Line Application”, later in this chapter.
D:\maven\simple-weather>mvn help:describe -Dplugin=exec -Dfull [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building simple-weather 1.0 [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-help-plugin:2.2:describe (default-cli) @ simple-weather --- [INFO] org.codehaus.mojo:exec-maven-plugin:1.2.1 Name: Exec Maven Plugin Description: A plugin to allow execution of system and Java programs Group Id: org.codehaus.mojo Artifact Id: exec-maven-plugin Version: 1.2.1 Goal Prefix: exec This plugin has 3 goals: exec:exec Description: A Plugin for executing external programs. Implementation: org.codehaus.mojo.exec.ExecMojo Language: java Available parameters: arguments A list of arguments passed to the executable, which should be of type <argument> or <classpath>. Can be overridden by using the exec.args environment variable. classpathScope (Default: runtime) User property: exec.classpathScope Defines the scope of the classpath passed to the plugin. Set to compile,test,runtime or system depending on your needs. Since 1.1.2, the default value is 'runtime' instead of 'compile'. commandlineArgs User property: exec.args Arguments for the executed program environmentVariables Environment variables to pass to the executed program. executable Required: true User property: exec.executable The executable. Can be a full path or a the name executable. In the latter case, the executable must be in the PATH for the execution to work. The plugin will search for the executable in the following order: 1. relative to the root of the project 2. as toolchain executable 3. relative to the working directory (Windows only) 4. relative to the directories specified in the system property PATH (Windows Only) Otherwise use the executable as is. longClasspath (Default: false) User property: exec.longClasspath If set to true the classpath and the main class will be written to a MANIFEST.MF file and wrapped into a jar. Instead of '-classpath/-cp CLASSPATH mainClass' the exec plugin executes '-jar maven-exec.jar'. outputFile User property: exec.outputFile Program standard and error output will be redirected to the file specified by this optional field. If not specified the standard maven logging is used. skip (Default: false) User property: skip Skip the execution. sourceRoot User property: sourceRoot This folder is added to the list of those folders containing source to be compiled. Use this if your plugin generates source code. successCodes Exit codes to be resolved as successful execution for non-compliant applications (applications not returning 0 for success). testSourceRoot User property: testSourceRoot This folder is added to the list of those folders containing source to be compiled for testing. Use this if your plugin generates test source code. workingDirectory User property: exec.workingdir The current working directory. Optional. If not specified, basedir will be used. exec:help Description: Display help information on exec-maven-plugin. Call mvn exec:help -Ddetail=true -Dgoal=<goal-name> to display parameter details. Implementation: org.codehaus.mojo.exec.HelpMojo Language: java Available parameters: detail (Default: false) User property: detail If true, display all settable properties for each goal. goal User property: goal The name of the goal for which to show help. If unspecified, all goals will be displayed. indentSize (Default: 2) User property: indentSize The number of spaces per indentation level, should be positive. lineLength (Default: 80) User property: lineLength The maximum length of a display line, should be positive. exec:java Description: Executes the supplied java class in the current VM with the enclosing project's dependencies as classpath. Implementation: org.codehaus.mojo.exec.ExecJavaMojo Language: java Before this mojo executes, it will call: Phase: 'validate' Available parameters: arguments User property: exec.arguments The class arguments. classpathScope (Default: runtime) User property: exec.classpathScope Defines the scope of the classpath passed to the plugin. Set to compile,test,runtime or system depending on your needs. Since 1.1.2, the default value is 'runtime' instead of 'compile'. cleanupDaemonThreads Expression: ${exec.cleanupDaemonThreads} default-value= Wether to interrupt/join and possibly stop the daemon threads upon quitting. If this is false, maven does nothing about the daemon threads. When maven has no more work to do, the VM will normally terminate any remaining daemon threads. In certain cases (in particular if maven is embedded), you might need to keep this enabled to make sure threads are properly cleaned up to ensure they don't interfere with subsequent activity. In that case, see daemonThreadJoinTimeout and stopUnresponsiveDaemonThreads for further tuning. commandlineArgs User property: exec.args # -Dexec.args="70112" 传递参数 Arguments for the executed program daemonThreadJoinTimeout (Default: 15000) User property: exec.daemonThreadJoinTimeout This defines the number of milliseconds to wait for daemon threads to quit following their interruption. This is only taken into account if cleanupDaemonThreads is true. A value <=0 means to not timeout (i.e. wait indefinitely for threads to finish). Following a timeout, a warning will be logged. Note: properly coded threads should terminate upon interruption but some threads may prove problematic: as the VM does interrupt daemon threads, some code may not have been written to handle interruption properly. For example java.util.Timer is known to not handle interruptions in JDK <= 1.6. So it is not possible for us to infinitely wait by default otherwise maven could hang. A sensible default value has been chosen, but this default value may change in the future based on user feedback. executableDependency If provided the ExecutableDependency identifies which of the plugin dependencies contains the executable class. This will have the affect of only including plugin dependencies required by the identified ExecutableDependency. If includeProjectDependencies is set to true, all of the project dependencies will be included on the executable's classpath. Whether a particular project dependency is a dependency of the identified ExecutableDependency will be irrelevant to its inclusion in the classpath. includePluginDependencies (Default: false) User property: exec.includePluginDependencies Indicates if this plugin's dependencies should be used when executing the main class. This is useful when project dependencies are not appropriate. Using only the plugin dependencies can be particularly useful when the project is not a java project. For example a mvn project using the csharp plugins only expects to see dotnet libraries as dependencies. includeProjectDependencies (Default: true) User property: exec.includeProjectDependencies Indicates if the project dependencies should be used when executing the main class. keepAlive (Default: false) User property: exec.keepAlive Indicates if mojo should be kept running after the mainclass terminates. Usefull for serverlike apps with deamonthreads. Deprecated. since 1.1-alpha-1 killAfter (Default: -1) User property: exec.killAfter Deprecated this is not needed anymore. Deprecated. since 1.1-alpha-1 mainClass Required: true User property: exec.mainClass The main class to execute. skip (Default: false) User property: skip Skip the execution. sourceRoot User property: sourceRoot This folder is added to the list of those folders containing source to be compiled. Use this if your plugin generates source code. stopUnresponsiveDaemonThreads Expression: ${exec.stopUnresponsiveDaemonThreads} default-value= Wether to call Thread.stop() following a timing out of waiting for an interrupted thread to finish. This is only taken into account if cleanupDaemonThreads is true and the daemonThreadJoinTimeout threshold has been reached for an uncooperative thread. If this is false, or if Thread.stop() fails to get the thread to stop, then a warning is logged and Maven will continue on while the affected threads (and related objects in memory) linger on. Consider setting this to true if you are invoking problematic code that you can't fix. An example is Timer which doesn't respond to interruption. To have Timer fixed, vote for this bug. systemProperties A list of system properties to be passed. Note: as the execution is not forked, some system properties required by the JVM cannot be passed here. Use MAVEN_OPTS or the exec:exec instead. See the user guide for more information. testSourceRoot User property: testSourceRoot This folder is added to the list of those folders containing source to be compiled for testing. Use this if your plugin generates test source code. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.381 s [INFO] Finished at: 2020-03-06T10:16:40+08:00 [INFO] Final Memory: 11M/241M [INFO] ------------------------------------------------------------------------
The Exec plugin makes it possible for us to run the Simple Weather program without having to load the appropriate dependencies into the classpath. In any other build system, we would have to copy all of the program dependencies into some sort of lib/
directory containing a collection of JAR files. Then, we would have to write a simple script that includes our program’s bytecode and all of our dependencies in a classpath. Only then could we run java org.sonatype.mavenbook.weather.Main
. The Exec plugin leverages the fact that Maven already knows how to create and manage your classpath and dependencies.
This is convenient, but it’s also nice to know exactly what is being included in your project’s classpath. Although the project depends on a few libraries such as Dom4J, Log4J, Jaxen, and Velocity, it also relies on a few transitive dependencies. If you need to find out what is on the classpath, you can use the Maven Dependency plugin to print out a list of dependencies.
$ mvn dependency:resolve ... [INFO] [dependency:resolve] [INFO] [INFO] The following files have been resolved: [INFO] jdom:jdom:jar:1.0:compile [INFO] velocity:velocity:jar:1.5:compile [INFO] xalan:xalan:jar:2.6.0:compile [INFO] log4j:log4j:jar:1.2.14:compile [INFO] commons-collections:commons-collections:jar:3.1:compile [INFO] xerces:xercesImpl:jar:2.6.2:compile [INFO] xerces:xmlParserAPIs:jar:2.6.2:compile [INFO] com.ibm.icu:icu4j:jar:2.6.1:compile [INFO] junit:junit:jar:3.8.1:test [INFO] xml-apis:xml-apis:jar:1.0.b2:compile [INFO] jaxen:jaxen:jar:1.1.1:compile [INFO] xom:xom:jar:1.0:compile [INFO] oro:oro:jar:2.0.8:compile [INFO] dom4j:dom4j:jar:1.6.1:compile [INFO] commons-lang:commons-lang:jar:2.1:compile
As you can see, our project has a very large set of dependencies. While we only included direct dependencies on four libraries, we appear to be depending on 15 dependencies in total. Dom4J depends on Xerces and the XML Parser APIs, and Jaxen depends on Xalan. The Dependency plugin is going to print out the final combination of dependencies under which your project is being compiled. If you would like to know about the entire dependency tree of your project, you can run the dependency:tree
goal.
$ mvn dependency:tree ... [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ simple-weather --- [INFO] org.sonatype.mavenbook.custom:simple-weather:jar:1.0 [INFO] +- log4j:log4j:jar:1.2.14:compile [INFO] +- dom4j:dom4j:jar:1.6.1:compile [INFO] | \- xml-apis:xml-apis:jar:1.0.b2:compile [INFO] +- jaxen:jaxen:jar:1.1.1:compile [INFO] | +- jdom:jdom:jar:1.0:compile [INFO] | +- xerces:xercesImpl:jar:2.6.2:compile [INFO] | \- xom:xom:jar:1.0:compile [INFO] | +- xerces:xmlParserAPIs:jar:2.6.2:compile [INFO] | +- xalan:xalan:jar:2.6.0:compile [INFO] | \- com.ibm.icu:icu4j:jar:2.6.1:compile [INFO] +- velocity:velocity:jar:1.5:compile [INFO] | +- commons-collections:commons-collections:jar:3.1:compile [INFO] | +- commons-lang:commons-lang:jar:2.1:compile [INFO] | \- oro:oro:jar:2.0.8:compile [INFO] \- junit:junit:jar:3.8.1:test ...
If you’re truly adventurous or want to see the full dependency trail, including artifacts that were rejected due to conflicts and other reasons, run Maven with the -X
debug flag.
$ mvn clean install -X ... [DEBUG] org.sonatype.mavenbook.custom:simple-weather:jar:1.0 (selected for null) [DEBUG] log4j:log4j:jar:1.2.14:compile (selected for compile) [DEBUG] dom4j:dom4j:jar:1.6.1:compile (selected for compile) [DEBUG] xml-apis:xml-apis:jar:1.0.b2:compile (selected for compile) [DEBUG] jaxen:jaxen:jar:1.1.1:compile (selected for compile) [DEBUG] jaxen:jaxen:jar:1.1-beta-6:compile (removed - ) [DEBUG] jaxen:jaxen:jar:1.0-FCS:compile (removed - ) [DEBUG] jdom:jdom:jar:1.0:compile (selected for compile) [DEBUG] xml-apis:xml-apis:jar:1.3.02:compile (removed - nearer: 1.0.b2) [DEBUG] xerces:xercesImpl:jar:2.6.2:compile (selected for compile) [DEBUG] xom:xom:jar:1.0:compile (selected for compile) [DEBUG] xerces:xmlParserAPIs:jar:2.6.2:compile (selected for compile) [DEBUG] xalan:xalan:jar:2.6.0:compile (selected for compile) [DEBUG] xml-apis:xml-apis:1.0.b2. [DEBUG] com.ibm.icu:icu4j:jar:2.6.1:compile (selected for compile) [DEBUG] velocity:velocity:jar:1.5:compile (selected for compile) [DEBUG] commons-collections:commons-collections:jar:3.1:compile [DEBUG] commons-lang:commons-lang:jar:2.1:compile (selected for compile) [DEBUG] oro:oro:jar:2.0.8:compile (selected for compile) [DEBUG] junit:junit:jar:3.8.1:test (selected for test)
In the debug output, we see some of the guts of the dependency management system at work. What you see here is the tree of dependencies for this project. Maven is printing out the full Maven coordinates for all of your project’s dependencies and the mechanism at work.
Maven has built-in support for unit tests, and testing is a part of the default Maven lifecycle. Let’s add some unit tests to our simple weather project. First, let’s create the org.sonatype.mavenbook.weather
package under src/test/java
:
$ cd src/test/java
$ cd org/sonatype/mavenbook
$ mkdir -p weather/yahoo
$ cd weather/yahoo
At this point, we will create two unit tests. The first will test the YahooParser
, and the second will test the WeatherFormatter
. In the weather
package, create a file named YahooParserTest.java
with the contents shown in the next example.
Simple Weather’s YahooParserTest Unit Test.
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() ); } }
This YahooParserTest
extends the TestCase
class defined by JUnit. It follows the usual pattern for a JUnit test: a constructor that takes a single String
argument that calls the constructor of the superclass, and a series of public methods that begin with “test
” that are invoked as unit tests. We define a single test method, testParser
, which tests the YahooParser
by parsing an XML document with known values. The test XML document is named ny-weather.xml
and is loaded from the classpath. We’ll add test resources in Section 4.11, “Adding Unit Test Resources”. In our Maven project’s directory layout, the ny-weather.xml
file is found in the directory that contains test resources — ${basedir}/src/test/resources
under org/sonatype/mavenbook/weather/yahoo/ny-weather.xml
. The file is read as an InputStream
and passed to the parse()
method on YahooParser
. The parse()
method returns a Weather
object, which is then tested with a series of calls to assertEquals()
, a method defined by TestCase
.
In the same directory, create a file named WeatherFormatterTest.java
.
Simple Weather’s WeatherFormatterTest Unit Test.
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 ).trim(), formattedResult.trim() ); } }
The second unit test in this simple project tests the WeatherFormatter
. Like the YahooParserTest
, the WeatherFormatterTest
also extends JUnit’s TestCase
class. The single test function reads the same test resource from ${basedir}/src/test/resources
under the org/sonatype/mavenbook/weather/yahoo
directory via this unit test’s classpath. We’ll add test resources in Section 4.11, “Adding Unit Test Resources”. WeatherFormatterTest
runs this sample input file through the YahooParser
which spits out a Weather
object, and this object is then formatted with the WeatherFormatter
. Since the WeatherFormatter
prints out a String
, we need to test it against some expected input. Our expected input has been captured in a text file named format-expected.dat
which is in the same directory as ny-weather.xml
. To compare the test’s output to the expected output, we read this expected output in as an InputStream
and use Commons IO’s IOUtils
class to convert this file to a String
. This String
is then compared to the test output using assertEquals()
.
In WeatherFormatterTest
, we used a utility from Apache Commons IO—the IOUtils
class. IOUtils
provides a number of helpful static methods that take most of the work out of input/output operations. In this particular unit test, we used IOUtils.toString()
to copy the format-expected.dat
classpath resource to a String
. We could have done this without using Commons IO, but it would have required an extra six or seven lines of code to deal with the various InputStreamReader
and StringWriter
objects. The main reason we used Commons IO was to give us an excuse to add a test
-scoped dependency on Commons IO.
A test
-scoped dependency is a dependency that is available on the classpath only during test compilation and test execution. If your project has war
or ear
packaging, a test
-scoped dependency would not be included in the project’s output archive. To add a test
-scoped dependency, add the dependency
element to your project’s dependencies
section, as shown in the following example:
Adding a Test-scoped Dependency.
<project>
...
<dependencies>
...
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
<scope>test</scope>
</dependency>
...
</dependencies>
</project>
After you add this dependency to the pom.xml
, run mvn dependency:resolve
and you should see that commons-io
is now listed as a dependency with scope test
. We need to do one more thing before we are ready to run this project’s unit tests. We need to create the classpath resources these unit tests depend on.
[INFO] The following files have been resolved: [INFO] jdom:jdom:jar:1.0:compile [INFO] velocity:velocity:jar:1.5:compile [INFO] xalan:xalan:jar:2.6.0:compile [INFO] log4j:log4j:jar:1.2.14:compile [INFO] commons-collections:commons-collections:jar:3.1:compile [INFO] xerces:xercesImpl:jar:2.6.2:compile [INFO] xerces:xmlParserAPIs:jar:2.6.2:compile [INFO] com.ibm.icu:icu4j:jar:2.6.1:compile [INFO] junit:junit:jar:3.8.1:test [INFO] xml-apis:xml-apis:jar:1.0.b2:compile [INFO] commons-io:commons-io:jar:1.3.2:test [INFO] jaxen:jaxen:jar:1.1.1:compile [INFO] xom:xom:jar:1.0:compile [INFO] oro:oro:jar:2.0.8:compile [INFO] dom4j:dom4j:jar:1.6.1:compile [INFO] commons-lang:commons-lang:jar:2.1:compile
A unit test has access to a set of resources which are specific to tests. Often you’ll store files containing expected results and files containing dummy input in the test classpath. In this project, we’re storing a test XML document for YahooParserTest
named ny-weather.xml
and a file containing expected output from the WeatherFormatter
in format-expected.dat
.
To add test resources, you’ll need to create the src/test/resources
directory. This is the default directory in which Maven looks for unit test resources. To create this directory execute the following commands from your project’s base directory.
$ cd src/test
$ mkdir resources
$ cd resources
Once you’ve create the resources directory, create a file named format-expected.dat
in the resources
directory.
Simple Weather’s WeatherFormatterTest Expected Output.
*********************************
Current Weather Conditions for:
New York, NY, US
Temperature: 39
Condition: Fair
Humidity: 67
Wind Chill: 39
*********************************
This file should look familiar. It is the same output that was generated previously when you ran the Simple Weather project with the Maven Exec plugin. The second file you’ll need to add to the resources directory is ny-weather.xml
.
Simple Weather’s YahooParserTest XML Input.
<?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>
This file contains a test XML document for the YahooParserTest
. We store this file so that we can test the YahooParser
without having to retrieve an XML response from Yahoo Weather.
Now that your project has unit tests, let’s run them. You don’t have to do anything special to run a unit test; the test
phase is a normal part of the Maven lifecycle. You run Maven tests whenever you run mvn package
or mvn install
. If you would like to run all the lifecycle phases up to and including the test
phase, run mvn test
:
$ mvn test ... [INFO] [surefire:test] [INFO] Surefire report directory: ~/examples/ch-custom/simple-weather/target/surefire-reports ------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.sonatype.mavenbook.weather.yahoo.WeatherFormatterTest 0 INFO YahooParser - Creating XML Reader 230 INFO YahooParser - Parsing XML Response 318 INFO WeatherFormatter - Formatting Weather Data Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.62 sec Running org.sonatype.mavenbook.weather.yahoo.YahooParserTest 490 INFO YahooParser - Creating XML Reader 495 INFO YahooParser - Parsing XML Response Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 sec Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
Executing mvn test
from the command line caused Maven to execute all lifecycle phases up to the test
phase. The Maven Surefire plugin has a test
goal which is bound to the test
phase. This test
goal executes all of the unit tests this project can find under src/test/java
with filenames matching **/Test*.java
, **/*Test.java
and **/*TestCase.java
. In the case of this project, you can see that the Surefire plugin’s test
goal executed WeatherFormatterTest
and YahooParserTest
. When the Maven Surefire plugin runs the JUnit tests, it also generates XML and text reports in the ${basedir}/target/surefire-reports
directory. If your tests are failing, you should look in this directory for details like stack traces and error messages generated by your unit tests.
<?xml version="1.0" encoding="UTF-8" ?> <testsuite tests="1" failures="0" name="org.sonatype.mavenbook.weather.yahoo.YahooParserTest" time="0.005" errors="0" skipped="0"> <properties> <property name="java.runtime.name" value="Java(TM) SE Runtime Environment"/> <property name="sun.boot.library.path" value="C:\Program Files\Java\jdk1.8.0_121\jre\bin"/> <property name="java.vm.version" value="25.121-b13"/> <property name="java.vm.vendor" value="Oracle Corporation"/> <property name="maven.multiModuleProjectDirectory" value="D:\maven\simple-weather"/> <property name="java.vendor.url" value="http://java.oracle.com/"/> <property name="path.separator" value=";"/> <property name="guice.disable.misplaced.annotation.check" value="true"/> <property name="java.vm.name" value="Java HotSpot(TM) 64-Bit Server VM"/> <property name="file.encoding.pkg" value="sun.io"/> <property name="user.script" value=""/> <property name="user.country" value="CN"/> <property name="sun.java.launcher" value="SUN_STANDARD"/> <property name="sun.os.patch.level" value="Service Pack 1"/> <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/> <property name="user.dir" value="D:\maven\simple-weather"/> <property name="java.runtime.version" value="1.8.0_121-b13"/> <property name="java.awt.graphicsenv" value="sun.awt.Win32GraphicsEnvironment"/> <property name="java.endorsed.dirs" value="C:\Program Files\Java\jdk1.8.0_121\jre\lib\endorsed"/> <property name="os.arch" value="amd64"/> <property name="java.io.tmpdir" value="C:\Users\ADMINI~1\AppData\Local\Temp\"/> <property name="line.separator" value=" "/> <property name="java.vm.specification.vendor" value="Oracle Corporation"/> <property name="user.variant" value=""/> <property name="os.name" value="Windows 7"/> <property name="classworlds.conf" value="D:\Program Files\apache-maven-3.5.0\bin\..\bin\m2.conf"/> <property name="sun.jnu.encoding" value="GBK"/> <property name="java.library.path" value="C:\Program Files\Java\jdk1.8.0_121\bin;C:\windows\Sun\Java\bin;C:\windows\system32;C:\windows;D:\Program Files\chromedriver;C:\Program Files\Python\Scripts\;C:\Program Files\Python\;D:\app\Administrator\product\11.2.0\dbhome_1\bin;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\windows\system32;C:\windows;C:\windows\System32\Wbem;C:\windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\Intel\WiFi\bin\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\AMD\ATI.ACE\Core-Static;C:\Program Files\TortoiseSVN\bin;C:\Program Files\MySQL\MySQL Server 5.5\bin;C:\Program Files\Calibre2\;C:\Program Files\MySQL\MySQL Utilities 1.6\;d:\Program Files\Git\cmd;C:\Program Files\nodejs\;C:\Program Files\MySQL\MySQL Shell 8.0\bin\;.;C:\Program Files\Java\jdk1.8.0_121\bin;C:\Program Files\Intel\WiFi\bin\;D:\Program Files\apache-maven-3.5.0\bin;D:\Program Files\gradle-5.3\bin;C:\Program Files\MySQL\MySQL Server 5.5\bin;C:\Users\Administrator\AppData\Local\GitHubDesktop\bin;C:\Program Files\WinRAR;D:\ext\Sencha\Cmd\4.0.5.87;D:\Program Files\spring-2.1.6.RELEASE\bin;E:\software\cmder;D:\Program Files\apache-ant-1.9.14\bin;D:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.6\sbin;D:\Programs\Microsoft VS Code\bin;D:\Program Files\Fiddler;C:\Users\Administrator\AppData\Local\Pandoc\;C:\Users\Administrator\AppData\Roaming\npm;."/> <property name="maven.conf" value="D:\Program Files\apache-maven-3.5.0\bin\../conf"/> <property name="java.specification.name" value="Java Platform API Specification"/> <property name="java.class.version" value="52.0"/> <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/> <property name="os.version" value="6.1"/> <property name="library.jansi.path" value="D:\Program Files\apache-maven-3.5.0\bin\..\lib\jansi-native\windows64"/> <property name="user.home" value="C:\Users\Administrator"/> <property name="user.timezone" value="Asia/Shanghai"/> <property name="java.awt.printerjob" value="sun.awt.windows.WPrinterJob"/> <property name="java.specification.version" value="1.8"/> <property name="file.encoding" value="GBK"/> <property name="user.name" value="Administrator"/> <property name="java.class.path" value="D:\Program Files\apache-maven-3.5.0\bin\..\boot\plexus-classworlds-2.5.2.jar"/> <property name="java.vm.specification.version" value="1.8"/> <property name="sun.arch.data.model" value="64"/> <property name="java.home" value="C:\Program Files\Java\jdk1.8.0_121\jre"/> <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher clean test"/> <property name="java.specification.vendor" value="Oracle Corporation"/> <property name="user.language" value="zh"/> <property name="awt.toolkit" value="sun.awt.windows.WToolkit"/> <property name="java.vm.info" value="mixed mode"/> <property name="java.version" value="1.8.0_121"/> <property name="java.ext.dirs" value="C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext;C:\windows\Sun\Java\lib\ext"/> <property name="sun.boot.class.path" value="C:\Program Files\Java\jdk1.8.0_121\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_121\jre\classes"/> <property name="sun.stderr.encoding" value="ms936"/> <property name="java.vendor" value="Oracle Corporation"/> <property name="maven.home" value="D:\Program Files\apache-maven-3.5.0\bin\.."/> <property name="file.separator" value="\"/> <property name="java.vendor.url.bug" value="http://bugreport.sun.com/bugreport/"/> <property name="sun.cpu.endian" value="little"/> <property name="sun.io.unicode.encoding" value="UnicodeLittle"/> <property name="sun.stdout.encoding" value="ms936"/> <property name="sun.desktop" value="windows"/> <property name="sun.cpu.isalist" value="amd64"/> </properties> <testcase classname="org.sonatype.mavenbook.weather.yahoo.YahooParserTest" name="testParser" time="0.005"/> </testsuite>
You will often find yourself developing on a system that has failing unit tests. If you are practicing Test-Driven Development (TDD), you might use test failure as a measure of how close your project is to completeness. If you have failing unit tests, and you would still like to produce build output, you are going to have to tell Maven to ignore build failures. When Maven encounters a build failure, its default behavior is to stop the current build. To continue building a project even when the Surefire plugin encounters failed test cases, you’ll need to set the testFailureIgnore
configuration property of the Surefire plugin to true
.
Ignoring Unit Test Failures.
<project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
The plugin documents (http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html) show that this parameter declares an expression:
Plugin Parameter Expressions.
testFailureIgnore Set this to true to ignore a failure during
testing. Its use is NOT RECOMMENDED, but quite
convenient on occasion.
* Type: boolean
* Required: No
* User Property: maven.test.failure.ignore
This property can be set from the command line using the -D
parameter:
$ mvn test -Dmaven.test.failure.ignore=true
You may want to configure Maven to skip unit tests altogether. Maybe you have a very large system where the unit tests take minutes to complete and you don’t want to wait for unit tests to complete before producing output. You might be working with a legacy system that has a series of failing unit tests, and instead of fixing the unit tests, you might just want to produce a JAR. Maven provides for the ability to skip unit tests using the skip
parameter of the Surefire plugin. To skip tests from the command line, simply add the maven.test.skip
property to any goal:
$ mvn install -Dmaven.test.skip=true
...
[INFO] [compiler:testCompile]
[INFO] Not compiling test sources
[INFO] [surefire:test]
[INFO] Tests are skipped.
...
When the Surefire plugin reaches the test
goal, it will skip the unit tests if the maven.test.skip
properties is set to true
. Another way to configure Maven to skip unit tests is to add this configuration to your project’s pom.xml
. To do this, you would add a plugin
element to your build configuration.
Skipping Unit Tests.
<project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
[...]
</project>
In Section 4.8, “Running the Simple Weather Program” earlier in descriptor in the Maven Assembly plugin to produce a distributable JAR file, which contains the project’s bytecode and all of the dependencies.
The Maven Assembly plugin is a plugin you can use to create arbitrary distributions for your applications. You can use the Maven Assembly plugin to assemble the output of your project in any format you desire by defining a custom assembly descriptor. In a later chapter we will show you how to create a custom assembly descriptor which produces a more complex archive for the Simple Weather application. In this chapter, we’re going to use the predefined jar-with-dependencies
format. To configure the Maven Assembly Plugin, we need to add the following plugin configuration to our existing build configuration in the pom.xml
.
Configuring the Maven Assembly Descriptor.
<project> [...] <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </build> [...] </project>
Once you’ve added this configuration, you can build the assembly by running the assembly:assembly
goal. In the following screen listing, the assembly:assembly
goal is executed after the Maven build reaches the install
lifecycle phase:
$ mvn install assembly:assembly ... [INFO] [jar:jar] [INFO] Building jar: ~/examples/ch-custom/simple-weather/target/simple-weather-1.0.jar [INFO] [assembly:assembly] [INFO] Processing DependencySet (output=) [INFO] Expanding: \ .m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar into \ /tmp/archived-file-set.1437961776.tmp [INFO] Expanding: .m2/repository/commons-lang/commons-lang/2.1/\ commons-lang-2.1.jar into /tmp/archived-file-set.305257225.tmp ... (Maven Expands all dependencies into a temporary directory) ... [INFO] Building jar: \ ~/examples/ch-custom/simple-weather/target/\ simple-weather-1.0-jar-with-dependencies.jar
Once our assembly is assembled in target/simple-weather-1.0-jar-with-dependencies.jar
, we can run the Main
class again from the command line. To run the simple weather application’s Main
class, execute the following commands from your project’s base directory:
$ cd target $ java -cp simple-weather-1.0-jar-with-dependencies.jar \ org.sonatype.mavenbook.weather.Main 10002 0 INFO YahooRetriever - Retrieving Weather Data 221 INFO YahooParser - Creating XML Reader 399 INFO YahooParser - Parsing XML Response 474 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: New York, NY, US Temperature: 44 Condition: Fair Humidity: 40 Wind Chill: 40 *********************************
The jar-with-dependencies
format creates a single JAR file that includes all of the bytecode from the simple-weather
project as well as the unpacked bytecode from all of the dependencies. This somewhat unconventional format produces a 9 MiB JAR file containing approximately 5,290 classes, but it does provide for an easy distribution format for applications you’ve developed with Maven. Later in this book, we’ll show you how to create a custom assembly descriptor to produce a more standard distribution.
In Maven 1, a build was customized by stringing together a series of plugin goals. Each plugin goal had prerequisites and defined a relationship to other plugin goals. With the release of Maven 2, a lifecycle was introduced and plugin goals are now associated with a series of phases in a default Maven build lifecycle. The lifecycle provides a solid foundation that makes it easier to predict and manage the plugin goals which will be executed in a given build. In Maven 1, plugin goals related to one another directly; in Maven 2, plugin goals relate to a set of common lifecycle stages. While it is certainly valid to execute a plugin goal directly from the command line as we just demonstrated, it is more consistent with the design of Maven to configure the Assembly plugin to execute the assembly:assembly
goal during a phase in the Maven lifecycle.
The following plugin configuration configures the Maven Assembly plugin to execute the attached
goal during the package
phase of the Maven default build lifecycle. The attached
goal does the same thing as the assembly
goal. To bind to assembly:attached
goal to the package
phase we use the executions
element under plugin
in the build
section of the project’s POM.
Configuring Attached Goal Execution During the Package Lifecycle Phase.
<project> [...] <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>simple-command</id> <phase>package</phase> <goals> <goal>attached</goal> </goals> </execution> </executions> </plugin> </plugins> </build> [...] </project>
Once you have this configuration in your POM, all you need to do to generate the assembly is run mvn package
. The execution configuration will make sure that the assembly:attached
goal is executed when the Maven lifecycle transitions to the package
phase of the lifecycle. The assembly will also be created if you run mvn install
, as the package
phase precedes the install
phase in the default Maven lifecycle.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。