该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2008-04-09
*作者:张荣华(ahuaxuan) *日期:2008-4-9 **/ 1背景 Spring2.5支持使用annotation来配置我们的service,比如如下代码: @Service("userService") public class UserServiceImpl extends BaseServiceSupport implements UserService { public void xxx() { } } 这样就表示这个service需要被spring管理,不过只是这样做是不够的,我们还需要在applicationcontext***.xml中加入这么一段: <context:component-scan base-package="xxxxxxx"/> 这么一来这个xxxxxxx包下所有的使用@Service这个注释的对象都会自动的被spring管理。 虽然这样看上去很美好,但是却是不满足我们的需求的,因为我们的service中,或者其他被管理的bean中有时候需要一些配置,比如说String,Integer等等,而且这些配置的值一般都来自Properties文件,一般情况下我们会使用如下这段代码: <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> 这样我们就可以通过${}来引用到properties文件中的值。 不过使用了@service之后,我们就无法通过${}来得到properties中的值了。downpour是spring2.5使用的先行者,他很早就意识到这个问题,通过我们的讨论,确定了解决问题的方向。下面我把这个方案拿出来和大家共享。 2目标: 我们的目标是实现一个Annotation,代码如下: @Service public class ImageFileUpload implements Serializable { @Properties(name="pic.address" ) private String picAddress; @Properties(name="pic.url" ) private String picUrl; private String picServerUrl; } pic.address和pic.url是properties文件中的两个属性 以上代码中的@Properties就是我们要实现的Annotation,通过name的值作为key去对应的properties中寻找对应的value,并且主动赋值给ImageFileUpload的对应属性。 3步骤: 我们知道,spring在初始化完bean之后我们可以对这些bean进行一定的操作,这里就是一个扩展点,我决定使用BeanPostProcessor这个接口,这个接口中有一个postProcessAfterInitialization方法就是用来做bean的后处理的,一旦一个bean被初始化完成之后,我们就可以对这个bean进行赋值了。 但是考虑到我们项目中不是所有的bean都使用Annotation来注册到spring中的,这些普通的,配置在xml文件中的bean也有用到${}的需求,所以我考虑扩展PropertyPlaceholderConfigurer这个类。我们来分析一下具体的代码。 首先建立一个Annotation,如下: /** * @author ahuaxuan(aaron zhang) * @since 2008-4-7 * @version $Id: Properties.java 261 2008-04-07 07:03:41Z aaron $ */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Properties { // String bundle(); String name(); } 接着我们实现我们的扩展主类: /** * @author ahuaxuan(aaron zhang) * @since 2008-4-7 * @version $Id: AnnotationBeanPostProcessor.java 260 2008-04-07 07:03:35Z aaron $ */ public class AnnotationBeanPostProcessor extends PropertyPlaceholderConfigurer implements BeanPostProcessor, InitializingBean { private static transient Log logger = LogFactory.getLog(AnnotationBeanPostProcessor.class); private java.util.Properties pros; @SuppressWarnings("unchecked") private Class[] enableClassList = {String.class}; @SuppressWarnings("unchecked") public void setEnableClassList(Class[] enableClassList) { this.enableClassList = enableClassList; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Field [] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); sb.append(" ========= ") .append(field.getType()) .append(" ============ ") .append(field.getName()) .append(" ============ ") .append(field.isAnnotationPresent(Properties.class)); logger.debug(sb.toString()); } if (field.isAnnotationPresent(Properties.class)) { if (filterType(field.getType().toString())) { Properties p = field.getAnnotation(Properties.class); try { // StringBuilder sb = new StringBuilder(); // sb.append("set").append(StringUtils.upperCase(field.getName().substring(0, 1))) // .append(field.getName().substring(1, field.getName().length())); // // Method method = bean.getClass().getMethod(sb.toString(), String.class); // method.invoke(bean, pros.getProperty(p.name())); 本来我是通过set方法来把properties文件中的值注入到对应的属性上去的,后来downpour提供了更好的方案,就是下面这两行代码,虽然这样做破坏了private的功能,同时破坏了封装,但是确实节省了很多代码,建议大家在业务代码中不要这样做,如果做框架代码可以考虑一下。 ReflectionUtils.makeAccessible(field); field.set(bean, pros.getProperty(p.name())); } catch (Exception e) { logger.error(" --- ", e); } } } } return bean; } @SuppressWarnings("unchecked") private boolean filterType(String type) { if (type != null) { for (Class c : enableClassList) { if (c.toString().equals(type)) { return true; } } return false; } else { return true; } } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } public void afterPropertiesSet() throws Exception { pros = mergeProperties(); } } 最后我们需要在xml文件中配置一下: <bean id="propertyConfigurer" class="xx.service.AnnotationBeanPostProcessor"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> 这样任何一个bean,不管它是使用annotation注册的,还是直接配置在xml文件中的都可以使用这种方式来注入properties中的值。 下面看一下我在项目中的一个真实的例子,这个类是一个value object,它代表一组配置: @Component public class Config implements Serializable{ /** */ private static final long serialVersionUID = 8737228049639915113L; @Properties(name = " online.pay.accounts") private String accounts; @Properties(name = " online.pay.user") private String user; @Properties(name = " online.pay.password") private String password; @Properties(name = " online.transurl") private String transUrl; @Properties(name = " online.refundurl") private String refundUrl; @Properties(name = " online.query") private String queryUrl; ```setter and getter method } 那么在需要用到该vo的地方比如: @Service(“userService”) public class UserServiceImpl implements UserService { @autowired private Config config; public void setConfig(Config config) { This.config = config; } } 就这么多内容就ok了,如果按照原来的办法,我们就需要在xml配置以上两个bean,然后在里面写一堆又一堆的${},肯定能让你看了之后崩溃,至少我差点崩溃,因为它看上去实在是太丑陋了。而现在,我的心情好多了,因为我用这个@Properties(name = "")用的很爽,呵呵,而且即使有些bean是配置在xml文件中的,比如datasource等等,我们还是可以通过${}来进行设值,也就是说这个方案既支持annotation,也支持${},很好,很强大。 结语: 很显然,在spring2.5的时代,以上这个需求是非常平常的,我相信在spring3.0中一定会提供这样的功能,而且我觉得spring2.5应该是一个过渡版本,虽然上面的方案中代码行数并不多,但是我觉得很有价值,应该很有市场才对,也许我们可以把这个东东叫做spring-properties2object-plugin。 题外话: 说点题外话吧,目前在我的项目里,我使用了struts2.0+spring2.5+hibernate3.2,使用struts2.0的时候我使用struts2.0的zero configuration和codebehind,基本上实现了真正意义零配置,剩下的都是一些common的配置,而且很少,不超过150行。在使用spring的时候,我也基本上是使用annotation来注册我的bean,同时使用上面的方案来作为补充,所以applicationContext-xxx.xml中的也是一些common的配置,也是非常少,应该只有200行左右。而hibernate我是使用annotation来配置我的PO,基本没有配置文件。所以整个项目的xml文件中配置的总行数大大下降。 [/size] 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-04-09
无意中发现,我解决了这个帖子所提出来的问题:http://www.iteye.com/topic/154121
|
|
返回顶楼 | |
发表时间:2008-04-10
本来我和楼主一样,所参与的项目也是struts2.0+spring2.5+hibernate3.2的,也是追求尽量少的xml的。可以进展到一定程度,发现很多问题,。现在除action中注入service保留了annotation,其他地方又改回为xml的配置了。并且对xml进行了教细粒度的划分。一样看着很舒服。。。我对annotation的一些困惑在我的blog里也有提到,如果你有好的建议,我很乐意学习一下。http://bulargy.iteye.com/blog/179781
|
|
返回顶楼 | |
发表时间:2008-04-10
自从有了Ioc和依赖注入以后,大家开始变得慵懒啦,暴力了!!
想没有Spring的时代,也许为解决这些事情,需要更多的设计,比较累,也比较有趣啊! 万能且万恶的Spring啊~~~:) |
|
返回顶楼 | |
发表时间:2008-04-10
bulargy 写道 本来我和楼主一样,所参与的项目也是struts2.0+spring2.5+hibernate3.2的,也是追求尽量少的xml的。可以进展到一定程度,发现很多问题,。现在除action中注入service保留了annotation,其他地方又改回为xml的配置了。并且对xml进行了教细粒度的划分。一样看着很舒服。。。我对annotation的一些困惑在我的blog里也有提到,如果你有好的建议,我很乐意学习一下。http://bulargy.iteye.com/blog/179781
xml和annotation各有各的用处,有些地方不该用annotation就不要用,比如说事务,不要为了annotation而annotation,其实这个的观点在这个帖子里都有明确的阐述。 http://www.iteye.com/topic/178725 |
|
返回顶楼 | |
发表时间:2008-04-10
一直使用xml配置,前段时间对spring2.5的annotation做了点研究,在一个私人项目(ext2.0 + spring mvc + db4o)里面尝试使用annotation来完成整个项目,结果发现很多不如意的地方,感觉spring2.5的annotation还很不完善。
就目前spring 2.5的annotation,不敢在正式的项目中使用。所以还在沿用xml的配置。 希望spring 3.0可以完善它的annotation,可以使得一个项目可以完整的使用annotation配置完成,至少要可以使用annotation完成绝大部分的配置才行。不然,一半annotation一半xml,反倒不如完全使用详细划分的xml配置来得清爽 |
|
返回顶楼 | |
发表时间:2008-04-10
看了看感觉有点晕,哈哈
入行不久就这样吧~ |
|
返回顶楼 | |
发表时间:2008-04-10
一个非常优雅的实现方案。
这里对注入的限制就是要注意,你所配置的bean,都是单例的,而且这种需求基本对应于某种配置是全局的,却又要在各处使用的那种情况。 额外提一点,这里的实现是可以扩展的,当前ahuaxuan的实现对Properties文件中的类型做了限制,只支持String类型,对于想进一步扩展的朋友,可以在这个基础上,利用反射进行其他类型的扩展,比如Integer,BigDecimal等等。 |
|
返回顶楼 | |
发表时间:2008-04-10
Frederick 写道 一直使用xml配置,前段时间对spring2.5的annotation做了点研究,在一个私人项目(ext2.0 + spring mvc + db4o)里面尝试使用annotation来完成整个项目,结果发现很多不如意的地方,感觉spring2.5的annotation还很不完善。
就目前spring 2.5的annotation,不敢在正式的项目中使用。所以还在沿用xml的配置。 希望spring 3.0可以完善它的annotation,可以使得一个项目可以完整的使用annotation配置完成,至少要可以使用annotation完成绝大部分的配置才行。不然,一半annotation一半xml,反倒不如完全使用详细划分的xml配置来得清爽 一个项目完整使用Annotation是不现实的,也不可能。 XML需要解决全局的,纲领性的配置,例如:dataSource,Transaction等等。而Annotation需要解决的是局部的,业务相关性非常强的配置,例如:Bean定义等等。 事实上,全局的,纲领性的配置可能在项目中永远不会改变,所以基本上XML文件变动的机会不大,可以考虑使用ant或者其他的生成工具生成。 |
|
返回顶楼 | |
发表时间:2008-04-10
这样做会不会更好一些?
在Config这个class所在的package下面,有一个同名的Config.properties文件,里面有设置同名field的值: accounts=xxx user=xxx password=xxx 然后实现一个BeanPostProcessor来根据这个约定来注入值。 这样就省了在Config.java文件写一堆的@Properties annotation,如果你觉得在某些情况下properties文件无法保证和field同名这个约定,你也可以再以@Properties annotation为优先设定。 |
|
返回顶楼 | |