论坛首页 Java企业应用论坛

Spring3注释装配的最佳实践

浏览 14663 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-01-31   最后修改:2011-01-31

2005夏于上海,初次使用了Spring框架开发企业应用,当时还没有中文书籍,只能看Spring官方提供的Reference,甚是简陋,直到一年后人民邮电出版了第一本关于Spring技术的中文翻译书籍《Spring in action》,解决了广大人民群众的精神需求,也因此书让我认识了Manning出版社,之后一直在关注他的Action系列图书,此前将众多书籍封面整理成册,闲时品茶拿来翻阅不亦乐乎,有兴趣的同学可以雅俗共赏《Manning出版社In Action系列图书 》。此篇且谈Spring注释配置之实践。

 

关键词: Spring, Annotation, iBatis, 依赖注入(IOC), BeanNameGenerator, Inner Class, 后依赖注入

 

引言:

 

长久以来国内的众多应用都在使用Spring框架,它为我们带来的好处不言而喻。但问题是Spring2.0以下版本尚未支持注释装配,而企业应用大多分作MVC三层结构,每层Bean的配置渐渐膨胀,直到打开了XML文件,IDE不堪重负崩溃为止,情形实为惊人。后有了Convention over Configuration的软件设计范式,即“约定优于配置”,也作“约定编程”。Ruby and Rails和EJB3也都按此实现,Spring注释也基于此。

 

首先,在解答为什么要使用注释装配之前,先看看没有它时配置文件臃肿的样子,如:持久层DAO的Spring配置文件

 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans ‘略去声明’>
			
	<bean id="scDbInfoDAO" class="com.data.switching.db.dao.impl.ScDbInfoDAOImpl"
		parent="sqlMapClientDAO" />
						
	<bean id="scFtpInfoDAO" class="com.data.switching.db.dao.impl.ScFtpInfoDAOImpl"
		parent="sqlMapClientDAO" />
				
	<bean id="scParmInfoDAO" class="com.data.switching.db.dao.impl.ScParmInfoDAOImpl"
		parent="sqlMapClientDAO" />
				
	<bean id="scParmTypeDAO" class="com.data.switching.db.dao.impl.ScParmTypeDAOImpl"
		parent="sqlMapClientDAO" />
				
	<bean id="scRoleDAO" class="com.data.switching.db.dao.impl.ScRoleDAOImpl"
		parent="sqlMapClientDAO" />	
			
	<bean id="scRoleMenuDAO" class="com.data.switching.db.dao.impl.ScRoleMenuDAOImpl"
		parent="sqlMapClientDAO" />	
			
	<bean id="scSiteLoadDAO" class="com.data.switching.db.dao.impl.ScSiteLoadDAOImpl"
		parent="sqlMapClientDAO" />	
			
	<bean id="scSiteStatDAO" class="com.data.switching.db.dao.impl.ScSiteStatDAOImpl"
		parent="sqlMapClientDAO" />	

略去同样999个配置 ... ... 

 

使用后的情况:

 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	
<context:annotation-config />

<context:component-scan base-package="com.longtop.data.switching.db.dao"
name-generator="com.seraph.bi.suite.support.core.generator.IBatisDaoBeanNameGenerator" />				
		
</beans>
 

现在大家想必都了解到为什么使用注释配置,两者之间后者很优雅,而这全在于约定优于配置。

 

解决方案:

 

改造过程是,首先在DAO的实现类中加入@Repository标签,说明这是持久层的服务。另外两层的标签@Service, @Controller,实现类如下:

 

 

import org.springframework.stereotype.Repository;
...

@Repository
public class ScDbInfoDAOImpl extends SqlMapClientDaoSupport implements ScDbInfoDAO {
...
 

在配置文件中加入:

 

 

<context:annotation-config />

<context:component-scan base-package="com.longtop.data.switching.db.dao"
name-generator="com.seraph.bi.suite.support.core.generator.IBatisDaoBeanNameGenerator" />

 

因接口名为ScDbInfoDAO,而实现类名为 ScDbInfoDAOImpl,引用类中的域名是接口的首字母小写名scDbInfoDAO ,而容器生成的默认类名是 scDbInfoDAOImpl,所以不行,但spring预留了

接口BeanNameGenerator,只要实现它我们就可以自己指定生成bean的名字,这里的实现类如下:

 

/**
 * 类说明: 生成iBatis的DAO的Spring注册名,规则是首字母小写,并去掉后缀名<br>
 * 创建时间: 2011-1-26 下午12:44:20<br>
 * 
 * @author seraph<br>
 * @email: seraph115@gmail.com<br>
 */
public class IBatisDaoBeanNameGenerator implements BeanNameGenerator {

	private static final Logger logger = Logger
			.getLogger(IBatisDaoBeanNameGenerator.class);

	private static final String DAO_IMPLEMENTS_SUFFIX = "Impl";

	public String generateBeanName(BeanDefinition paramBeanDefinition,
			BeanDefinitionRegistry paramBeanDefinitionRegistry) {
		String[] strs = paramBeanDefinition.getBeanClassName().split("\\.");
		String shortName = strs[strs.length - 1];
		shortName = StringUtils.uncapitalize(shortName);
		shortName = shortName.replace(DAO_IMPLEMENTS_SUFFIX, "");

		logger.debug("Generated a ibatis DAO bean's name: [" + shortName + "]");

		return shortName;
	}

}
 

到这里我们可以自由的指定注释类的bean名称,但对于为DAO提供dataSource和sqlMapClient的Inner Class,即parent="sqlMapClientDAO"要如何处理呢?

 

<bean id="scRoleDAO" class="com.data.switching.db.dao.impl.ScRoleDAOImpl"
		parent="sqlMapClientDAO" />

 

<bean id="sqlMapClientDAO"
	class="org.springframework.orm.ibatis.support.SqlMapClientDaoSupport"
	abstract="true">
	<property name="dataSource" ref="${jdbc.dataSource}" />
	<property name="sqlMapClient" ref="sqlMapClient" />
</bean>

<bean id="sqlMapClient" class="com.seraph.bi.suite.support.core.IncludesSqlMapClientFactoryBean">
	<property name="configLocation" value="classpath:ibatis/platform/orcl/sqlmap.xml" />
</bean>

 

为了解决此问题,我们实现了一个后置注入的类:SqlMapClientDaoInjector用来在DAO加载到context中后注入其依赖。类代码如下:

 

/**
 * 类说明: 向iBatis的DAO中注入依赖<br>
 * 创建时间: 2011-1-26 上午10:51:28<br>
 * 
 * @author seraph<br>
 * @email: seraph115@gmail.com<br>
 */
public class SqlMapClientDaoInjector implements ApplicationContextAware, InitializingBean {

	private static final Logger logger = Logger.getLogger(SqlMapClientDaoInjector.class);
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		SpringContext.setApplicationContext(applicationContext);
	}

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(dataSource, "Property 'dataSource' is required.");
		Assert.notNull(sqlMapClient, "Property 'sqlMapClient' is required.");
		injectDependence();
	}
	
	private void injectDependence() {
                // 获取Context上下文
		ApplicationContext ctx = SpringContext.getApplicationContext();
                // 按类型获取上下文中的对象
                Map<String, SqlMapClientDaoSupport> map = ctx.getBeansOfType(org.springframework.orm.ibatis.support.SqlMapClientDaoSupport.class, true, true);
		for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
			try {
				String supportName = (String) i.next();
				SqlMapClientDaoSupport support = map.get(supportName);
				// 后注入依赖
				support.setSqlMapClient(sqlMapClient);
				support.setDataSource(dataSource);
			} catch (RuntimeException e) {
				logger.error("SqlMapClientDaoInjector.injectDependence()", e); 
  			}
		}
	}
	
	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public void setSqlMapClient(SqlMapClient sqlMapClient) {
		this.sqlMapClient = sqlMapClient;
	}
	
	private DataSource dataSource;

	private SqlMapClient sqlMapClient;

}

 然后加入此类的配置即可,

	<bean id="sqlMapClientDaoInjector"
		class="com.seraph.bi.suite.support.dao.assembly.SqlMapClientDaoInjector">
		<property name="dataSource" ref="${jdbc.dataSource}" />
		<property name="sqlMapClient" ref="sqlMapClient" />
	</bean>

 

至此我们完成了Spring注释配置的改造。

 

总结下实现思路,首先是在需要自动加载的类上加入@Repository注释标签,对于需要改变默认类名生成规则的约定,编写实现BeanNameGenerator接口的类,然后对于需要抽象的内置类的配置,自实现后依赖注入的实现。针对此例我们要体会实现的思路,即了解Spring容器的工作原理和设计思想,而后我们可以对其实现有益且有必要的改进工作,但最终都是旨在简化配置,较少没有必要的工作量。

 

人的懒惰,推进了科技的发展。新年好,祝工作学习愉快!2011年Beijing

   发表时间:2011-01-31  
<context:component-scan base-package="com.longtop" >
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" /> 
	</context:component-scan>
0 请登录后投票
   发表时间:2011-02-01  
Spring还是用配置文件配置比较好,臃肿的话可以拆分为多个配置文件,不太建议使用Annotation进行注入
0 请登录后投票
   发表时间:2011-02-01  
spring该用注释的还是用注释
不然就像楼主说的
注入dao层 services层多的吓死你
注解只需要2行就解决问题了
0 请登录后投票
   发表时间:2011-02-01  
evanzzy 写道
Spring还是用配置文件配置比较好,臃肿的话可以拆分为多个配置文件,不太建议使用Annotation进行注入


是可以用import的方式拆分,不过也只是解决了大XML文件编辑的问题,但目的并不在于此,而在于去除配置的不便及开发过程的繁琐问题,开发一个MVC三层的应用,要配置三层的类,而类文件本身已说明了自己的用途,何必要在配置文件中配置,还有很多同学的IDE没有类似SpringIDE的插件,这样就更麻烦了,因为你需要自己确认包名类名,不知大家对此可有体会?
0 请登录后投票
   发表时间:2011-02-01  
我们公司的配置文件用人名命名,出错了马上知道找谁......Annotation怎么实现?
0 请登录后投票
   发表时间:2011-02-01  
gtssgtss 写道
我们公司的配置文件用人名命名,出错了马上知道找谁......Annotation怎么实现?


-__________-# 离职了、换人维护了,要改名不?
0 请登录后投票
   发表时间:2011-02-02  
gtssgtss 写道
我们公司的配置文件用人名命名,出错了马上知道找谁......Annotation怎么实现?

囧,这个太有特色了,难道不测试就提交?有多大可能在配置问题上出错?
即使出错了难道没有svn,hg什么的查历史?
再说起码的实践,版本管理+CI应该有的

最后,用人名这个,我觉得简直是个joke了,太欢乐了
0 请登录后投票
   发表时间:2011-02-02  
gtssgtss 写道
我们公司的配置文件用人名命名,出错了马上知道找谁......Annotation怎么实现?


对于你们公司的这种特殊需求,对于使用Annotation并无影响,给出我的解决方案,首先你们的配置文件以员工名命名,这个可以使用javadoc,如:
/** 
 * 类说明: 需求A<br> 
 * 创建时间: 2011-1-26 上午10:51:28<br> 
 *  
 * @author 员工007<br> 
 * @email: <br> 
 */ 


签上大名后就可以搜索了,同样也可以跟踪

0 请登录后投票
   发表时间:2011-02-02  
flashing 写道
gtssgtss 写道
我们公司的配置文件用人名命名,出错了马上知道找谁......Annotation怎么实现?

囧,这个太有特色了,难道不测试就提交?有多大可能在配置问题上出错?
即使出错了难道没有svn,hg什么的查历史?
再说起码的实践,版本管理+CI应该有的

最后,用人名这个,我觉得简直是个joke了,太欢乐了


哈哈,的确非常囧,小作坊,必须的

我猜这么做是为了最大限度节省每日集成的时间吧,领导要求这样,我也不多嘴问
0 请登录后投票
论坛首页 Java企业应用版

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