上一篇文章圍繞 @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
處理邏輯一樣,可以參考博主以前文章:
https://blog.csdn.net/anLA_/article/details/104507941
當在每個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反饋給社區,等博主後面文章具體分析。
覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,一起研究Spring: