Dubbo与Spring的融合机制

微信公众号:房东的小黑黑

路途随遥远,将来更美好

学海无涯,大家一起加油!

我们都知道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://juejin.im/post/5e8c8f3ee51d45470b4fde86

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Dubbo与Spring的融合机制

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址