過去我都寫 PropertyUtil 類別載入設定檔,再透過 get 方法取值。Spring 提供 PropertyPlaceholderConfigurer 讀取設定檔,在 Spring 設定檔用 ${hibernate.dialect} 方式注入 JavaBean,但程式碼中取值不便。接觸 Java 註解技術後發現其強大 (Hibernate 映射、WebService 皆可用註解完成),便思考能否透過註解載入 properties 值到 Java 類別。
大體方案:
根據 Spring 中 JavaBean 初始化順序,實作 BeanPostProcessor 接口的 Bean 會在其他 Bean 初始化後處理。可寫一個 Bean 實作 BeanPostProcessor,屬性指向屬性檔路徑,初始化時讀取內容,在 postProcessBeforeInitialization 方法中對標註註解的 Bean 賦值。但 Spring 已有 PropertyPlaceholderConfigurer 載入屬性檔,另寫一類不夠統一,可能造成多個屬性檔。解決辦法是擴展 PropertyPlaceholderConfigurer:繼承該類並實作 BeanPostProcessor、InitializingBean 介面。在 PropertyPlaceholderConfigurer 初始化完成後取得載入的屬性內容,在 postProcessBeforeInitialization 中對標註註解的類別屬性賦值。
如何確定 PropertyPlaceholderConfigurer 載入完成?根據初始化順序,實作 InitializingBean 接口的 Bean,容器會在初始化後呼叫 afterPropertiesSet 方法。故寫一類繼承 PropertyPlaceholderConfigurer 並實作 BeanPostProcessor、InitializingBean 即可解決。
package com.test.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Property {
String name() default "";
}
package com.test.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.util.ReflectionUtils;
public class AnnotationBeanPostProcessor extends PropertyPlaceholderConfigurer
implements BeanPostProcessor, InitializingBean {
private java.util.Properties pros;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().getAnnotation(Property.class) != null) {
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
Property p = method.getAnnotation(Property.class);
if (p != null) {
Object para = pros.getProperty(p.name());
// 參數型別轉換
if (method.getParameterTypes()[0].getName().equals("java.lang.Integer")) {
para = new Integer(para.toString());
}
ReflectionUtils.invokeMethod(method, bean, new Object[]{para});
}
}
}
return bean;
}
@Override
public void afterPropertiesSet() throws Exception {
pros = mergeProperties();
}
}
package com.test;
import com.test.annotation.Property;
@Property
public class Bean {
private String name;
private Integer age;
private String address;
// ... getter
public void setName(String name) { this.name = name; }
@Property(name="com.test.Bean.address")
public void setAddress(String address) { this.address = address; }
public Integer getAge() { return age; }
@Property(name="com.test.Bean.age")
public void setAge(Integer age) { this.age = age; }
}
@Property
public class JavaBean {
private String name;
private String address;
// ... getter
@Property(name="com.test.JavaBean.name")
public void setName(String name) { this.name = name; }
public void setAddress(String address) { this.address = address; }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="propertyConfigurer" class="com.test.annotation.AnnotationBeanPostProcessor">
<property name="locations">
<list><value>classpath*:system.properties</value></list>
</property>
</bean>
<bean id="javaBean" class="com.test.JavaBean">
<property name="address" value="${com.test.JavaBean.address}"/>
</bean>
<bean id="bean" class="com.test.Bean">
<property name="name" value="${com.test.Bean.name}"/>
</bean>
</beans>
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
System.out.println("載入設定檔結束");
JavaBean javaBean = (JavaBean) context.getBean("javaBean");
System.out.println(javaBean.getName());
System.out.println(javaBean.getAddress());
Bean bean = (Bean) context.getBean("bean");
System.out.println(bean.getName());
System.out.println(bean.getAddress());
System.out.println(bean.getAge());
}
}
繼承 PropertyPlaceholderConfigurer 另一原因是保留原有 ${} 注入方式。BeanPostProcessor 有兩個方法,為何寫在 postProcessBeforeInitialization 而非 postProcessAfterInitialization?因為前者在 Bean 的 init 方法之前執行,init 方法中可能用到類別屬性,故必須在 init 執行前先賦值完成。