4.6. Simple Weather源码

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 模板生成结果。

这里我们不是想要详细阐述样例中的代码,但解释一下程序中使之运行的核心代码还是必要的。 我们假设大部分读者已经下载的本书的源码,但也不会忘记那些按着书一步一步往下看的读者。 本小节列出了 simple-weather 项目的类,这些类都放在同一个包下面,org.sonatype.mavenbook.weather

让我们删掉由 archetype:create 生成 App 类和 AppTest 类,然后加入我们新的包。在 Maven 项目中,所有项目的源代码都存储在 src/main/java 目录。 在新项目的基础目录下,运行下面的命令:

$ cd src/test/java/org/sonatype/mavenbook
$ rm AppTest.java
$ cd ../../../../../..
$ cd src/main/java/org/sonatype/mavenbook
$ rm App.java
$ mkdir weather
$ cd weather

你已经创建了一个新的包 org.sonatype.mavenbook.weather 。 现在,我们需要把那些类放到这个目录下面。 用你最喜欢的编辑器,创建一个新文件,名字为 Weather.java,内容如下:

Example 4.4. Simple Weather 的 Weather 模型对象

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 模型对象只保存温度,湿度和当前天气情况的文字描述等信息。

在同一目录下,创建 Main.java 文件。Main 这个类有一个静态的 main() 函数——样例程序的入口。

Example 4.5. Simple Weather 的 Main 类

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)
    int zipcode = 60202;
    try {
      zipcode = Integer.parseInt(args[0]);
    } catch( Exception e ) {}

    // Start the program
    new Main(zipcode).start();
  }

  private int zip;

  public Main(int 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 ,打印到标准输出。

在相同目录下创建文件 YahooRetriever.java ,内容如下:

Example 4.6. Simple Weather 的 YahooRetriever 类

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

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

Example 4.7. Simple Weather 的 YahooParser 类

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

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

我们已经创建了 Weather 对象,我们需要格式化输出以供人阅读。 在同一目录中创建一个名为 WeatherFormatter.java 的文件。

Example 4.8. Simple Weather 的 WeatherFormatter 类

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 添加一些资源。