我们都知道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
,同时生成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 {}
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()方法进行操作
然后会调用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
开始暴露。 具体的暴露流程之前已经介绍容量。
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
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对应的服务被注入到其他类中时引用。
网友评论