微信公众号:房东的小黑黑
路途随遥远,将来更美好
学海无涯,大家一起加油!
我们都知道Dubbo可以与Spring进行融合,那是怎么进行融合的呢?
我先介绍一下官方文档中是如何实现与Spring融合的,然后再从底层分析一下。
@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 复制代码
@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
注解是怎么实现的。
当用户使用注解 @DubboComponentScan
时,会激活 DubboComponentScanRegister
,同时生成 ServiceAnnotationBeanPostProcessor
和 ReferenceAnnotationBeanPostProcessor
, 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 {} 复制代码
只包含 afterPropertiesSet()
方法,继承该接口的类,在初始化Bean的时候会执行该方法。在构造方法之后调用。
Spring容器会检测容器中的所有Bean,如果发现某个Bean实现了 ApplicationContextAware
接口,Spring容器会在创建该Bean之后,自动调用该Bean的 setApplicationContextAware()
方法,调用该方法时,会将容器本身作为参数传给该方法。
当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()方法进行操作
然后会调用 InitializingBean
的 afterPropertiesSet()
方法。
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
开始暴露。 具体的暴露流程之前已经介绍容量。
在Dubbo中处理 ReferenceBean
是通过 ReferenceAnnotionBeanPostProcessor
处理的,该类继承了 InstantiationAwareBeanPostProcessor
,用来解析@Reference注解并完成依赖注入。
public class ReferenceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered, ApplicationContextAware, BeanClassLoaderAware, DisposableBean { // 省略注解 } 复制代码
postProcessBeforeInstantiation
方法: 在实例化目标对象执行之前,可以自定义实例化逻辑,如返回一个代理对象。
postProcessAfterInitialization
方法:Bean实例化完成后执行的后处理操作,所有初始化逻辑、装配逻辑之前执行。
postProcessPropertyValues
方法: 完成其他定制的一些依赖注入和依赖检查等,可以增加属性和属性值修改。
新版本出现了改动,采用 AnnotationInjectedBeanPostProcessor
来处理。
public class ReferenceAnnotationBeanPostProcessor extends AnnotationInjectedBeanPostProcessor<Reference> implements ApplicationContextAware, ApplicationListener 复制代码
AnnotationInjectedBeanPostProcessor
是 ReferenceAnnotationBeanPostProcessor
的父类,它实现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对应的服务被注入到其他类中时引用。