天天看點

BeanPostProcessor的使用

  • 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的執行個體化過程圖示:

BeanPostProcessor的使用

注意:

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()及之後的流程。