BeanDefinitionRegistryPostProcessor与动态代理配合使用例子

  • Post author:
  • Post category:其他


实现这样一种功能:

自定义了一个注解@MyReference,

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

被它注解的字段是就是我们要代理的类,希望在Spring启动时将代理类注入到这些被该注解标识的字段。



如何实现:

1,如何实现在Spring启动后将被

@MyReference

标注的类的替换 ?

将代理类的beanDefination注册到容器中,并将其与委托类在容器中的name关联,这样容器注入的就是我们的代理类了。

利用

BeanDefinitionRegistryPostProcessor

来实现。

这里我们代理类的功能是一样的,都是获取委托类的信息来向远端发送请求再获得结果,所以利用

FactoryBean



InitializingBean

,创建一个叫

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 某某方法的调用将直接触发代理类。



版权声明:本文为sinat_34976604原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。