论坛首页 Java企业应用论坛

若干条J2EE应用中运用“配置”的最佳实践

浏览 12779 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-04-22  
本文所提到的所有内容的前提是使用一些开源框架搭建简单的J2EE应用时,对配置的运用方面的一些总结出来的最佳实践。

1. 尽最大的可能简化你的配置

这一点似乎是基本原则,没有人会愿意多写一行代码,配置也是代码,多一行配置,就意味着多一行的维护量。简化配置的主要途径大致有:
1) 尽可能减少配置文件的数量
2) 使用语义鲜明的Annotation来代替复杂的XML文件配置
3) 使用CoC来代替配置文件
4) 使用一些特殊的技巧来简化配置文件的内容

2. 分离关注点,让配置文件各尽其用

这一点似乎与第一点有所背离,不过事实上,分离关注点对于配置文件的可维护性是非常重要的一点。

举一个针对Spring+Hibernate的配置场景作为例子。通常我们需要一个Spring的配置文件(applicationContext.xml),来配置DataSource和SessionFactory,由于Spring本身提供了针对Hibernate的Global属性进行配置的选项,所以,其实我们可以通过如下的配置文件,对Spring+Hibernate完成配置:


<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="mappingResources">
        <list>
           <value>com/demo2do/demo/entity/User.hbm.xml</value>
           <value>com/demo2do/demo/entity/Order.hbm.xml</value>
           <value>com/demo2do/demo/entity/Admin.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
           <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
           <prop key="hibernate.show_sql">true</prop>
        </props>
     </property>
</bean>


在这里,我们发现,一个SessionFactory的配置实在太长了,一旦我们需要对其中的某些配置进行改动,就需要用肉眼去观察我们所需要修改的配置片段。在项目开发过程中,我们会发现,这个文件的这个配置片段修改频度会非常高,因为在一个团队中,每个人都可能需要增加一个持久化类,或者对hibernate进行一些全局化的配置修改。结果,这段配置可能会在版本管理上造成merge的混乱。

所以,我们可以在这个基础上对这段配置进行重构,重构的原则就在于把Hibernate的配置和Spring的配置进行关注点分离。我们选择hibernate.properties对Hibernate的一些Global的选项进行指定。同时使用指定持久化类hbm配置文件路径的方式,批量定义持久化类。重构后的配置文件变成了2个:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="mappingDirectoryLocations">
            <list>
				<value>classpath*:persist/system</value>
				<value>classpath*:persist/role</value>
				<value>classpath*:persist/activity</value>
				<value>classpath*:persist/extension</value>
				<value>classpath*:persist/user</value>
            </list>
        </property>
</bean>


######################
### Query Language ###
######################

## define query language constants / function names
hibernate.query.substitutions true 1, false 0, yes 'Y', no 'N'

#################
### Platforms ###
#################

## MySQL

hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect
hibernate.connection.url jdbc:mysql://127.0.0.1:3306/test
hibernate.connection.username root
hibernate.connection.password root

## Oracle

#hibernate.dialect org.hibernate.dialect.OracleDialect

....


此时,我们可以发现,程序员可以独立工作,不需要为增加持久化类修改公共配置而烦恼,hibernate.properties也更加清晰的反映hibernate相关的配置。

这还不够,因为在项目中,我们往往可能在不同的DataSource的实现上切换。多数情况下,我们会使用类似C3P0这样的数据连接池,当然,也可能会通过JNDI来指定我们的DataSource。所以,我们在这里很有必要对JDBC连接相关的关注点再一次进行分离。引入一个jdbc.properties的文件指定JDBC相关的链接信息,并在Spring配置文件中导入这些配置:

<!-- A Local dataSource Definition using c3p0 connection pool -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"	destroy-method="close">
		<property name="driverClass" value="${connection.driver_class}"/>
		<property name="jdbcUrl" value="${jdbc.connection.url}"/>
		<property name="idleConnectionTestPeriod" value="${jdbc.pool.c3p0.idle_connection_test_period}" />
		<property name="preferredTestQuery" value="${jdbc.pool.c3p0.preferred_test_query}" />
		<property name="maxIdleTime" value="${jdbc.pool.c3p0.max_idle_time}" />
		<property name="properties">
			<props>
				<prop key="user">${jdbc.connection.username}</prop>
				<prop key="password">${jdbc.connection.password}</prop>
				<prop key="c3p0.acquire_increment">${jdbc.pool.c3p0.acquire_increment}</prop>
				<prop key="c3p0.max_size">${jdbc.pool.c3p0.max_size}</prop>
				<prop key="c3p0.min_size">${jdbc.pool.c3p0.min_size}</prop>
			</props>
		</property>
	</bean>
	
	<!-- Hibernate SessionFactory definition using exposed dataSource -->
	<!-- hibernate.properties and hibernate.cfg.xml will be loaded on startup -->
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="mappingDirectoryLocations">
            <list>
				<value>classpath*:persist/system</value>
				<value>classpath*:persist/role</value>
				<value>classpath*:persist/activity</value>
				<value>classpath*:persist/extension</value>
				<value>classpath*:persist/user</value>
            </list>
        </property>
	</bean>


connection.driver_class=com.mysql.jdbc.Driver

jdbc.connection.url=jdbc:mysql://192.168.1.251:3307/test
jdbc.connection.username=root
jdbc.connection.password=root

jdbc.pool.c3p0.acquire_increment=2
jdbc.pool.c3p0.max_size=20
jdbc.pool.c3p0.min_size=2
jdbc.pool.c3p0.preferred_test_query='SELECT 1'
jdbc.pool.c3p0.idle_connection_test_period=18000
jdbc.pool.c3p0.max_idle_time=25000


这样,一段配置变成了3个文件。但是通过重构,我们可以轻松在本地实现数据库切换(修改jdbc.properties),数据连接池切换(修改jdbc.properties和applicationContext.xml),hibernate的Global配置切换等功能。这些功能会提供你更加灵活的调试方式。

3. 整理你的配置文件,不要让它们分散到各处

这一点作为一个最佳实践提出来,主要是因为在项目中,我们可能使用到的技术框架和配置文件是很难预期的。而默认情况下,这些配置文件所存放的位置也不同,比如Spring,往往会把applicationContext.xml文件放在WEB-INF/目录下,而hibernate和struts默认是放在classpath下的。这就为管理配置文件带来了不便。所以,一个比较好的做法,是把我们的配置文件都放在一起。通过存放在不同的目录结构进行管理。例如,把所有的配置文件都放到classpath下。

如果你用maven进行项目管理,maven会为你创建专门存放配置文件的目录(通常是一个叫做resource的目录)。如果不使用maven,我们也可以做类似的工作。以我个人的习惯为例,我喜欢在项目中建立一个叫做conf的source folder来存放所有的配置文件。由于conf是一个source folder,所以它会被类似eclipse这样的IDE自动编译到classpath下,那么我们就可以在这个目录下创建一些package,例如context的package专门存放Spring相关的配置文件,persistent专门存放ORM相关的配置文件,web专门存放表示层的配置文件。而其他的一些配置文件,则直接放到conf根目录下。

一个典型的目录结构如下:



如果你用这种方式来管理你的配置文件,有一个极大的好处就在于,不必再担心你团队的成员为找不到配置文件而发愁,他们被集中存放,集中管理了。

当然,任何事情不能做得过于偏激,有时候,我们需要package level的配置文件,例如struts的validation,类型转化定义,i18n配置文件,等等。这些package level的配置文件,我们完全没有必要把他们集中到一起,因为他们在各自的package中承担着各自的作用。实际上,如果把他们集中到一起,反而会造成这样那样的问题,这些问题将会在第五点最佳实践中有所涉及。

4. 选择合理的配置类型,XML or Annotation or Properties?

之前已经谈到了尽可能简化配置,其中的重要途径是使用Annotation来代替XML进行配置。在这里我想提出的是,XML和Annotation甚至是Properties文件,他们都各自有各自的特点,我们可以根据实际情况,选择最合适的方式进行配置。

比如说在Spring2.5中,XML配置可以被用作全局的,公共的配置,这些配置包括:DataSource定义、SessionFactory定义、事务定义等等。而Annotation可以被用作Bean定义,而无需在XML文件中一一指定。此时,两者结合将成为一个比较好的选择。

再比如Hibernate的全局配置,可以使用hibernate.properties,也可以使用hibernate.cfg.xml。但是properties文件无法指定持久化类,不过properties文件在定义全局配置时,显然比xml的语义性更强,也更容易编辑(你只要把Hibernate发行包中的模板properties复制一份过来改一下就行了)

而hibernate的持久化类的配置,Annotation和XML的比较上,Annotation似乎更占上风。虽然个人不喜欢在Domain Object上加过多的Annotation,不过不得不说,在降低维护成本上,Annotation占有了绝对的优势。可惜,hibernate的Annotation最大的缺点,就在于它的Annotation所定义的属性,与XML定义的属性是互相不兼容的,因而带来了一定的学习成本。

5. 在团队工作中,尽可能不要把团队操作度很高的配置放到一个文件中

这条最佳实践其实应该成为一条很重要的最佳实践。因为merge代码或者merge配置文件而给程序员带来痛苦的情况是数不胜数的。因此,解决这个问题的最佳方案就是尽量把团队操作度很高的配置文件拆分开。

在项目中,那些配置的团队操作度很高呢?我大致考虑了以下一些情况:
1) 持久化类的配置在SessionFactory中的定义
2) web层的配置文件(struts-config.xml)等
3) 涉及到i18n的资源类文件
4) Spring中的Bean定义

第一条,如果使用XML进行持久化类的配置,那么可以通过指定路径来解决这个问题。如果你使用的是Annotation,那么很不幸,当前还没有可以通过指定带有Annotation的持久化类的package的方式来简化配置。当然,要自己实现一个似乎也并不困难,有兴趣的朋友可以自己尝试一下。

第二条,在Struts2中,已经提供了一些0配置的方案,通过使用这些方案,大家可以最大程度上降低配置的公用性。同时Struts2也支持将XML文件分开定义,每个程序员可以工作在自己那个模块所在的XML文件上。

第三条,之前很多的做法,是在classpath下定义一个统一的资源文件,所有的资源信息都写在一起,这会造成操作冲突的几率很大。比较合适的做法,就是在Action的package level定义与这个Action相关的一些资源,这也是Struts比较推荐的做法。

第四条,如果你使用Spring2.5,可以使用Annotation来代替Bean定义。

6. 适度使用Annotation和CoC

这条是值得讨论的。有关XML和Annotation的是是非非,在其他的帖子中经常有讨论。我的观点在于,对于公用的全局的配置,使用XML,而单独的Bean相关的配置,使用Annotation。对于Domain Object的配置,个人倾向使用XML配置进行关注点分离。

谈到CoC,这是一个懒人的时代,RoR中的一些约定大于配置的观念也开始深入人心。不过在Java世界,这一点似乎还没有完全被推开。甚至有人问我,这家伙写的东西,怎么连个配置文件都没有,我哪里知道谁对应谁呢。所以CoC,我想也有个度,甚至有时候,需要一定的Reference Doc进行说明,否则,过度的CoC,也会给许多人带来困惑。
  • 大小: 16 KB
   发表时间:2008-04-29  
能否把你的配置文件作为附件提供下载,以供学习
0 请登录后投票
   发表时间:2008-04-29  
不同的项目可能有不同的配置文件,我可以上传一个我之前做的一个项目所用到的配置文件。

这些配置文件都在classpath下

0 请登录后投票
   发表时间:2008-04-29  
还有就是同一配置管理。。。
比如很多组件都想要读一些配置文件,那么如果使用统一的配置管理组件,就可以统一配置,定义统一接口,以后升级也有很大余地
0 请登录后投票
   发表时间:2008-04-29  
这样分类存放后感觉好多了,你说的数据库切换实在运行是切换还是测试的时候换个数据库呢,比如帐套这样的需求,换个帐套可能就是换个数据库
0 请登录后投票
   发表时间:2008-04-30  
我的spring-hibernate 配置应该比你简单


	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:/config.properties</value>				
			</list>
		</property>
	</bean>
		
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="${project.datasource.driverClassName}"/>
		<property name="url" value="${project.datasource.dburl}"/>
		<property name="username" value="${project.datasource.dbuserName}"/>
		<property name="password" value="${project.datasource.dbpassword}"/>
		<property name="maxActive" value="${project.datasource.dbmaxActive}"/>
		<property name="maxIdle" value="${project.datasource.dbmaxIdle}"/>
		<property name="maxWait" value="${project.datasource.dbmaxWait}"/>
	</bean>
	
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<!-- property name="namingStrategy">
		<bean class="org.hibernate.cfg.ImprovedNamingStrategy" />
		</property-->
		<property name="mappingDirectoryLocations">
			<list>				
				<value>classpath:/com/rocky/project/model</value>				
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">true</prop>
			</props>
		</property>
	</bean>

0 请登录后投票
   发表时间:2008-04-30  
laiseeme 写道
这样分类存放后感觉好多了,你说的数据库切换实在运行是切换还是测试的时候换个数据库呢,比如帐套这样的需求,换个帐套可能就是换个数据库


我说的数据库切换是指,如果你使用类似像Hibernate这样的ORM产品,那么你只需要修改你的jdbc.properties就可以了。甚至你有可能会切换你的数据源种类,你可以轻松切换到JNDI数据源。



0 请登录后投票
   发表时间:2008-04-30  
1998a 写道
还有就是同一配置管理。。。
比如很多组件都想要读一些配置文件,那么如果使用统一的配置管理组件,就可以统一配置,定义统一接口,以后升级也有很大余地


事实上我上传的配置文件中,有一个叫做constant.properties,就是完成了你所说的这个功能。同时,我自己写了一个Spring的FactoryBean对这个properties文件注入到一个统一的Bean中间,这样就可以在任何的层次使用这个Bean并对配置文件中的内容进行读取。
0 请登录后投票
   发表时间:2008-04-30  
jianfeng008cn 写道
我的spring-hibernate 配置应该比你简单


	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:/config.properties</value>				
			</list>
		</property>
	</bean>
		
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="${project.datasource.driverClassName}"/>
		<property name="url" value="${project.datasource.dburl}"/>
		<property name="username" value="${project.datasource.dbuserName}"/>
		<property name="password" value="${project.datasource.dbpassword}"/>
		<property name="maxActive" value="${project.datasource.dbmaxActive}"/>
		<property name="maxIdle" value="${project.datasource.dbmaxIdle}"/>
		<property name="maxWait" value="${project.datasource.dbmaxWait}"/>
	</bean>
	
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<!-- property name="namingStrategy">
		<bean class="org.hibernate.cfg.ImprovedNamingStrategy" />
		</property-->
		<property name="mappingDirectoryLocations">
			<list>				
				<value>classpath:/com/rocky/project/model</value>				
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">true</prop>
			</props>
		</property>
	</bean>



我已经说过了,这并不是比谁的配置文件简单,而是如何进行配置,使得你的配置文件更加合理,更加清晰。

你的配置文件没问题,只是我认为把hibernate.properties拆分到另一个文件更清晰。
0 请登录后投票
   发表时间:2008-04-30  
我的意思是 mappingresource 完全可以不配置 给个路径就能识别了 你上面不是有一大串嘛 理念大家都有 问题是要有好的实践经验分享,不可能出来个啥东西你就把几百种配置都自己玩个遍吧
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics