Spring源碼擴展篇-BeanPostProcessor

 

 

 

在spring中關於對bean的擴張可以分爲兩種:

1 基於所有bean:可以使用beanfactorypostproccessor進行修改,但是這種修改是全局的,也就是所有的bean都會被進行修改

2 基於單個bean:只針對單個特定的bean的實例化修改,這種情況可以使用BeanPostProcessor,也是接下來要簡單介紹和使用的

 

從spring源碼的來看,主要可以包含下面的幾個流程

1. 初始化時,spring容器有特別處理,會直接調用beanFactory.addBeanPostProcessor進行註冊(例如AbstractApplicationContext類的prepareBeanFactory方法中就有);
2. 找出所有實現了BeanPostProcessor接口的bean,註冊到容器,註冊順序如下:
第一:實現了PriorityOrdered接口的,排序後;
第二:實現了Ordered接口的,排序後;
第三:既沒實現PriorityOrdered接口,也沒有實現Ordered接口的;
第四:實現了MergedBeanDefinitionPostProcessor接口的(這些也按照PriorityOrdered、Ordered等邏輯拍過續);
第五:實例化一個ApplicationListenerDetector對象;
3. 實例化bean的時候,對於每個bean,先用MergedBeanDefinitionPostProcessor實現類的postProcessMergedBeanDefinition方法處理每個bean的定義類;
4. 再用BeanPostProcessor的postProcessBeforeInitialization方法處理每個bean實例;
5. bean實例初始化;
6. 用BeanPostProcessor的postProcessAfterInitialization方法處理每個bean實例;

它的核心方法只有兩個

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;    
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

可以自定義個bean的後置處理器來控制bean

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if("calculateService".equals(beanName)) {
            CalculateService calculateService = (CalculateService)bean;
            //修改calculateService實例的成員變量serviceDesc的值
            calculateService.setServiceDesc("desc from " + this.getClass().getSimpleName());
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("calculateService".equals(beanName)) {
            Utils.printTrack("do postProcess after initialization");
        }
        return bean;
    }
}

下面再介紹一個實際開發中可能會用到的例子:就是一個接口有多個實現類

當然對於一個接口有多個實現類,我們可以直接使用spring自帶的註解,只不過需要加入@Primary,但是這樣看起來不友好,接下來通過自定義註解+後置處理器來實現動態切換

首先定義一個接口和兩個實現類

public interface HelloService{
    public void sayHello();
}

@Service
public class HelloServiceImpl1 implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("你好我是HelloServiceImpl1");
    }
}

@Service
public class HelloServiceImpl2 implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("你好我是HelloServiceImpl2");
    }
}

定義一個自定義註解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface RountingInjected {
    String value() default "helloServiceImpl1";
}


定義bean的後置處理器
@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);
        }
    }
}

還需要一個代理工廠
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());
        }
    }
}

最後啓動一個application 測試一下效果
@SpringBootApplication
@MapperScan("com.lx.mapper")
public class MlxcApplication {
    public static void main(final String[] args) {
        try (ConfigurableApplicationContext applicationContext = SpringApplication.run(MlxcApplication.class, args)) {
            HelloServiceTest helloService = applicationContext.getBean(HelloServiceTest.class);
            helloService.testSayHello();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章