- 2020部落格位址彙總
- 2019年部落格彙總
轉載:https://blog.csdn.net/geekjoker/article/details/79868945
"對于Spring架構,現實公司使用的非常廣泛,但是由于業務的複雜程度不同,了解到很多小夥伴們利用Spring開發僅僅是利用了Spring的IOC,即使是AOP也很少用,但是目前的Spring是一個大家族,形成了一個很大的生态,覆寫了我們平時開發的方方面面,抛開特殊的苛刻要求之外,Spring的生态其實已經很全面了,是以在此開個系列來研究下Spring提供給我們的一些平時不太卻又很實用的内容。"
說明:
對于Spring開發時,我們有時會遇到同一個接口有多個實作類,為了避免錯誤,我們通常在具體調用的地方通過ApplicationContext根據業務的需要來選擇不同的接口實作類,雖然可以在抽象出一個工廠方法,但是還是感覺不夠優雅,如果通過@Autowired直接引入接口,則需要在某個實作類上标注@Primary,否則會報錯。那麼書歸正傳如何優雅的解決上述的問題呢,此處就介紹一種利用Spring的BeanPostProcessor來處理。話不多說先上接口
示例:
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()及之後的流程。