1. 程式人生 > >手動建立動態代理物件bean到spring容器

手動建立動態代理物件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}) @Slf
4j 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的時機。