`

基于Struts2+Spring+iBatis的web应用最佳实践系列之一(自动配置篇)

阅读更多

由于最近有点时间,便想动手写点东西,其一算是对自己这段时间来项目经验的一个总结,其二也希望能和大家探讨下最佳实践这个主题。说来也怪,网络上关于这三个框架的介绍很多,整合的教程也很多,但绝大多数都属于入门级别,浅尝则止,对于探讨如何在实际项目中用好他们,如何发挥出整合后的巨大威力的文章却很少,甚至连javaEye都没有最佳实践这个分类。不知道是大家的忽视还是那些大牛们藏着掖着不愿拿出来与大家分享。但笔者觉得,再好的框架给你,你不能用好它也是白搭。同时笔者也觉得,真正好的东西不是闭门造车造出来的,而是在与外界交流中不断完善起来的。本着“不是原创的不发,没有新意的不发”的原则,笔者在此保证,虽然有些工具和类库等从网上搜集或改造而来,但本系列文章绝对原创,都是笔者在项目实践当中思考总结而来。并会在本系列最后放出一个demo以及所有源代码供下载。但同时在这里也指出,本系列不是入门教程,需要读者有一定的基础,如果对有些技术点不是很明白的话,请自行查阅相关资料(网上入门级别的介绍资料很多)。

 

作为本系列的开篇,主要讨论如何合理的管理配置文件,把这个主题放在第一位主要也考虑到本篇所介绍或涉及到的技巧、工具等其后的篇章会应用到。其实写好技术类文章也是挺难的,在实际工作中可能一个项目里就涉及到多个新的技术点,而这些技术点往往是作为一个互相联系的整体而存在的,要把涉及到的每个技术点清晰而又有条理的向读者介绍清楚确实是一大考验。闲话少叙,就此转入正题。如果诸位有一定的项目经历的话,一定会和形形色色的诸多配置打过交道,比如web应用当中的web.xml,struts2中的struts.xml,又比如iBatis中对数据源的配置。但不知道大家有没有发现,其中有些配置是部署特有的(deployment specific),比如iBatis中对数据源的配置;而有些是应用特有的(application specific),比如struts2中对拦截器的配置。对那些应用特有的配置,不管这个应用被部署到那里,这个配置都不会发生变化,而部署特有的配置则不一样,部署到不同的环境具体的配置则不一样。最典型的莫过于对数据源的配置,开发人员在开发的时候需要将应用程序连接到本地的数据库做开发用,当部署到生产环境时则需要切换到生产环境的数据库。如果不能将部署特有的配置与应用特有的配置相隔离,那么该应用的部署则会成为噩梦。

 

好在我们有Spring。那么,Spring将如何帮助我们把部署特有的配置同应用特有的配置隔离出来?Spring就象一个宝藏,只要细心发掘,总会有意向不到的收获。在Spring中有一个PropertyPlaceholderConfigurer类,使用这个类在解析bean配置中遇到${...}这样的占位符后可以从properties文件中将属性读取出来替换占位符。

 

Example XML context definition:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName"><value>${driver}</value></property>
   <property name="url"><value>jdbc:${dbname}</value></property>
</bean>

 

Example properties file:

driver=com.mysql.jdbc.Driver
dbname=mysql:mydb

 

当然 PropertyPlaceholderConfigurer本身也要配置成一个bean,在这个bean中用以指定我们properties文件的位置。

 

<bean id="propertyConfigurer"
	class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations">
		<list>
			<value>classpath:com/meidusa/demo/dal/datasource.properties</value>
		</list>
	</property>
</bean>

 

这样我们通过使用PropertyPlaceholderConfigurer将部署特有的配置隔离到了一个properties文件里,有意思的是PropertyPlaceholderConfigurer还有一个孪生兄弟PropertyOverrideConfigurer,同样也是从properties文件中读取配置属性,但与PropertyPlaceholderConfigurer不同的是,使用PropertyOverrideConfigurer不需要再使用占位符,而是可以在bean定义中先指定一个默认值,而在properties文件中指定的值会override bean的默认值,形象的说就是PropertyOverrideConfigurer是将值从properties文件中push到bean定义中而PropertyPlaceholderConfigurer则是将值从properties文件中pull到bean定义中。

 

诸位看到这里或许会想,这篇文章里到目前为止介绍东西的基本上也是属于大路货性质,网上基本上都有。是的,这点我承认,不过写文章总得有个过渡是吧,一上来就把所有的货都倒出来也不见得就讨人喜欢。不过我保证,如果大家耐着性子看下去一定会有所收获的。

 

题外话点到为止,我们继续。在Spring中普遍使用的resourceLoader一般有三种,即classpath,file和url。在我们上面的那个例子中我们就使用了classpath resourceLoader。但是作为部署特有的配置不论使用这三种resourceLoader中的任何一种笔者都觉得不是很合适。显然,我们不想使用classpath resourceLoader,因为我们并不想将部署特有的配置打包到应用里,我们也不想使用file resourceLoader,因为在不同的部署环境中,properties文件的绝对路径并不一定相同,所以指定一个绝对路径并不是一个很好的做法。那使用url resourceLoader就更不靠谱了。既然这样,那我们还有没有更好的办法呢?首先,思路是这样的,一般而言,一个典型的部署过程是将开发人员在windows平台上开发好的应用打包然后部署到linux/Unix平台上(也有可能仍然是windows),但是不管是windows平台还是Linux或者Unix平台,都有user home目录。在windows平台上是C:\Documents and Settings\user目录下,Linux或者Unix则是在/home/user目录下,既然如此,那我们是否可以把properties文件放在user home目录下,把部署特有的配置从应用当中隔离出来,使得应用在部署的时候不需要做额外的配置工作。思路是这样的,那么具体如何做到呢?首先我们看一下下面这张类继承关系图,从图上我们可以看到PropertyPlaceholderConfigurer继承自顶层的PropertiesLoaderSupport

 

 

这个类里有一个setLocations方法,接受一个Resource类型的数组作为参数,对比上面PropertyPlaceholderConfigurer的bean配置我们就可以发现locations标签下值都会被解析成Resource,而这个resource本身则包含了访问这个resource的方法,在这里resource代表的则是properties文件。

	/**
	 * Set locations of properties files to be loaded.
	 * <p>Can point to classic properties files or to XML files
	 * that follow JDK 1.5's properties XML format.
	 * <p>Note: Properties defined in later files will override
	 * properties defined earlier files, in case of overlapping keys.
	 * Hence, make sure that the most specific files are the last
	 * ones in the given list of locations.
	 */
	public void setLocations(Resource[] locations) {
		this.locations = locations;
	}

 

而这个解析过程应该是由applicationContext完成的。研究一下代码发现,所有的applicationContext都间接的继承或实现了ResourceLoader这个接口,这个接口主要有一个getResource方法,而Resource本身也是一个接口。

 

Resource Loader接口

public interface ResourceLoader {

	Resource getResource(String location);

	ClassLoader getClassLoader();
}

 

Resource接口

public interface Resource extends InputStreamSource {

	boolean exists();

	boolean isReadable();

	boolean isOpen();

	URI getURI() throws IOException;

	File getFile() throws IOException;

	Resource createRelative(String relativePath) throws IOException;

	String getDescr*ption();
}

 

 
研究到这一步我们就受到一个启发,既然applicationContext通过解析后的Resource来访问这个properties文件的话,那我们是否可以通过写一个bean,这个bean实现Resource这个接口,并且由这个bean负责解析对这个properties文件访问呢?既然这个bean是一个Resource的话,那么它就可以被applicationContext装载。

 

 假设我们的properties文件名是projectName.properties,并且这个bean接受一个名称是projectName的属性,那我们的PropertyPlaceholderConfigurer可以这样配置

<beans default-autowire="byName">
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<bean class="com.meidusa.toolkit.common.spring.DefinedFileSystemResource">
					<property name="projectName">
						<value>demo</value>
					</property>
				</bean>
			</list>
		</property>
	</bean>
</beans>

 

 com.meidusa.toolkit.common.spring.DefinedFileSystemResource是这个bean的class。这个bean通过projectName的值来解析具体的properties文件的位置。

 

而projectName的值是应用特有的配置而不是部署特有的,通过由这个bean来解析具体的properties文件的位置,我们成功的实现了对部署特有的配置与应用特有的配置的分离。

 

因为这个类的定义很长,所以这里只放出部分代码,从下面的代码中可以看出,这个类继承了AbstractResource并实现了InitializingBean这个接口。AbstractResource是对Resource这个接口的默认实现,而InitializingBean则定义了这个类里最重要的一个方法,即afterPropertiesSet(),这个方法在projectName属性设置后被调用,正是由这个方法计算出对properties文件的访问路径。

 

public class DefinedFileSystemResource extends AbstractResource implements
		InitializingBean {
	private static Logger logger = Logger.getLogger(DefinedFileSystemResource.class);
	private String projectName;
	private File file;
	private String path;

	public DefinedFileSystemResource() {

	}

	public void afterPropertiesSet() throws Exception {
		try{
	
			this.path = System.getProperty("user.home");
			this.path = StringUtils.cleanPath(path);
			this.file = new File(path,projectName+".properties");
		}finally{
			logger.info("loading project config file from :"+file.getAbsolutePath());
		}
	}

}

 

按说到这里应该算是圆满了,但笔者还是不得不指出,使用这种方法还是有一个致命的不足之处,那就是对Spring的依赖。虽说Spring很好很强大,对几乎所有的主流框架都有一定的支持,但总有不支持的吧?特别是自己写的工具类库,比如在这个系列的下一篇我们会讨论到一个自己定制的Struts2的cookie拦截器,这个拦截器会用到一个自己定义的xml配置文件,这个配置文件里会放置一些部署特有的配置信息,遇到这种情况Spring就没辄了。那么,我们有没有一种更普遍适用的解决方案呢?

 

 答案是肯定的,这也涉及到我们这篇文章的主题,即自动化配置。在讨论这个新的方法之前,有必要先引入一下maven这个管理工具。如果诸位对maven不是很了解的话可以去Juven的博客浏览一番,在他的博客里有着对maven十分详尽的介绍,地址是http://juvenshun.iteye.com/  不过maven并不是我们今天的主题,在这里我主要想介绍一个maven的插件,maven-autoconfig-plugin。

 

在命令行界面下运行maven命令可以得到如下输出

 

mvn help:describe -Dplugin=autoconfig -Ddetail

 

Name: maven-autoconfig-plugin
Descr*ption: (no descr*ption available)
Group Id: com.meidusa.toolkit.plugin
Artifact Id: maven-autoconfig-plugin
Version: 1.0
Goal Prefix: autoconfig

This plugin has 1 goal:

autoconfig:config
  Descr*ption: (no descr*ption available)
  Deprecated. No reason given
  Implementation: com.meidusa.toolkit.plugin.autoconfig.AutoConfig
  Language: java

  Available parameters:

    charset
      (no descr*ption available)
      Deprecated. No reason given

    excludeDescr*ptors
      excludeDescr*ptors
      Deprecated. No reason given

    excludePackages
      excludePackages
      Deprecated. No reason given

    includeDescr*ptors
      includeDescr*ptors
      Deprecated. No reason given

    includePackages
      includePackages
      Deprecated. No reason given

    projectName
      projectName
      Deprecated. No reason given

 

从上面的输出中我们可以看到这个autoconfig插件有且只有一个goal,那就是config。另外支持的参数有charset,excludeDescr*ptors,excludePackages,includeDescr*ptors,includePackages,projectName。

 

一个典型的应用是在父项目的pom中将这个插件的config goal bind到initialize这个phase中,也就是说在maven构建的初始阶段即运行autoconfig这个插件,如果是第一次运行,它会根据includeDescr*ptors参数扫描所有的自动配置符文件,通过运行一个向导配置好user home目录下的projectName.properties属性文件。若projectName.properties属性文件已经存在并已配置好的时候,这个插件会根据projectName的值自动扫描这个文件并读取里面的值,同时includeDescr*ptors参数包含的自动配置描述符文件中若有<script>项,插件会根据读取的属性渲染定义在配置描述符里的模板生成真正的配置文件,即完成了配置的自动化。在子项目中则把这个插件的config goal bind到install phase中,这样在打包的时候就可以将自动配置好的配置文件打包进war中。

 

在父项目pom.xml中的配置片段如下

 

<plugin>
	<groupId>com.meidusa.toolkit.plugin</groupId>
	<artifactId>maven-autoconfig-plugin</artifactId>
	<configuration>
		<projectName>demo</projectName>
		<includeDescr*ptors>deploy/**/auto-config.xml</includeDescr*ptors>
	</configuration>
	<executions>
		<execution>
			<phase>initialize</phase>
			<goals>
				<goal>config</goal>
			</goals>
		</execution>
	</executions>
</plugin>
<plugin>

 

在子项目pom.xml中配置片段如下

 

<plugin>
	<groupId>com.meidusa.toolkit.plugin</groupId>
	<artifactId>maven-autoconfig-plugin</artifactId>
	<configuration>
		<projectName>demo</projectName>
		<includePackages>target/*.war</includePackages>
		<includeDescriptors>src/**/auto-config.xml</includeDescriptors>
	</configuration>
	<executions>
		<execution>
			<phase>install</phase>
			<goals>
				<goal>config</goal>
			</goals>
		</execution>
	</executions>
</plugin>

   

groupId和artifactId标识了这个插件的坐标,configuration标签则指定了运行这个插件时给的参数及其值,executions标签及其子标签则表明如何将这个插件绑定到使用maven构建的生命周期中。

对于maven的配置文件pom.xml及生命周期以及插件的goal等概念读者请自行查询相关资料,这里不再赘述。

 

 autoconfig的描述符本身也是一个xml配置文件,group定义了properties属性,script则定义了需要渲染的模板

 

<?xml version="1.0" encoding="utf-8"?>
<config>
    <group name="usercookie setting" description="用户cookie的配置">
    	<property name="cookie.encryptKey" defaultValue="ei*736TR" description="cookie加密的key,至少8位" />
    	<property name="cookie.loginUrl" defaultValue="http://demo.meidusa.com:8080" description="cookie失败后登录的地址" />
    	<property name="cookie.algorithm" defaultValue="DES" description="cookie加密的算法" />
    	<property name="cookie.domain" defaultValue="demo.meidusa.com" description="cookie domain" />
    </group>
    <script>
        <generate template="cookieMapping.xml" destfile="deploy/conf/cookieMapping.xml" charset="utf-8"/>
    </script>
</config>

 

 

 cookie配置文件的模板,可以看到需要被渲染替换的属性占位符。

 

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE cookieMapping SYSTEM "cookieMapping.dtd">
<cookieMapping
	cookieClass="com.meidusa.pirateweb.web.account.Cookie"
	encryptKey="${cookie_encryptKey}"
	loginUrl="${cookie_loginUrl}" algorithm="${cookie_algorithm}" maxLifeTime="1440" maxIdleTime="-1">
	
	<cookie cookieName="meidusa_cookie2" innerCookieName="loginId">
		<property name="age">1000</property>
		<property name="domain">${cookie_domain}</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>

	<cookie cookieName="meidusa_mask" innerCookieName="mask">
		<property name="age">1000</property>
		<property name="domain">${cookie_domain}</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>
</cookieMapping>

 

 

假设projectName是demo,在user home目录下demo.properties文件的内容可以是

 

cookie.algorithm   = DES
cookie.domain      = www.meidusa.com
cookie.encryptKey  = ei*736TR
cookie.loginUrl    = http://www.meidusa.com:8080/account/signin.html

 

 最终渲染出来的配置文件是

 

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE cookieMapping SYSTEM "cookieMapping.dtd">
<cookieMapping
	cookieClass="com.meidusa.pirateweb.web.account.Cookie"
	encryptKey="ei*736TR"
	loginUrl="http://www.meidusa.com:8080/account/signin.html" algorithm="DES" maxLifeTime="1440" maxIdleTime="-1">
	
	<cookie cookieName="meidusa_cookie2" innerCookieName="loginId">
		<property name="age">1000</property>
		<property name="domain">www.meidusa.com</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>

	<cookie cookieName="meidusa_mask" innerCookieName="mask">
		<property name="age">1000</property>
		<property name="domain">www.meidusa.com</property>
		<property name="writable">true</property>
		<property name="secure">true</property>
		<property name="path">/</property>
	</cookie>
</cookieMapping>

 

 这里我们先不管这个cookie配置文件是作什么用的(本系列下一篇会谈到),但是我们可以看到域名,登录的url等部署特有的被自动配置了。

 

 

  • 大小: 9.6 KB
分享到:
评论
6 楼 grandboy 2011-01-20  
个人认为现在完全可以用spring mvc 来代替struts2,反正都很少使用自己的标签,都是用JSTL, 不知道S2现在还有什么优势吗?
5 楼 kevindude 2011-01-18  
服务器ip更新了,需要的童鞋请站内短信我
4 楼 stuhack0303 2011-01-18  
demo呢?楼主无私奉献下吧。
先谢过了。
3 楼 jjjoken2004 2011-01-18  
连接不上


[WARNING] Unable to get resource 'org.apache.maven.plugins:maven-resources-plugi
n:pom:2.3' from repository central (http://122.224.223.123:8081/nexus/content/gr
oups/public): Error transferring file: Connection timed out: connect
2 楼 77tt77 2010-02-10  
好东西,先记下,慢慢看。

楼主在杭州吗?
1 楼 kevindude 2010-02-08  
<p>下载先撤了,大家还是看demo吧</p>

相关推荐

    struts2+spring+Ibatis框架包

    Struts2、Spring和iBatis是Java Web开发中三个非常重要的开源框架,它们共同构建了一个灵活、可扩展且高效的应用程序开发环境。这个“struts2+spring+iBatis框架包”集成了这三个框架,使得开发者能够快速构建基于...

    struts2+spring3+ibatis项目整合案例

    Struts2、Spring3和iBATIS是Java Web开发中常用的三大框架,它们各自负责不同的职责,协同工作可以构建出高效、松耦合的Web应用。在这个“struts2+spring3+ibatis项目整合案例”中,我们将深入探讨这三个框架如何...

    struts2+spring2+ibatis

    在这个案例中,开发者可能创建了一个简单的Web应用,包括了Struts2的Action类、Spring的Bean配置以及iBatis的数据访问对象(DAO)和SQL映射文件。 在实际的整合过程中,通常会首先配置Struts2的核心配置文件(struts...

    Struts2+Spring2+iBatis2整合的例子

    Struts2、Spring和iBatis是Java Web开发中三个非常重要的框架,它们分别负责表现层、业务层和数据访问层。将这三个框架整合在一起,可以实现MVC(Model-View-Controller)架构,提高应用的灵活性和可维护性。 **...

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis

    Struts2+Spring+Hibernate和Struts2+Spring+Ibatis是两种常见的Java Web应用程序集成框架,它们分别基于ORM框架Hibernate和轻量级数据访问框架Ibatis。这两种框架结合Spring,旨在提供一个强大的、可扩展的、易于...

    struts2+spring+ibatis+mysql

    "Struts2+Spring+Ibatis+MySQL" 是一个经典的Java Web开发框架组合,用于构建高效、可扩展的企业级应用程序。这个组合集成了强大的MVC(Model-View-Controller)框架Struts2、依赖注入与面向切面编程的Spring框架、...

    Struts2+Spring2+iBatis2+MySQL的完整示例

    开发环境说明 ...本示例完整地结合Struts2+Spring2+iBatis2+MySQL5,演示了一个用户表的增、删、改、查。 想完整学习Struts2+Spring+iBatis的同仁,可以在这个例子中学习或模仿最基本也是最核心的技术要点。

    Struts2+Spring2.5+Ibatis完整增删改查Demo(含全部jar包)

    Struts2、Spring和iBatis是Java Web开发中经典的三大框架,它们分别负责MVC模式中的Action层、业务逻辑层和服务数据访问层。这个"Struts2+Spring2.5+iBatis完整增删改查Demo"提供了一个完整的集成示例,包括所有必要...

    spring+struts2+ibatis整合的jar包

    当我们把Spring、Struts2和iBatis整合在一起时,可以构建出一个高效、模块化的Web应用。Spring作为整体的框架容器,负责管理所有的Bean,包括Struts2和iBatis的相关组件。Struts2处理HTTP请求,调用Spring管理的业务...

    Struts2+spring+ibatis三大框架整合实例

    Struts2、Spring和iBatis是Java Web开发中常用的三大框架,它们分别负责MVC模式中的Action层、业务逻辑层和服务数据访问层。本文将详细介绍这三个框架如何整合,以及在实际项目中如何运用。 首先,Struts2作为表现...

    struts2+spring2+ibatis集成

    Struts2、Spring2 和 iBatis 是三个在Java Web开发中广泛应用的开源框架,它们分别负责MVC架构中的控制层、服务层和数据访问层。这个集成项目,"SSITest",是为了帮助初学者理解和实践这三大框架的协同工作。 **...

    struts2+spring+ibatis的小demo

    Struts2、Spring和iBatis是Java Web开发中经典的三大框架,它们组合起来可以构建出高效、可维护的企业级应用程序。在这个“struts2+spring+ibatis”的小demo中,我们将深入探讨这三个框架的核心功能以及它们如何协同...

    struts2+spring+ibatis整合项目实例

    Struts2、Spring和iBatis是Java Web开发中常用的三个开源框架,它们各自负责不同的职责,协同工作可以构建出高效、松耦合的Web应用。这个整合项目实例旨在展示如何将这三个框架集成到一起,以实现更强大的功能。 1....

    struts2+spring+ibatis项目实例

    Struts2、Spring和iBatis是Java Web开发中经典的三大框架,它们组合起来可以构建出高效、可维护的企业级应用程序。在这个项目实例中,我们将深入探讨这三个框架如何协同工作,以及它们各自的核心功能。 首先,...

    struts2 + spring + ibatis 整合例子

    Struts2、Spring和iBatis是Java Web开发中常用的三大框架,它们分别负责MVC模式中的Action层、业务逻辑层以及数据访问层。将这三个框架整合在一起,可以构建出高效、灵活且易于维护的Web应用程序。下面我们将详细...

    struts2+spring2+ibatis简单登录例子

    Struts2、Spring2和iBatis是Java Web开发中常用的三大框架,它们结合使用可以构建高效、可扩展的企业级应用程序。在这个简单的登录例子中,我们将深入探讨这三个框架如何协同工作来实现用户身份验证。 首先,Struts...

    SSI2 Struts2+Spring2.5+IBatis2 配置

    Struts2、Spring2.5 和 iBatis2 是经典的Java Web开发框架组合,它们各自在应用程序的不同层面提供了强大的功能。下面将详细讲解这三大框架的集成配置以及log4j的相关知识。 首先,Struts2 是一个基于MVC(Model-...

    struts2+spring+ibatis+oracle+分页搜索+上传附件实例

    Struts2、Spring、iBatis以及Oracle是Java Web开发中的四大核心组件,它们共同构建了一个强大且灵活的后端架构。在这个实例中,我们将会深入探讨这些技术如何协同工作,实现分页搜索功能和上传附件操作。 1. **...

    Struts2+Spring2.5+Ibatis2.3架构

    Struts2+Spring2.5+Ibatis2.3架构是一种经典的Java Web开发技术栈,广泛应用于企业级应用系统中。这个架构结合了Struts2的MVC框架、Spring的依赖注入(DI)和面向切面编程(AOP)以及Ibatis的持久层解决方案,为...

    struts2 + spring2.5 + ibatis2.3.4整合包文件

    这个"SSI整合包文件"包含了所有必要的配置文件、库文件、示例代码等,帮助开发者快速搭建起一个基于Struts2、Spring和iBatis的Web应用。开发者只需要根据实际需求调整配置和代码,即可开始开发工作。这样的整合包极...

Global site tag (gtag.js) - Google Analytics