Spring實戰系列(三)-BeanPostProcessor的妙用
"對於Spring框架,現實公司使用的非常廣泛,但是由於業務的複雜程度不同,瞭解到很多小夥伴們利用Spring開發僅僅是利用了Spring的IOC,即使是AOP也很少用,但是目前的Spring是一個大家族,形成了一個很大的生態,覆蓋了我們平時開發的方方面面,拋開特殊的苛刻要求之外,Spring的生態其實已經很全面了,所以在此開個系列來研究下Spring提供給我們的一些平時不太卻又很實用的內容。"
說明:
對於Spring開發時,我們有時會遇到同一個介面有多個實現類,為了避免錯誤,我們通常在具體呼叫的地方通過ApplicationContext根據業務的需要來選擇不同的介面實現類,雖然可以在抽象出一個工廠方法,但是還是感覺不夠優雅,如果通過@Autowired
示例:
1、宣告介面
public interface HelloService {
public void sayHello();
}
2、對應的介面實現類1:
@Service public class HelloServiceImpl1 implements HelloService{ @Override public void sayHello() { System.out.println("你好我是HelloServiceImpl1"); } }
3、對應介面實現類2:
@Service
public class HelloServiceImpl2 implements HelloService{
@Override
public void sayHello() {
System.out.println("你好我是HelloServiceImpl2");
}
}
4、自定義註解:
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface RountingInjected { String value() default "helloServiceImpl1"; }
5、自定義BeanPostProcessor實現類:
@Component
public class HelloServiceInjectProcessor implements BeanPostProcessor {
@Autowired
private ApplicationContext applicationContext;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> targetCls = bean.getClass();
Field[] targetFld = targetCls.getDeclaredFields();
for (Field field : targetFld) {
//找到制定目標的註解類
if (field.isAnnotationPresent(RountingInjected.class)) {
if (!field.getType().isInterface()) {
throw new BeanCreationException("RoutingInjected field must be declared as an interface:" + field.getName()
+ " @Class " + targetCls.getName());
}
try {
this.handleRoutingInjected(field, bean, field.getType());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
/**
* @param field
* @param bean
* @param type
* @throws IllegalAccessException
*/
private void handleRoutingInjected(Field field, Object bean, Class type) throws IllegalAccessException {
Map<String, Object> candidates = this.applicationContext.getBeansOfType(type);
field.setAccessible(true);
if (candidates.size() == 1) {
field.set(bean, candidates.values().iterator().next());
} else if (candidates.size() == 2) {
String injectVal = field.getAnnotation(RountingInjected.class).value();
Object proxy = RoutingBeanProxyFactory.createProxy(injectVal, type, candidates);
field.set(bean, proxy);
} else {
throw new IllegalArgumentException("Find more than 2 beans for type: " + type);
}
}
6、對應的代理實現類:
public class RoutingBeanProxyFactory {
private final static String DEFAULT_BEAN_NAME = "helloServiceImpl1";
public static Object createProxy(String name, Class type, Map<String, Object> candidates) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(type);
proxyFactory.addAdvice(new VersionRoutingMethodInterceptor(name, candidates));
return proxyFactory.getProxy();
}
static class VersionRoutingMethodInterceptor implements MethodInterceptor {
private Object targetObject;
public VersionRoutingMethodInterceptor(String name, Map<String, Object> beans) {
this.targetObject = beans.get(name);
if (this.targetObject == null) {
this.targetObject = beans.get(DEFAULT_BEAN_NAME);
}
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return invocation.getMethod().invoke(this.targetObject, invocation.getArguments());
}
}
}
7、結果測試類
@Component
public class HelloServiceTest {
@RountingInjected(value = "helloServiceImpl2")
private HelloService helloService;
public void testSayHello() {
helloService.sayHello();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("colin.spring.basic.advanced.bbp");
HelloServiceTest helloServiceTest = applicationContext.getBean(HelloServiceTest.class);
helloServiceTest.testSayHello();
}
上述是整個解決方案的示例流程,其核心思想就是根據自定義註解攔截要注入的介面實現類,運用java反射和代理的知識點來進行有效的實現類注入。
再次補充下BeanPostProcessor的一些知識點,
BeanPostProcessor介面作用:
如果我們想在Spring容器中完成bean例項化、配置以及其他初始化方法前後要新增一些自己邏輯處理。我們需要定義一個或多個BeanPostProcessor介面實現類,然後註冊到Spring IoC容器中。
Spring中Bean的例項化過程圖示:
注意:
1、介面中的兩個方法都要將傳入的bean返回,而不能返回null,如果返回的是null那麼我們通過getBean方法將得不到目標。
2、BeanFactory和ApplicationContext對待bean後置處理器稍有不同。ApplicationContext會自動檢測在配置檔案中實現了BeanPostProcessor介面的所有bean,並把它們註冊為後置處理器,然後在容器建立bean的適當時候呼叫它,因此部署一個後置處理器同部署其他的bean並沒有什麼區別。而使用BeanFactory實現的時候,bean 後置處理器必須通過程式碼顯式地去註冊,在IoC容器繼承體系中的ConfigurableBeanFactory介面中定義了註冊方法
/**
* Add a new BeanPostProcessor that will get applied to beans created
* by this factory. To be invoked during factory configuration.
* <p>Note: Post-processors submitted here will be applied in the order of
* registration; any ordering semantics expressed through implementing the
* {@link org.springframework.core.Ordered} interface will be ignored. Note
* that autodetected post-processors (e.g. as beans in an ApplicationContext)
* will always be applied after programmatically registered ones.
* @param beanPostProcessor the post-processor to register
*/
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);
另外,不要將BeanPostProcessor標記為延遲初始化。因為如果這樣做,Spring容器將不會註冊它們,自定義邏輯也就無法得到應用。假如你在<beans />元素的定義中使用了'default-lazy-init'屬性,請確信你的各個BeanPostProcessor標記為'lazy-init="false"'。
InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor是BeanPostProcessor的子介面,可以在Bean生命週期的另外兩個時期提供擴充套件的回撥介面,即例項化Bean之前(呼叫postProcessBeforeInstantiation方法)和例項化Bean之後(呼叫postProcessAfterInstantiation方法),該介面定義如下:package org.springframework.beans.factory.config;
import java.beans.PropertyDescriptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
throws BeansException;
}
其使用方法與上面介紹的BeanPostProcessor介面類似,只時回撥時機不同。如果是使用ApplicationContext來生成並管理Bean的話則稍有不同,使用ApplicationContext來生成及管理Bean例項的話,在執行BeanFactoryAware的setBeanFactory()階段後,若Bean類上有實現org.springframework.context.ApplicationContextAware介面,則執行其setApplicationContext()方法,接著才執行BeanPostProcessors的ProcessBeforeInitialization()及之後的流程。