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目標: 我們的目標是實現(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;
- }
@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就是我們要實現(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
- }
@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;
- }
- }
@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ù)大大下降。
|