手动创建动态代理对象bean到spring容器

一般编写spring boot starter时都涉及到自动配置,自动配置的的实现都涉及的手动注册bean到容器和从容器获取bean。

一般的情况下,自动配置的方式可以参考spring-boot自动配置(AutoConfiguration)的实现,来定义我们的自定义自动配置,参考:
spring boot自动配置原理
以下是一个zookeeper的自动创建连接的类,并且将连接bean注入到容器

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//@Bean注解方法的会执行创建bean
@Configuration
//当以下类在路径上才执行创建bean
@ConditionalOnClass({CuratorFrameworkFactory.class, CuratorFramework.class})
//使配置bean生效
@EnableConfigurationProperties(value = {ZkProperties.class})
@Slf4j
public class ZkAutoConfiguration {

    private final ZkProperties properties;


    public ZkAutoConfiguration(ZkProperties properties) {
        this.properties = properties;
    }

    @Bean
    @ConditionalOnProperty(value = {"myrpc.zk.on"}, matchIfMissing = false)
    public CuratorFramework createCurator() throws Exception {
        return ZkConnectUtil.buildAZkConnection();
    }

    @Bean
    @ConditionalOnBean(CuratorFramework.class)
    public ZkRegisterCenter createRegister(){
        log.info("------创建服务注册组件");
        return new ZkRegisterCenter();
    }

    @Bean
    @ConditionalOnBean(CuratorFramework.class)
    public ZkDiscoverCenter createDiscover(){
        log.info("------创建服务发现组件");
        return new ZkDiscoverCenter();
    }
}

以上只是简单的例子,代码没贴全,不过大概模式是这样子,同时需要在spring.factories配置我们的配置类,进行自动配置

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.garine.learn.myrpc.registry.zookeeper.ZkAutoConfiguration\

这样的话一般我们starter中需要自动配置的bean都可以通过@Configuration注解和@Bean注解来完成,这种配置对于一般情况下是能够适用的。

特殊情况下我们需要创建动态代理对象的bean到容器中,如果我们能够明确知道代理的接口类,当然也可以通过@Bean注解实现动态代理类的bean注册,但是更多的时候是无法确定到底需要生成哪些接口的动态代理类

例如rpc框架中的客户端实际调用用来访问远程服务的对象一般是动态生成的一个代理类,内部封装网络请求的处理等操作。
例如rmi的访问模式就是通过一个stub类访问远程接口服务。一般来说,rpc需要通过xml或者注解来配置哪些接口需要生成动态代理访问对象,提供给客户端使用,因此涉及不确定的代理类的bean注册

以上听起来很拗口,实际过程就是
1.通过程序扫描获得所有需要代理的接口类
2.为接口创建代理对象,代理逻辑看我们实际需求
3.注册代理对象到容器

使用@bean注解已经无法满足需求,因此我们可以改用原生的spring操作,来进行注册bean。

首先是一般的原生注册bean操作都是利用BeanDefinitionBuilder首先,只能实例化非接口的类,写了个简单的工具类,registryBean方法是一般bean注册方案:

import com.example.gupaolearn.rpc.bean.InterfaceFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * spring 手动创建、获取bean工具
 * @author zhoujy
 * @date 2018年07月10日
 **/
@Component
public class BeanUtil implements ApplicationContextAware,BeanDefinitionRegistryPostProcessor {
    private static ApplicationContext applicationContext;

    /**
     * 实例化时自动执行,通常用反射包获取到需要动态创建的接口类,容器初始化时,此方法执行,创建bean
     * 执行过程与registryBeanWithDymicEdit基本一致
     * @param beanDefinitionRegistry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        List<Class<?>> beanClazzs = null;//反射获取需要代理的接口的clazz列表
        for (Class beanClazz : beanClazzs) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
            definition.getPropertyValues().add("interfaceClass", beanClazz);
            definition.getPropertyValues().add("params", "注册传入工厂的参数,一般是properties配置的信息");
            definition.setBeanClass(InterfaceFactoryBean.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            beanDefinitionRegistry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
        }
    }

    /**
     * 实例化时自动执行
     * @param configurableListableBeanFactory
     * @throws BeansException
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    /**
     * BeanUtil实例化时自动注入applicationContext
     * @param applicationContextz
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContextz) throws BeansException {
        applicationContext = applicationContextz;
    }

    public static Object getBean(Class<?> clazz){
        return applicationContext.getBean(clazz);
    }

    public static Object getBean(String className){
        return applicationContext.getBean(className);
    }
    /**
     * 直接创建bean,不设置属性
     * @param beanId
     * @param clazz
     * @return
     */
    public static boolean registryBean(String beanId, Class<?> clazz){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        BeanDefinition definition = builder.getBeanDefinition();
        getRegistry().registerBeanDefinition(beanId, definition);
        return true;
    }


    /**
     * 为已知的class创建bean,可以设置bean的属性,可以用作动态代理对象的bean扩展
     * @param beanId
     * @param
     * @return
     */
    public static boolean registryBeanWithEdit(String beanId, Class<?> factoryClazz, Class<?> beanClazz){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
        GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
        definition.getPropertyValues().add("myClass", beanClazz);
        definition.setBeanClass(factoryClazz);
        definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        getRegistry().registerBeanDefinition(beanId, definition);
        return true;
    }

    /**
     * 为已知的class创建bean,可以设置bean的属性,可以用作动态代理对象的bean扩展
     * @param beanId
     * @param
     * @return
     */
    public static boolean registryBeanWithDymicEdit(String beanId, Class<?> factoryClazz, Class<?> beanClazz, String params){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
        GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
        definition.getPropertyValues().add("interfaceClass", beanClazz);
        definition.getPropertyValues().add("params", params);
        definition.setBeanClass(factoryClazz);
        definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        getRegistry().registerBeanDefinition(beanId, definition);
        return true;
    }

    /**
     * 获取注册者
     * context->beanfactory->registry
     * @return
     */
    public static BeanDefinitionRegistry getRegistry(){
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        return (DefaultListableBeanFactory)configurableApplicationContext.getBeanFactory();
    }
}

假如需要实现动态代理类的bean注册,需要使用registryBeanWithDymicEdit方法,我们需要提供一个工厂类(factoryClazz参数)用来实例化bean,也就是通过工厂方法来初始化bean,代码如下:


import lombok.Data;
import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.Proxy;

/**
 * 1.支持动态代理类创建bean
 * 2.动态代理逻辑需要我们自己实现invocationHandler
 * 3.用途:dubbo、rmi等rpc框架都需要在客户端,根据访问的服务接口,
 *   进行创建一个动态代理对象,然后注册到spring容器中,客户端通过注解引用这个代理对象进行一系列我们封装的操作,如网络io等。
 * @author garine
 * @date 2018年07月10日
 **/
@Data
public class InterfaceFactoryBean<T> implements FactoryBean<T> {
    private Class<T> interfaceClass;

    /**
     * 在bean注册时设置
     */
    private String params;

    /**
     * 新建bean
     * @return
     * @throws Exception
     */
    @Override
    public T getObject() throws Exception {
        //利用反射具体的bean新建实现,不支持T为接口。
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass},new DymicInvocationHandler(params));
    }

    /**
     * 获取bean
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return interfaceClass;
    }

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

}
import com.example.gupaolearn.Util.CommonUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author garine
 * @date 2018年07月10日
 **/
public class DymicInvocationHandler implements InvocationHandler{
    private String params;

    public DymicInvocationHandler(String params){
        this.params = params;
    }

    /**
     * 可扩展处理点invoke
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (args.length > 0){
            CommonUtil.println("代理对象\n->方法"+method.getName()+
                    "\n->方法调用参数:"+args[0].toString()+
                    "\n->bean注册时读取到参数:"+params);
        }
        return "invocation return";
    }
}

在上面中,getObject方法实现了代理类的生成,并且通过registryBeanWithDymicEdit的工厂bean注册到spring容器中,registryBeanWithDymicEdit中可以设置interfaceClasses属性来指定实例化何种接口的代理类,params注入到工厂中,一般可以作为我们程序启动时读取的自定义配置扩展。

回头看看我们的bean注册工具类,提供的registryBeanWithDymicEdit,registryBean,registryBeanWithEdit三个方法都是静态方法,是可以用来在程序运行时手动注册bean的。那么如何能够在程序启动时就创建注册bean呢?原理大致一样,不过是利用spring自动调用的方法。

BeanUtil 实现了ApplicationContextAware,BeanDefinitionRegistryPostProcessor接口,里面有三个方法实现
setApplicationContext:注入applicationContext
postProcessBeanFactory:注入beangactory
postProcessBeanDefinitionRegistry:注入注册bean使用的类

我们可以在postProcessBeanDefinitionRegistry里面执行注册我们所需要的bean的逻辑,程序启动时spring自动执行这个方法。需要注意:这个方法执行顺序先于@Value @Configuration @Bean等注解所以方法内部千万不能依赖这些注解获取值或者实例,因为是获取不到的。如果需要做配置文件读取,那么请你用原始的文件IO操作0.0~
使用静态的registryBeanWithDymicEdit方法来初始化的话,那么就可以依赖这些注解,因为我们可以定义注册bean的时机。

猜你喜欢

转载自blog.csdn.net/qq_20597727/article/details/80996190
今日推荐