BeanDefinitionRegistryPostProcessor與動態代理配合使用例子

實現這樣一種功能:
自定義了一個註解@MyReference,

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyReference{
}

被它註解的字段是就是我們要代理的類,希望在Spring啓動時將代理類注入到這些被該註解標識的字段。

如何實現:

1,如何實現在Spring啓動後將被@MyReference標註的類的替換 ?

將代理類的beanDefination註冊到容器中,並將其與委託類在容器中的name關聯,這樣容器注入的就是我們的代理類了。
利用 BeanDefinitionRegistryPostProcessor 來實現。

這裏我們代理類的功能是一樣的,都是獲取委託類的信息來向遠端發送請求再獲得結果,所以利用FactoryBeanInitializingBean,創建一個叫 ProxyFactoryBean 的類實現它們,內部聲明一個成員變量用來標識委託類的類型,afterPropertiesSet方法中根據類型創建對應代理類,該方法在初始化bean時被調用;對於每個標註類都會創建一個 ProxyFactoryBean 類型的beanDefination。

這樣我們項目下所有被標註的類,其在Spring容器中的BeanDefination實質上是個FactoryBean,而Spring在獲取一個Bean實例時若發現其是FactoryBean,則調用它的getObject,由於我們實現了InitializingBean,在bean初始化時afterPropertiesSet被調用,我們在該方法里根據類型創建該標註類的代理類對象,getObject返回的也是它。具體代碼實現在下面。

2,我們使用的是SpringBoot,上述功能作爲一個獨立的模塊,在項目中進行依賴,需要在容器啓動時進行觸發,如何觸發?利用Spring Boot的Spring Factories機制來實現。

關於Spring Factories推薦兩篇文章:
Spring Boot的擴展機制之Spring Factories
EnableAutoConfiguration註解的工作原理

功能模塊的resources目錄下 META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.miao.client.MyAutoConfiguration

MyAutoConfiguration

@Configuration
@ConditionalOnMissingBean(ProxyFactoryBeanRegistry.class)
@EnableConfigurationProperties(ClientProperties.class)
public class MyAutoConfiguration{

    @Autowired
    private ClientProperties properties;
    
    // 爲什麼爲static? 因爲下面的proxyFactoryBeanRegistry方法中要用到client
    // 而該方法必須是static的,因爲在@Configuration的類裏,若@Bean標註的方法的
    // 返回類型是BeanDefinitionRegistryPostProcessor,則該方法必須是static的
    // https://github.com/ulisesbocchio/jasypt-spring-boot/issues/45
    //https://stackoverflow.com/questions/41939494/springboot-cannot-enhance-configuration-bean-definition-beannameplaceholderreg
    private static Client client = new Client();

    @Bean
    public Client client() {
        log.info("初始化Client設置discovery");
        ServiceDiscovery discovery = ......
        client.setDiscovery(discovery);
        client.init();
        return client;
    }
    
    /**
     * Cannot enhance @Configuration bean definition 'com.miao.rpc.client.RpcClientAutoConfiguration'
     * since its singleton instance has been created too early.
     * The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type:
     * Consider declaring such methods as 'static'.
     *
     * 在@Configuration的類裏,若@Bean標註的方法的返回類型是BeanDefinitionRegistryPostProcessor,則該方法必須是static的
     * 原因可能是: 
     * Having a BeanFactoryPostProcessor in a @Configuration class breaks the default post-processing 
     * of that @Configuration class.
     */
    @Bean
    public static ProxyFactoryBeanRegistry proxyFactoryBeanRegistry(){
    	// 這裏只能直接去property文件中獲取,因爲此時配置文件對象還未注入
        String basePackage = PropertityUtil.getProperty("rpc.serverBasePackage");
        return new ProxyFactoryBeanRegistry(basePackage, client);
    }

接下來我們利用 BeanDefinitionRegistryPostProcessor 在bean初始化前將委託類的beanDefinition替換爲代理類的beanDefinition,委託類指的是被@MyReference標註,將其實現指向我們的proxy代理類,對它們的調用實際是對我們代理類的調用。

/**
 * 實現後置處理器BeanDefinitionRegistryPostProcessor,該類繼承自BeanFactoryPostProcessor
 * BeanFactoryPostProcessor在容器實例化任何bean之前讀取bean的定義(配置元數據),並可以修改它。
 * BeanDefinitionRegistryPostProcessor可以讓我們註冊bean到容器中
 *
 * 這裏我的目的就是生成代理類的beanDefinition,將其與委託類在容器中的名字關聯,
 * 這樣@Autowired注入的就是我們實現的代理類了
 */
@Slf4j
public class ProxyFactoryBeanRegistry implements BeanDefinitionRegistryPostProcessor {
    private String basePackage;
    private Client client;

    public ProxyFactoryBeanRegistry(String basePackage, Client client) {
        this.basePackage = basePackage;
        this.client = client;
    }

    /**
     * 掃描指定路徑下的所有類,得到其class對象,查詢class對象中是否有@RpcReference註解的字段,
     * 爲該字段生成RpcProxyFactoryBean類型的beanDefinition,使其與字段的beanName關聯,
     * RpcProxyFactoryBean是個FactoryBean,該類初始化時返回的是getObject方法的對象的bean,
     * 而我們的RpcProxyFactoryBean同樣實現了InitializingBean,初始化時會先調用它的afterPropertiesSet方法,
     * 在該方法中利用反射創建代理類對象,這樣在客戶端代碼中@Autowired便將代理類注入到了@RpcReference註解的字段,
     * 客戶端對服務接口方法的調用,實際上觸發了代理類的invoke方法,在該方法中收集信息,如類名,方法名,參數
     * 再向服務端發出請求
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        log.info("正在添加動態代理類的FactoryBean");
        // 掃描工具類
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true); // 設置過濾條件,這裏掃描所有
        Set<BeanDefinition> beanDefinitionSet = scanner.findCandidateComponents(basePackage); // 掃描指定路徑下的類
        for (BeanDefinition beanDefinition : beanDefinitionSet) {
            log.info("掃描到的類的名稱{}", beanDefinition.getBeanClassName());
            String beanClassName = beanDefinition.getBeanClassName(); // 得到class name
            Class<?> beanClass = null;
            try {
                beanClass = Class.forName(beanClassName); // 得到Class對象
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            Field[] fields = beanClass.getDeclaredFields(); // 獲得該Class的多有field
            for (Field field : fields) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                // @MyReference註解標識
                MyReference reference = field.getAnnotation(MyReference.class);
                Class<?> fieldClass = field.getType(); // 獲取該標識下的類的類型,用於生成相應proxy
                if (reference != null) {
                    log.info("創建" + fieldClass.getName() + "的動態代理");
                    BeanDefinitionHolder holder = createBeanDefinition(fieldClass);
                    log.info("創建成功");
                    // 將代理類的beanDefination註冊到容器中
                    BeanDefinitionReaderUtils.registerBeanDefinition(holder, beanDefinitionRegistry);
                }
            }

        }
    }

    /**
     * 生成fieldClass類型的BeanDefinition
     * @return
     */
    private BeanDefinitionHolder createBeanDefinition(Class<?> fieldClass) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ProxyFactoryBean.class);
        String className = fieldClass.getName();
        // bean的name首字母小寫,spring通過它來注入
        String beanName = StringUtils.uncapitalize(className.substring(className.lastIndexOf('.')+1));
        // 給ProxyFactoryBean字段賦值
        builder.addPropertyValue("interfaceClass", fieldClass);
        builder.addPropertyValue("client", client);
        return new BeanDefinitionHolder(builder.getBeanDefinition(), beanName);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

這裏對代理類的要求相同,都是獲取委託類的類名,請求方法名,請求參數信息等,發送到遠端,所以這裏利用 FactoryBean 來返回統一的代理類

/**
 * InitializingBean:初始化時afterPropertiesSet被調用,生成interfaceClass類型的代理類對象
 * 在上面的ProxyFactoryBeanRegistry#createBeanDefinition方法中會創建
 * 該類的BeanDefination,並給interfaceClass,client字段注入值
 */
@Slf4j
public class ProxyFactoryBean implements FactoryBean<Object>, InitializingBean {
    private Client client;
    private Class<?> interfaceClass; // 要生成的代理的類型
    private Object proxy;

    @Override
    public Object getObject() throws Exception {
        return proxy;
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceClass;
    }

    @Override
    public boolean isSingleton() {
        return true; // 單例
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.proxy = Proxy.newProxyInstance(
                interfaceClass.getClassLoader(),
                new Class<?>[]{interfaceClass},
                (proxy, method, args) -> {
                    ......
                });
    }

    // 下面兩個setxxx方法用於容器的注入使用
    public void setClient(RpcClient client) {
        this.client = client;
    }

    public void setInterfaceClass(Class<?> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }
}

接下來我們只需要在項目代碼中像下面這樣直接注入即可

    @Autowired
    @MyReference
    HelloService helloService;

對 helloService#xxx 某某方法的調用將直接觸發代理類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章