上一篇文章围绕 @EnableDubbo
进行了深入的分析,本篇文章将重点看@Service
和@Reference
原理。
与上面两个注解相关联两个Bean类分别为:
ServiceAnnotationBeanPostProcessor
ReferenceAnnotationBeanPostProcessor
ServiceAnnotationBeanPostProcessor
上一篇文章中,ServiceAnnotationBeanPostProcessor
是在 DubboComponentScan
中,在 DubboComponentScanRegistrar
中,被注册到Spring 容器中。
public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
...
}
ServiceAnnotationBeanPostProcessor
最核心的角色是 一个 BeanDefinitionRegistryPostProcessor
,这样能够在refresh
中对BeanFactoryPostProcessor
处理时,对 Spring 容器中bean定义进行删改。
里面主要方法为 postProcessBeanDefinitionRegistry
:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 注册 DubboBootstrapApplicationListener
registerBeans(registry, DubboBootstrapApplicationListener.class);
// 获取扫描路径
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
// 具体扫描
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
if (logger.isWarnEnabled()) {
logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
}
上面方法则中,普通获取扫描包路径,最终调用 registerServiceBeans
进行扫描:
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
// 构建一个 DubboClassPathBeanDefinitionScanner,父类为 Spring 的 ClassPathBeanDefinitionScanner
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
// 获取一个beanGenerator
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
// 添加过滤类,即只扫描@Service注解的类。
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
// 添加对旧版本支持
scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
for (String packageToScan : packagesToScan) {
// 扫描,并且会将@Service 注解的类都注册进Spring
scanner.scan(packageToScan);
// 获取所有的 @Service 的 BeanDefinitionHolders
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
// 将上一步中获取到的所有 @Service 注解bean,都注册到ServiceBean类型到 Spirng中
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
if (logger.isInfoEnabled()) {
logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +
beanDefinitionHolders +
" } were scanned under package[" + packageToScan + "]");
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["
+ packageToScan + "]");
}
}
}
}
上面方法中,主要有以下几步操作:
- 构建一个
DubboClassPathBeanDefinitionScanner
,父类为Spring
的ClassPathBeanDefinitionScanner
,这是Spring
提供出来一个通用扫描器,其内置的@Conponent
扫描,以及Mybatis
中扫描,也是以这个类为主。 - 将 所有扫描出 的
@Service
Bean类,通过registerServiceBean
注册进成为Spring的Bean。
registerServiceBean
:
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {
// 获取到BeanClass
Class<?> beanClass = resolveClass(beanDefinitionHolder);
Annotation service = findServiceAnnotation(beanClass);
// 获取 @Service 注解的参数
AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
// 获取接口名
Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
// 获取ServiceBean名字
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
// 构造一个 serviceBean
AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);
// ServiceBean Bean name
String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) {
// 如果没有注册过,那么久进行注册
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
if (logger.isInfoEnabled()) {
logger.info("The BeanDefinition[" + serviceBeanDefinition +
"] of ServiceBean has been registered with name : " + beanName);
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition +
"] of ServiceBean[ bean name : " + beanName +
"] was be found , Did @DubboComponentScan scan to same package in many times?");
}
}
}
上面注册方法中,包括以下三个逻辑:
- 获取
@Service
参数 - 获取
ServiceBean
名字 - 构造一个
ServiceBean
类型BeanDefinition
- 将
ServiceBean
注册进Spring
容器
整个过程最终要的就是 ServiceBean
构造过程,即 buildServiceBeanDefinition
方法:
private AbstractBeanDefinition buildServiceBeanDefinition(Annotation serviceAnnotation,
AnnotationAttributes serviceAnnotationAttributes,
Class<?> interfaceClass,
String annotatedServiceBeanName) {
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol",
"interface", "interfaceName", "parameters");
propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames));
// References "ref" property to annotated-@Service Bean
addPropertyReference(builder, "ref", annotatedServiceBeanName);
// Set interface
builder.addPropertyValue("interface", interfaceClass.getName());
// Convert parameters into map
builder.addPropertyValue("parameters", convertParameters(serviceAnnotationAttributes.getStringArray("parameters")));
// Add methods parameters
List<MethodConfig> methodConfigs = convertMethodConfigs(serviceAnnotationAttributes.get("methods"));
if (!methodConfigs.isEmpty()) {
builder.addPropertyValue("methods", methodConfigs);
}
/**
* Add {@link org.apache.dubbo.config.ProviderConfig} Bean reference
*/
String providerConfigBeanName = serviceAnnotationAttributes.getString("provider");
if (StringUtils.hasText(providerConfigBeanName)) {
addPropertyReference(builder, "provider", providerConfigBeanName);
}
/**
* Add {@link org.apache.dubbo.config.MonitorConfig} Bean reference
*/
String monitorConfigBeanName = serviceAnnotationAttributes.getString("monitor");
if (StringUtils.hasText(monitorConfigBeanName)) {
addPropertyReference(builder, "monitor", monitorConfigBeanName);
}
/**
* Add {@link org.apache.dubbo.config.ApplicationConfig} Bean reference
*/
String applicationConfigBeanName = serviceAnnotationAttributes.getString("application");
if (StringUtils.hasText(applicationConfigBeanName)) {
addPropertyReference(builder, "application", applicationConfigBeanName);
}
/**
* Add {@link org.apache.dubbo.config.ModuleConfig} Bean reference
*/
String moduleConfigBeanName = serviceAnnotationAttributes.getString("module");
if (StringUtils.hasText(moduleConfigBeanName)) {
addPropertyReference(builder, "module", moduleConfigBeanName);
}
/**
* Add {@link org.apache.dubbo.config.RegistryConfig} Bean reference
*/
String[] registryConfigBeanNames = serviceAnnotationAttributes.getStringArray("registry");
List<RuntimeBeanReference> registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);
if (!registryRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("registries", registryRuntimeBeanReferences);
}
/**
* Add {@link org.apache.dubbo.config.ProtocolConfig} Bean reference
*/
String[] protocolConfigBeanNames = serviceAnnotationAttributes.getStringArray("protocol");
List<RuntimeBeanReference> protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);
if (!protocolRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
}
return builder.getBeanDefinition();
}
围绕两个问题说明:
-
@Service
注解 的bean,最终注入到Spring
中,是以ServiceBean
方式存在。 - 注册进Spring时候,需要将对应属性填充到
BeanDefinition
中,大概有以下:
- 包括基本的组件
"provider", "monitor", "application", "module", "registry", "protocol","interface", "interfaceName", "parameters"
- ref 属性,默认为实现类名字
- interface属性,暴露接口对应的接口类
- parameters 属性
- methods 属性
- provider 属性,即 ProviderConfig,即确定使用哪一份ProviderConfig
- monitor 属性,同上,可以指定哪一份配置
- application 属性
- module 属性
- registry(registries) 属性
- protocol(protocols) 属性
最终,这就是一个 @Service
在Spring 中初始化过程,但是似乎还少了些什么,读过博主以前博客同学应该知道,Dubbo的接口暴露过程远不止这些,最重要的还要执行export
方法呀。
其实这个问题的答案,在上一篇文章最后已经给出,在Spring容器完全初始化完之后,由 DubboBootstrap
中 exportServices
将所有ServiceBean
执行其 export
方法。
还有一个问题,如果使用 @Service
暴露后,在本地,可以使用 @Autowired
之类的注入注解 引用到吗?
答案是可以,因为Dubbo会往Spring中注入两种类型bean:
- 对应接口 实现类的bean(扫描时注入)
-
ServiceBean
类型bean
ReferenceAnnotationBeanPostProcessor
ReferenceAnnotationBeanPostProcessor
是为了处理 @Reference
注解,运行原理和 AutowiredAnnotationBeanPostProcessor
(处理@Autowired
)很相似。
public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements
ApplicationContextAware, ApplicationListener {
...
}
对应的父类为:
public abstract class AbstractAnnotationBeanPostProcessor extends
InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {
}
ReferenceAnnotationBeanPostProcessor
关键的接口超类为: InstantiationAwareBeanPostProcessorAdapter
和 MergedBeanDefinitionPostProcessor
。
从调用关系来看,MergedBeanDefinitionPostProcessor
在实例化前被调用(具体原因看博主前面文章哦),postProcessMergedBeanDefinition
优先调用,先看其方法:
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (beanType != null) {
// 获取所有的@Reference注解字段并缓存
InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
// 校验
metadata.checkConfigMembers(beanDefinition);
}
}
上面方法中,重要的是 findInjectionMetadata
方法,主要目的:
- 扫描所有非static 的字段和方法,并进行缓存。
这一步和@Autowired
的AutowiredAnnotationBeanPostProcessor
处理逻辑一样,可以参考博主以前文章:
当在每个Bean实例化前,找完其所有的@Reference
注解过的字段和方法后,就进入到 InstantiationAwareBeanPostProcessorAdapter
的作用范围了。InstantiationAwareBeanPostProcessorAdapter
的 postProcessPropertyValues
会在实例化时被调用:
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().getSimpleName()
+ " dependencies is failed", ex);
}
return pvs;
}
上面就是实例化之后,具体的初始化动作中执行,还是那个方法 findInjectionMetadata
获取到 InjectionMetadata
, 而后进行注入。
对 获取到的 InjectMetadata
进行注入:
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
}
此处的 InjectedElement
包括 两种类型 AnnotatedMethodElement
和 AnnotatedFieldElement
。这两种都是在 findInjectionMetadata
已经获取到的数据。
以 AnnotatedFieldElement
为例:
public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {
private final Field field;
private final AnnotationAttributes attributes;
private volatile Object bean;
protected AnnotatedFieldElement(Field field, AnnotationAttributes attributes) {
super(field, null);
this.field = field;
this.attributes = attributes;
}
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Class<?> injectedType = field.getType();
Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);
ReflectionUtils.makeAccessible(field);
field.set(bean, injectedObject);
}
}
对应 inject
方法逻辑,首先是获取对应类型,而后调用具体子类的 getInjectedObject
获取bean实例,最后反射调用set方法注入值。
在 AbstactAnnotationBeanPostProcessor
的 getInjectedObject
中,则是定义了一层缓存,如果缓存中没有,则再从子类中获取:
protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
String cacheKey = buildInjectedObjectCacheKey(attributes, bean, beanName, injectedType, injectedElement);
// 从缓存中获取
Object injectedObject = injectedObjectsCache.get(cacheKey);
if (injectedObject == null) {
// 调用子类创建
injectedObject = doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement);
// Customized inject-object if necessary
injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
}
return injectedObject;
}
最终还是回到了 ReferenceAnnotationBeanPostProcessor
,看它的 doGetInjectedBean
方法:
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
// 创建一个ServiceBean对应名字,例如 ServiceBean:com.anla.rpc.configcenter.provider.service.HelloService:1.0.0
String referencedBeanName = buildReferencedBeanName(attributes, injectedType);
// 生成一个@Reference bean对应名字,如果有id属性,则直接用id属性替代。 @Reference(check=false,version=1.0.0) com.anla.rpc.configcenter.provider.service.HelloService
String referenceBeanName = getReferenceBeanName(attributes, injectedType);
// 以 injectedElement 为蓝本,创建一个 @ReferenceBean实例
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
// 注册到Spring中
registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType);
// 缓存一份bean实例
cacheInjectedReferenceBean(referenceBean, injectedElement);
// 返回
return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType);
}
上面方法有以下几点关键逻辑:
-
buildReferenceBeanIfAbsent
方法里面其实也做了挺多复杂逻辑,包括检查依赖,检查Spring 各层级组件等:ReferenceBeanBuilder
的 build方法
public final C build() throws Exception {
checkDependencies();
C configBean = doBuild();
configureBean(configBean);
if (logger.isInfoEnabled()) {
logger.info("The configBean[type:" + configBean.getClass().getSimpleName() + "] has been built.");
}
return configBean;
}
- 构建出对应的
@Service
方法的bean名字,用于判断是否@Reference
的对象就在本地,如果就在本地,则注册一个@Reference
对象的别名。这一点在registerReferenceBean
有体现:
private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
AnnotationAttributes attributes,
Class<?> interfaceClass) {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
String beanName = getReferenceBeanName(attributes, interfaceClass);
if (existsServiceBean(referencedBeanName)) { // If @Service bean is local one
/**
* Get the @Service's BeanDefinition from {@link BeanFactory}
* Refer to {@link ServiceAnnotationBeanPostProcessor#buildServiceBeanDefinition}
*/
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref");
// The name of bean annotated @Service
String serviceBeanName = runtimeBeanReference.getBeanName();
// register Alias rather than a new bean name, in order to reduce duplicated beans
beanFactory.registerAlias(serviceBeanName, beanName);
} else { // Remote @Service Bean
if (!beanFactory.containsBean(beanName)) {
beanFactory.registerSingleton(beanName, referenceBean);
}
}
- 如果不是在本地,则直接注入单例到Spring容器中。
beanFactory.registerSingleton(beanName, referenceBean);
- 缓存注入的bean,以及inject对象。
- 在最后一步的
getOrCreateProxy
中,仍然会以 是否就是@Reference
本地对象为基础:
private Object getOrCreateProxy(String referencedBeanName, String referenceBeanName, ReferenceBean referenceBean, Class<?> serviceInterfaceType) {
if (existsServiceBean(referencedBeanName)) { // If the local @Service Bean exists, build a proxy of ReferenceBean
// 如果存在@Service对象,则返回一个代理对象
return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
wrapInvocationHandler(referenceBeanName, referenceBean));
} else {
// 默认是立刻获取
return referenceBean.get();
}
referenceBean.get();
就是dubbo
接口的refer
过程,这个看博主dubbo系列文章即可清楚。
ReferenceBean
本身是一个 FactoryBean
类型,其bean实例可以动态制定,主要在 getObject
方法中体现:
public Object getObject() {
return get();
}
整个 @Service
暴露和 @Reference
注入原理过程即已讲清楚,
但是感觉目前最新版本(2.7.7-SNAPSHOT) 在 @Reference
处理上,还有点小缺陷,已经有了处理,打算提交Issue和PR反馈给社区,等博主后面文章具体分析。