1, Demon见http://www.sonatype.com/book/mvn-examples-1.0.zip 的ch-multi-spring 目录。
2, 架构示例图(在Simple Parent下构建5个工程):
箭头指明POM继承关系。
这里需要明白一点,POM继承关系不决定父子模块的关系,个人认为POM继承和父子模块继承设计成一致也是出于简化方便的考虑。这里的POM继承关系刚好和父子模块关系是一致的。POM继承称之为“继承关系”,父子模块称之为“聚合关系”。继承和聚合的更详细说明见“Maven聚合与继承”。
3, 依赖关系
Simple Model: 简单对象模型。
Simple Weather: 通过调用外部接口获取天气数据并解析XML结果的逻辑封装;
直接依赖于Simple Model。
Simple Persist: 数据访问对象层(DAO);直接依赖于Simple Model。
Simple WebApp: 控制层和Web展示层;
直接依赖于Simple Weather和Simple Persist。
Simple Command: 控制层和命令行展示层;
直接依赖于Simple Weather和Simple Persist。
4, Simple Parent POM
<groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <modules><!—子模块配置,聚合关系--> <module>simple-command</module> <module>simple-model</module> <module>simple-weather</module> <module>simple-persist</module> <module>simple-webapp</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </pluginManagement> </build> […]
5, Simple Model 模块
对象模型使用Hibernate标注来映射模型对象至关系数据库的表,依赖于org.hibernate:hibernate-annotations。POM:
<parent><!—父POM申明,继承关系--> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-model</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> </dependencies> […]
Weather模型对象:
@Entity @NamedQueries({@NamedQuery (name="Weather.byLocation", query="from Weather w where w.location = :location")//一个hql查询 }) public class Weather { @Id @GeneratedValue(strategy=GenerationType.IDENTITY)// @GeneratedValue控制新的主键值如何产生,IDENTITY GenerationType,使用了下层数据库的主键生成设施 private Integer id; @ManyToOne(cascade=CascadeType.ALL)//级联,多对一(weather多location一) private Location location; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Condition condition; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Wind wind; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Atmosphere atmosphere; private Date date;//默认映射,@Transient可忽略映射 public Weather() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } // All getter and setter methods omitted...
Condition模型对象:
@Entity public class Condition { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private String text; private String code; private String temp; private String date; @OneToOne(cascade=CascadeType.ALL) @JoinColumn(name="weather_id", nullable=false)// 通过一个名为weather_id的外键引用关联 的Weather对象 private Weather weather; public Condition() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } // All getter and setter methods omitted... }
6, Simple Weather 模块
POM:
[…] <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Simple Weather API</name> […] <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-model</artifactId><!--子模块间的直接依赖--> <version>1.0</version> </dependency> […]
此模块暴露一个服务 WeatherService,获取和解析天气并返回Weather对象。
由于Simple WebApp 和 Simple Command 都将通过调用WeatherService获取结果,这里使用Spring Framework 的IOC实现,在此模块提供Spring的beans资源文件,通过ClasspathXmlApplicationContext,另外两个模块均可调用到WeatherService 的实例。
Spring的ApplicationContext资源文件bean配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd default-lazy-init="true"> <bean id="weatherService" class="org.sonatype.mavenbook.weather.WeatherService"> <property name="yahooRetriever" ref="yahooRetriever"/> <property name="yahooParser" ref="yahooParser"/> </bean> <bean id="yahooRetriever" class="org.sonatype.mavenbook.weather.YahooRetriever"/> <!--接收雅虎天气的实现--> <bean id="yahooParser" class="org.sonatype.mavenbook.weather.YahooParser"/><!--解析雅虎天气的实现--> </beans>
7, Simple Persist 模块
从雅虎获取的天气信息和本地数据库交互的DAO层。POM文件略,直接依赖于Simple Model,继承于Simple Parent。
WeatherDAO类:
public class WeatherDAO extends HibernateDaoSupport{ public WeatherDAO() {} public void save(Weather weather) { getHibernateTemplate().save( weather ); } public Weather load(Integer id) { return (Weather) getHibernateTemplate().load( Weather.class, id); } public List<Weather> recentForLocation( final Location location ) { return (List<Weather>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) { Query query = getSession().getNamedQuery("Weather.byLocation");//使用Hibernate 标注,见Weather的Model query.setParameter("location", location); return new ArrayList<Weather>( query.list() ); } }); } }
这里也使用到Spring,这里的Spring ApplicationContext 资源配置文件如下:
<property name="annotatedClasses"> <list> <value>org.sonatype.mavenbook.weather.model.Weather</value> <value>org.sonatype.mavenbook.weather.model.Wind</value> […] </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">false</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </prop> <prop key="hibernate.dialect"> org.hibernate.dialect.HSQLDialect </prop> <prop key="hibernate.connection.pool_size">0</prop> <prop key="hibernate.connection.driver_class"> org.hsqldb.jdbcDriver </prop> <prop key="hibernate.connection.url"> jdbc:hsqldb:data/weather;shutdown=true </prop> <prop key="hibernate.connection.username">sa</prop> <prop key="hibernate.connection.password"></prop> <prop key="hibernate.connection.autocommit">true</prop> <prop key="hibernate.jdbc.batch_size">0</prop> </props> </property> </bean><bean id="locationDAO"
class="org.sonatype.mavenbook.weather.persist.LocationDAO">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="weatherDAO"
class="org.sonatype.mavenbook.weather.persist.WeatherDAO">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
8, Simple WebApp 模块
weather-servlet.xml为simmple-webapp中使用的Spring MVC的资源配置文件,applicationContext-xxx.xml为各自项目Spring IOC使用的普通配置文件。
weatherController: 使用weatherService获取天气,使用weatherDAO持久化天气。
POM:
<groupId>org.sonatype.mavenbook.multispring</groupId><!—继承,注意不要纠结值,这是为了表现形式--> <artifactId>simple-parent</artifactId> <version>0.8-SNAPSHOT</version> </parent> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.multispring</groupId> <artifactId>simple-weather</artifactId> <version>0.8-SNAPSHOT</version> </dependency> <dependency> <groupId>org.sonatype.mavenbook.multispring</groupId> <artifactId>simple-persist</artifactId> <version>0.8-SNAPSHOT</version> </dependency> </dependencies><build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<dependencies>
<dependency><!—轻量级数据库-->
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>[…]
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>hibernate3-maven-plugin</artifactId><!—hibernate插件-->[…]
<configuration>
<components>
<component>
<name>hbm2ddl</name><!—使用注解配置形式实现hbm to ddl-->
<implementation>annotationconfiguration</implementation>
</component>
</components>
</configuration>
<dependencies>
<dependency><!--让Maven Hibernate3插件能成功的使用JDBC连接该数据库,
需要引用HSQLDB JDBC驱动-->
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>[…]
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Maven hibernate 插件:
l 如果正在使用Hibernate映射XML文件(.hbm.xml),要使用hbm2java目标生成模型类,implementation设置成“configuration”。
l 如果使用Hibernate3插件逆向工程从一个数据库产生.hbm.xml文件和模型类, implementation设置成“jdbcconfiguration”。
l 如果使用现存的标注对象模型来生成一个数据库,也就是有Hibernate映射,但没有数据库,implementation设置成“annotationconfiguration”。
Tip:不建议使用<extensions>添加插件需要的依赖,会造成classpath污染,除非是定义新的wagon实现。
WeatherController类如下:
[…] public class WeatherController implements Controller {//实现Spring MVC Controller接口 private WeatherService weatherService; private WeatherDAO weatherDAO; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { String zip = request.getParameter("zip"); Weather weather = weatherService.retrieveForecast(zip);//获取天气 weatherDAO.save(weather);//持久化天气 return new ModelAndView("weather", "weather", weather);//返回数据给模板 ,第一个参数为模板名称 […]
ModelAndView类将被用来呈现Velocity模板,这个模板有对${weather}变量的引用。weather.vm模板存储在src/main/webapp/WEB-INF/vm/下,如下所示:
${weather.location.city}, ${weather.location.region}, ${weather.location.country}</b><br/> <ul> <li>Temperature: ${weather.condition.temp}</li> <li>Condition: ${weather.condition.text}</li> <li>Humidity: ${weather.atmosphere.humidity}</li> <li>Wind Chill: ${weather.wind.chill}</li> <li>Date: ${weather.date}</li> </ul>HistoryController及其对应的 history.vm 略。
主要用于Spring MVC的 weather-servlet.xml配置文件如下:
<bean id="weatherController" class="org.sonatype.mavenbook.web.WeatherController"> <property name="weatherService" ref="weatherService"/> <property name="weatherDAO" ref="weatherDAO"/> </bean> <bean id="historyController" class="org.sonatype.mavenbook.web.HistoryController"> <property name="weatherDAO" ref="weatherDAO"/> <property name="locationDAO" ref="locationDAO"/> </bean> <!-- you can have more than one handler defined --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"><!—URL映射模式--> <map> <entry key="/weather.x"> <ref bean="weatherController" /> </entry> <entry key="/history.x"> <ref bean="historyController" /> </entry> </map> </property> </bean> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/vm/"/><!—velocity模板目录 配置--> </bean><!—使用Velocity的配置--> <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="cache" value="true"/><!—velocity模板配置--> <property name="prefix" value=""/> <property name="suffix" value=".vm"/> <property name="exposeSpringMacroHelpers" value="true"/> </bean> </beans>
web.xml:
<param-name>contextConfigLocation</param-name> <param-value><!—加载 Spring配置文件,依赖项目打包后的Jar将位于本项目的lib目录下--> classpath:applicationContext-weather.xml classpath:applicationContext-persist.xml </param-value> </context-param> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param> <listener> <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener> <listener> <listener-class> <!—Spring上下文监听器,本质上是一个Spring容器,根据contextConfigLocation参数构造一个ApplicationContext--> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet><!--定义一个名为weather的Spring MVC DispatcherServlet。这会让Spring 从/WEB-INF/weather-servlet.xml寻找Spring配置文件--> <servlet-name>weather</servlet-name> <servlet-class><!—Spring URL分发--> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>weather</servlet-name> <url-pattern>*.x</url-pattern> </servlet-mapping>
9, 运行Simple WebApp
1),执行”mvn hibernate3:hbm2ddl”, 使用Hibernate3插件构造HSQLDB数据库
2), 执行”mvn jetty:run”
3), 访问http://localhost:8080/simple-webapp/weather.x?zip=60202
10, Simple Command 模块
simple-command项目是simple-webapp的一个命令行版本。这个命令行工具有这同样的模块依赖:simple-persist和simple-weather。我们从命令行运行这个simple-command工具,而非通过web浏览器与该应用交互。
POM:
[…]<parent>…</parent>
<artifactId>simple-command</artifactId>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore><!—忽略测试失败-->
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId><!—装配插件-->
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef><!—包含运行应用所需要的所有二进制代码-->
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>hibernate3-maven-plugin</artifactId>
[此处hibernate插件的配置同Simple WebApp模块的POM]
</plugin>
</plugins>
</build>[…]
此模块提供Main类,提供main方法,通过Sping的ClassPathXmlApplicationContext加载Spring的bean配置文件,获取bean实例实现数据的获取、调用、持久化操作。这里还使用到Velocity,手工加载Velocity模板,给其上下文注入值,具体使用不作介绍。
11, 运行 Simple Command
这里使用装配插件打包,目的是将本Command工程所以来的环境都打包进来,使用命令:mvn assembly:assembly;
然后执行:mvn hibernate3:hbm2ddl, 创建HSQLDB数据库;
最后可以运行Jar的入口类。
12, 通过以上介绍,可以观察到我们可以通过Maven聚合的概念,分离接口模块和实现模块为两个不同的模块,而可以并存多个实现模块,在其他聚合模块只需要使用接口模块的依赖进行编码,不同的实现可以通过依赖坐标的切换实现(似乎没有意义?)。