《玩轉Spring》第二章 BeanPostProcessor擴充套件
這一章,我們介紹如何通過實現BeanPostProcessor介面,對容器中的Bean做一層代理,來滿足我們的個性化需求。
一、基本原理
我很不想貼程式碼,顯得太沒水平。有時候自己的語言又很空洞,不得不貼程式碼,感覺用程式碼來說明一件事反而更容易些。
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class BeanPostPrcessorImpl implements BeanPostProcessor { // Bean 例項化之前執行該方法 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println( beanName + "開始例項化"); return bean; } // Bean 例項化之後執行該方法 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println( beanName + "例項化完成"); return bean; } }
然後將這個BeanPostProcessor介面的實現配置到Spring的配置檔案中就可以了:
<bean class="com.jialin.spring.BeanPostPrcessorImpl"/>
注意:
1、BeanPostProcessor的作用域是容器級的,它只和所在容器有關。
2、BeanFactory和ApplicationContext對待bean後置處理器稍有不同。ApplicationContext會自動檢測在配置檔案中實現了BeanPostProcessor介面的所有bean,並把它們註冊為後置處理器,然後在容器建立bean的適當時候呼叫它。部署一個後置處理器同部署其他的bean並沒有什麼區別。而使用BeanFactory實現的時候,bean 後置處理器必須通過下面類似的程式碼顯式地去註冊:
BeanPostPrcessorImpl beanPostProcessor = new BeanPostPrcessorImpl();
Resource resource = new FileSystemResource("applicationContext.xml");
ConfigurableBeanFactory factory = new XmlBeanFactory(resource);
factory.addBeanPostProcessor(beanPostProcessor);
factory.getBean("beanName");
二、應用
好東西總要用起來
1、利用BeanPostProcessor介面實現Bean的動態代理。
2、自動注入Logging,用於記錄日誌。
Logger註解
@Retention(RetentionPolicy.RUNTIME)
@Target( {
ElementType.FIELD
})
public @interface Logger {
}
package com.jialin.framework.annotation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class LogBeanPostProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
List<Class<?>> clazzes = getAllClasses(bean);
for (Class<?> clazz : clazzes) {
initializeLog(bean, clazz);
}
return bean;
}
/**
* 取得指定bean的class以及所有父類的列表, 該列表排列順序為從父類到當前類
* @param bean
* @return
*/
private List<Class<?>> getAllClasses(Object bean) {
Class<? extends Object> clazz = bean.getClass();
List<Class<?>> clazzes = new ArrayList<Class<?>>();
while (clazz != null) {
clazzes.add(clazz);
clazz = clazz.getSuperclass();
}
Collections.reverse(clazzes);
return clazzes;
}
/**
* 對logger變數進行初始化
*
* @param bean
* @param clazz
*/
private void initializeLog(Object bean, Class<? extends Object> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(Logger.class) == null) {
continue;
}
if (!field.getType().isAssignableFrom(Log.class)) {
continue;
}
field.setAccessible(true);
try {
field.set(bean, LogFactory.getLog(clazz));
} catch (Exception e) {
throw new BeanInitializationException(String
.format("初始化logger失敗!bean=%s;field=%s", bean, field));
}
}
}
}
在Spring配置檔案中,加入
<!--配置根據註解,自動注入Log物件-->
<bean id="logBeanPocessor" class="com.jialin.framework.annotation.LogBeanPostProcessor" lazy-init="false" />
//實現代理的BeanPostProcessor的例項,其中注入上文介紹的log:
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.apache.commons.logging.Log;
public class ProxyBeanPostProcesser implements BeanPostProcessor {
private Map map = new ConcurrentHashMap(100);
//使用logger的註解來簡化定義,並自動注入
@Logger
private static final Log log;
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
MyProxy proxy = new MyProxy();
if (bean.toString().contains("Proxy")) {
log.info(beanName + "為代理類,不進行再次代理!");
return bean;
}
//……
//可以加一些其他條件,過濾掉你不想代理的bean
//……省略部分程式碼
if (map.get(beanName) != null) {
log.info(beanName + "已經代理過,不進行再次代理!");
return map.get(beanName);
}
proxy.setObj(bean);
proxy.setName(beanName);
Class[] iterClass = bean.getClass().getInterfaces();
if (iterClass.length > 0) {
Object proxyO = Proxy.newProxyInstance(bean.getClass().getClassLoader(), iterClass, proxy);
map.put(beanName, proxyO);
return proxyO;
} else {
log.info(beanName + "必須實現接口才能被代理。");
return bean;
}
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import sun.reflect.Reflection;
//代理類Proxy,其中注入上文介紹的log:
public class MyProxy implements InvocationHandler {
//使用logger的註解來簡化定義,並自動注入
@Logger
private static final Log log;
private Object obj;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("-------------------" + "bean 名稱為【" + name + "】方法為【" + method.getName() + "】-------------"
+ obj.getClass());
return method.invoke(obj, args);
}
public void printDetail(String detail) {
log.error(detail);
}
}
在Spring配置檔案中,加入
<!--配置自動為Bean配置代理物件-->
<bean id="proxyBeanPocessor" class="com.jialin.framework.proxy. ProxyBeanPostProcesser " />
從上面的介紹不難看出,實現了BeanPostProcessor介面定製我們自己的Bean處理器可以在Spring容器初始化Bean的時候做我們想做的很多事。Spring首先會使用自己的處理器,然後陸續使用我們的處理器,典型的裝飾者模式,我們自定義的處理器就是一個個具體的裝飾者。
在這裡預告一下,後續會出一個文章,教大家如何《實現一個面向服務的IOC容器,不使用任何框架,純j2se程式碼》。
這篇到這,下篇繼續,敬請關注!謝謝
賈琳 寫於 2014-6-25 河北廊坊 多雲