1背景
Spring2.5支持使用annotation來配置我們的service,比如如下代碼:- @Service("userService")
- public class UserServiceImpl extends BaseServiceSupport implements UserService {
- public void xxx() {
- }
- }
@Service("userService")public class UserServiceImpl extends BaseServiceSupport implements UserService { public void xxx() { }}
這樣就表示這個service需要被spring管理,不過只是這樣做是不夠的,我們還需要在applicationcontext***.xml中加入這么一段: - <context:component-scan base-package="xxxxxxx"/>
<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>
<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目標(biāo): 我們的目標(biāo)是實現(xiàn)一個Annotation,代碼如下: - @Service
- public class ImageFileUpload implements Serializable {
- @Properties(name="pic.address" )
- private String picAddress;
- @Properties(name="pic.url" )
- private String picUrl;
- private String picServerUrl;
- }
@Servicepublic 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就是我們要實現(xiàn)的Annotation,通過name的值作為key去對應(yīng)的properties中尋找對應(yīng)的value,并且主動賦值給ImageFileUpload的對應(yīng)屬性。 3步驟: 我們知道,spring在初始化完bean之后我們可以對這些bean進行一定的操作,這里就是一個擴展點,我決定使用BeanPostProcessor這個接口,這個接口中有一個postProcessAfterInitialization方法就是用來做bean的后處理的,一旦一個bean被初始化完成之后,我們就可以對這個bean進行賦值了。 但是考慮到我們項目中不是所有的bean都使用Annotation來注冊到spring中的,這些普通的,配置在xml文件中的bean也有用到${}的需求,所以我考慮擴展PropertyPlaceholderConfigurer這個類。我們來分析一下具體的代碼。 首先建立一個Annotation,如下: - @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Properties {
- String name();
- }
/** * @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();}
接著我們實現(xiàn)我們的擴展主類: - 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 {
- 本來我是通過set方法來把properties文件中的值注入到對應(yīng)的屬性上去的,后來downpour提供了更好的方案,就是下面這兩行代碼,雖然這樣做破壞了private的功能,同時破壞了封裝,但是確實節(jié)省了很多代碼,建議大家在業(yè)務(wù)代碼中不要這樣做,如果做框架代碼可以考慮一下。
- 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();
- }
- }
/** * @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文件中的值注入到對應(yīng)的屬性上去的,后來downpour提供了更好的方案,就是下面這兩行代碼,雖然這樣做破壞了private的功能,同時破壞了封裝,但是確實節(jié)省了很多代碼,建議大家在業(yè)務(wù)代碼中不要這樣做,如果做框架代碼可以考慮一下。 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 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
- }
@Componentpublic 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;
- }
- }
@Service(“userService”)public class UserServiceImpl implements UserService { @autowired private Config config; public void setConfig(Config config) { This.config = config; }}
就這么多內(nèi)容就ok了,如果按照原來的辦法,我們就需要在xml配置以上兩個bean,然后在里面寫一堆又一堆的${},肯定能讓你看了之后崩潰,至少我差點崩潰,因為它看上去實在是太丑陋了。而現(xiàn)在,我的心情好多了,因為我用這個@Properties(name = "")用的很爽,呵呵,而且即使有些bean是配置在xml文件中的,比如datasource等等,我們還是可以通過${}來進行設(shè)值,也就是說這個方案既支持annotation,也支持${},很好,很強大。 結(jié)語: 很顯然,在spring2.5的時代,以上這個需求是非常平常的,我相信在spring3.0中一定會提供這樣的功能,而且我覺得spring2.5應(yīng)該是一個過渡版本,雖然上面的方案中代碼行數(shù)并不多,但是我覺得很有價值,應(yīng)該很有市場才對,也許我們可以把這個東東叫做spring-properties2object-plugin。 題外話: 說點題外話吧,目前在我的項目里,我使用了struts2.0+spring2.5+hibernate3.2,使用struts2.0的時候我使用struts2.0的zero configuration和codebehind,基本上實現(xiàn)了真正意義零配置,剩下的都是一些common的配置,而且很少,不超過150行。在使用spring的時候,我也基本上是使用annotation來注冊我的bean,同時使用上面的方案來作為補充,所以applicationContext-xxx.xml中的也是一些common的配置,也是非常少,應(yīng)該只有200行左右。而hibernate我是使用annotation來配置我的PO,基本沒有配置文件。所以整個項目的xml文件中配置的總行數(shù)大大下降。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點擊舉報。