美文网首页Dubbo专题
Dubbo与Spring的融合机制

Dubbo与Spring的融合机制

作者: 九点半的马拉 | 来源:发表于2020-04-07 22:41 被阅读0次

    我们都知道Dubbo可以与Spring进行融合,那是怎么进行融合的呢?
    我先介绍一下官方文档中是如何实现与Spring融合的,然后再从底层分析一下。

    案例

    Service注解暴露服务

    @Service
    public class AnnotationServiceImpl implements AnnotationService {
        @Override
        public String sayHello(String name) {
            return "annotation: hello, " + name;
        }
    }
    

    增加应用配置信息

    # dubbo-provider.properties
    dubbo.application.name=annotation-provider
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    dubbo.protocol.name=dubbo
    dubbo.protocol.port=20880
    

    指定Spring扫描路径

    @Configuration
    @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.simple.annotation.impl")
    @PropertySource("classpath:/spring/dubbo-provider.properties")
    static public class ProviderConfiguration {      
    }
    

    Reference注解引用服务

    @Component("annotationAction")
    public class AnnotationAction {
        @Reference
        private AnnotationService annotationService;
        public String doSayHello(String name) {
            return annotationService.sayHello(name);
        }
    }
    

    增加应用配置信息

    # dubbo-consumer.properties
    dubbo.application.name=annotation-consumer
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    dubbo.consumer.timeout=3000
    

    指定Spring扫描路径

    @Configuration
    @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.simple.annotation.action")
    @PropertySource("classpath:/spring/dubbo-consumer.properties")
    @ComponentScan(value = {"org.apache.dubbo.samples.simple.annotation.action"})
    static public class ConsumerConfiguration {
    
    }
    

    调用服务

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
        context.start();
        final AnnotationAction annotationAction = (AnnotationAction) context.getBean("annotationAction");
        String hello = annotationAction.doSayHello("world");
    }
    

    上面是整体融合Spring的案例,接下来分析Service注解和Reference注解是怎么实现的。

    ServiceBean的有关过程

    当用户使用注解@DubboComponentScan时,会激活DubboComponentScanRegister,同时生成ServiceAnnotationBeanPostProcessorReferenceAnnotationBeanPostProcessor, ServiceAnnotationBeanPostProcessor处理器实现了BeanDefinitionRegistryPostProcessor接口,Spring容器会在所有Bean注册之后回调postProcessBeanDefinitionRegistry方法。
    在这个方法里先提取用户配置的扫描包名称,然后委托Spring对所有符合包名的class文件做字节码分析,然后扫描Dubbo的注解@Service作为过滤条件,将扫描的服务创建BeanDefinitionHolder,用于生成ServiceBean定义,最后注册ServiceBean的定义并做数据绑定和解析。

    这时我们注册了ServiceBean的定义,但是还没有实例化。
    ServicecBean的结构如下:

    public class ServiceBean<T> extends ServiceConfig<T> implements 
                InitializingBean, DisposableBean, ApplicationContextAware,
                        ApplicationListener<ContextRefreshedEvent>, BeanNameAware {}
    

    InitializingBean

    只包含afterPropertiesSet()方法,继承该接口的类,在初始化Bean的时候会执行该方法。在构造方法之后调用。

    ApplicationContextAware

    Spring容器会检测容器中的所有Bean,如果发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该Bean的setApplicationContextAware()方法,调用该方法时,会将容器本身作为参数传给该方法。

    ApplicationListener

    当Spring容器初始化之后,会发布一个ContextRefreshedEvent事件,实现ApplicationListener接口的类,会调用onApplicationEvent()方法。

    重要的接口主要是这几个,那么执行的先后顺序是怎样的呢?

    如果某个类实现了ApplicationContextAware接口,会在类初始化完成后调用setApplicationContext()方法进行操作

    首先会执行ApplicationContextAware中的setApplicationContextAware()方法。

     @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
            SpringExtensionFactory.addApplicationContext(applicationContext);
            try {
                Method method = applicationContext.getClass().getMethod("addApplicationListener", ApplicationListener.class); // backward compatibility to spring 2.0.1
                method.invoke(applicationContext, this);
                supportedApplicationListener = true;
            } catch (Throwable t) {
                if (applicationContext instanceof AbstractApplicationContext) {
                    try {
                        Method method = AbstractApplicationContext.class.getDeclaredMethod("addListener", ApplicationListener.class); // backward compatibility to spring 2.0.1
                        if (!method.isAccessible()) {
                            method.setAccessible(true);
                        }
                        method.invoke(applicationContext, this);
                        supportedApplicationListener = true;
                    } catch (Throwable t2) {
                    }
                }
            }
        }
    

    这里主要是将Spring的上下文引用保存到SpringExtensionFactory中,里面有个set集合,保存所有的Spring上下文。这里实现了Dubbo与Spring容器的相连,在SPI机制中利用ExtensionLoader.getExtension生成扩展类时,会有一个依赖注入的过程,即调用injectExtension()方法,它会通过反射获取类的所有方法,然后遍历以set开头的方法,得到set方法的参数类型,再通过ExtensionFactory寻找参数类型相同的扩展类实例。

    如果某个类实现了InitializingBean接口,会在类初始化完成后,并在setApplicationContext()方法执行完毕后,调用afterPropertiesSet()方法进行操作

    然后会调用InitializingBeanafterPropertiesSet()方法。

    public void afterPropertiesSet() throws Exception {
        if (getProvider() == null) {
            //......
        }
        if (getApplication() == null
                && (getProvider() == null || getProvider().getApplication() == null)) {
            //......
        }
        if (getModule() == null
                && (getProvider() == null || getProvider().getModule() == null)) {
            //......
        }
        if ((getRegistries() == null || getRegistries().isEmpty())) {
            //......
        }
        if ((getProtocols() == null || getProtocols().isEmpty())
                && (getProvider() == null || getProvider().getProtocols() == null || 
                getProvider().getProtocols().isEmpty())) {
            //......
        }
        if (getPath() == null || getPath().length() == 0) {
            if (beanName != null && beanName.length() > 0
                    && getInterface() != null && getInterface().length() > 0
                    && beanName.startsWith(getInterface())) {
                setPath(beanName);
            }
        }
        if (!isDelay()) {
            export();
        }
    }
    

    主要是将Dubbo中的应用信息、注册信息、协议信息等设置到变量中。最后有个方法值得注意的是isDelay方法当返回true时,表示无需延迟导出;返回false时,表示需要延迟导出。

    最后会调用ApplicationListene中的onApplicationEvent方法。

    public void onApplicationEvent(ContextRefreshedEvent event) {
        //是否有延迟暴露 && 是否已暴露 && 是不是已被取消暴露
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            //暴露服务
            export();
        }
    }
    

    此时ServiceBean开始暴露。 具体的暴露流程之前已经介绍容量。

    ReferenceBean的有关过程

    在Dubbo中处理ReferenceBean是通过ReferenceAnnotionBeanPostProcessor处理的,该类继承了InstantiationAwareBeanPostProcessor,用来解析@Reference注解并完成依赖注入。

    public class ReferenceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
            implements MergedBeanDefinitionPostProcessor, PriorityOrdered, ApplicationContextAware, BeanClassLoaderAware,
            DisposableBean {
        // 省略注解
    }
    

    InstatiationAwareBeanPostProcessor

    postProcessBeforeInstantiation方法: 在实例化目标对象执行之前,可以自定义实例化逻辑,如返回一个代理对象。

    postProcessAfterInitialization方法:Bean实例化完成后执行的后处理操作,所有初始化逻辑、装配逻辑之前执行。

    postProcessPropertyValues方法: 完成其他定制的一些依赖注入和依赖检查等,可以增加属性和属性值修改。

    新版本出现了改动,采用AnnotationInjectedBeanPostProcessor来处理。

    public class ReferenceAnnotationBeanPostProcessor extends AnnotationInjectedBeanPostProcessor<Reference>
            implements ApplicationContextAware, ApplicationListener
    

    AnnotationInjectedBeanPostProcessorReferenceAnnotationBeanPostProcessor的父类,它实现InstantiationAwareBeanPostProcessorAdapter的postProcessPropertyValues方法,这个是实例化的后置处理,这个方式是在注入属性时触发,就是要在注入@Reference的接口时候,要将接口封装成动态代理的实例注入到Spring容器中.

    public PropertyValues postProcessPropertyValues(
                PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
            InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
            try {
                metadata.inject(bean, beanName, pvs);
            } catch (BeanCreationException ex) {
                throw ex;
            } catch (Throwable ex) {
                throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getName()
                        + " dependencies is failed", ex);
            }
            return pvs;
        }
    

    主要分为两步:
    1) 获取类中标注的@Reference注解的字段和方法。
    2)反射设置字段或方法对应的引用

    最重要的是第二步,通过inject方法进行反射绑定。

      @Override
            protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
                Class<?> injectedType = field.getType();
                injectedBean = getInjectedObject(annotation, bean, beanName, injectedType, this);
                ReflectionUtils.makeAccessible(field);
                field.set(bean, injectedBean);
            }
    

    里面最主要的就是对生成的ReferenceBean设置一个代理对象。

     private Object buildProxy(String referencedBeanName, ReferenceBean referenceBean, Class<?> injectedType) {
            InvocationHandler handler = buildInvocationHandler(referencedBeanName, referenceBean);
            Object proxy = Proxy.newProxyInstance(getClassLoader(), new Class[]{injectedType}, handler);
            return proxy;  
    
    private static class ReferenceBeanInvocationHandler implements InvocationHandler {
    
            private final ReferenceBean referenceBean;
    
            private Object bean;
    
            private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) {
                this.referenceBean = referenceBean;
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    if (bean == null) { // If the bean is not initialized, invoke init()
                        init();
                    }
                    result = method.invoke(bean, args);
                } catch (InvocationTargetException e) {
                    // re-throws the actual Exception.
                    throw e.getTargetException();
                }
                return result;
            }
    
            private void init() {
                this.bean = referenceBean.get();
            }
        }
    

    服务引用的触发时机有两个:
    一种是ReferenceBean初始化的时候;另一种是ReferenceBean对应的服务被注入到其他类中时引用。

    相关文章

      网友评论

        本文标题:Dubbo与Spring的融合机制

        本文链接:https://www.haomeiwen.com/subject/mahaphtx.html