1. 程式人生 > >Spring實戰系列(三)-BeanPostProcessor的妙用

Spring實戰系列(三)-BeanPostProcessor的妙用

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