@DubboComponentScan注解
我們在使用Dubbo時,在沒有使用阿里的starter時,都會在啟動類打上@DubboComponentScan這個注解來將我們被Dubbo的@service注解標注的類,注冊到Spring IOC容器中,這個注解很關鍵,Dubbo服務的發布,以及參考都從此注解開始,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
// 省略注解屬性
}
注解里Import了DubboComponentScanRegistrar這個類,從字面上就能大致理解是做什么用的了–掃描Dubbo的組件并將其注冊,我們看看這個類具體長什么樣并干些什么,
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
}
DubboComponentScanRegistrar 實作了spring 提供的ImportBeanDefinitionRegistrar介面,這個介面對于擴展spring非常重要,其作用是注冊BeanDefinition,熟悉spring的應該會知道,spring在注冊bean前,會將掃描的bean組裝成BeanDefinition,由于這里不是講解spring IOC原理,就不過多贅述,后續再另開篇幅寫IOC原理,ImportBeanDefinitionRegistrar介面定義如下:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
// 忽略BeanNameGenerator 轉發呼叫其多載的另一個registerBeanDefinitions方法
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
我們再看看DubboComponentScanRegistrar 是如何實作重寫ImportBeanDefinitionRegistrar的介面:
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 決議@service注解,獲取將要掃描的包路徑集合
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 后置處理器--將帶有@service注解的類決議并注冊到Ioc容器中,并export進行服務暴露
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
// 后置處理器--將帶有@Reference注解的類決議并注冊到IOC容器中,并初始化ReferenceBeanInvocationHandler,代理執行invoke進行服務呼叫
registerReferenceAnnotationBeanPostProcessor(registry);
}
// 省略其他代碼....
}
由于篇幅限制,本文著重分析Dubbo的發布原理,呼叫原理將另起篇幅分析,
我們先分析getPackagesToScan方法,看看Dubbo是如何處理獲取包路徑的,在分析之前,我們先看看這個DubboComponentScan注解有哪些屬性
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
接著具體看看getPackagesToScan的操作
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
// 獲取DubboComponentScan注解的注解屬性,這里的AnnotationAttributes類在spring原始碼中也是能尋覓到其蹤跡的,
// AnnotationAttributes是spring抽出來的注解屬性物件,與其經常配對出現的就是spring封裝的工具類AnnotationUtils和AnnotatedElementUtils
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(DubboComponentScan.class.getName()));
// 獲取注解的basePackages屬性值
String[] basePackages = attributes.getStringArray("basePackages");
// 獲取注解的basePackageClasses屬性值
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
// 獲取注解的value屬性值
String[] value = attributes.getStringArray("value");
// 用value 初始化一個packagesToScan 集合
Set<String> packagesToScan = new LinkedHashSet<String>(Arrays.asList(value));
// 添加basePackages的值
packagesToScan.addAll(Arrays.asList(basePackages));
// 遍歷basePackageClasses,將制定的類所處的包路徑也添加到packagesToScan集合中
for (Class<?> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
// 如果從三處basePackages、basePackageClasses、value獲取的包路徑都為空
// 回傳該注解所標注的當前類所處的包路徑作為packagesToScan
// 從此處我們就可以知道為什么@DubboComponentScan為何通常標注在啟動類上的原因了
if (packagesToScan.isEmpty()) {
return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
}
return packagesToScan;
}
看到這里,如果熟悉spring的朋友不知是否會有跟筆者有這么這么一個疑問,value 和 basePackages 兩個屬性本質都一樣,可互相作為別名,注解原始碼的注釋也證明這一點,那為何不用@AliasFor注解來簡化這兩個屬性的決議操作呢?
搞定了要掃描的包路徑,我們當然就要在這些包路徑來做決議了,具體怎么決議,我們來看一下原始碼:
private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
// 得到一個將指定類作為RootBeanDefinition,并將其作為BeanDefinitionBuilder構造引數來實體化BeanDefinitionBuilder,跟BeanDefinitionBuilder 比較相似的還有個BeanDefinitionHolder,因此可見spring的抽象設計是做得很好的,拿beanDefinition 不會直接拿,而是通過builder或者holder這些包裝類來拿
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
// 添加構造引數
builder.addConstructorArgValue(packagesToScan);
// 設定角色
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 獲得beanDefinition
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 將此beanDefinition 注冊到IOC中
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
最重要的一步就是構建ServiceAnnotationBeanPostProcessor后置處理器這個beanDefinition,我們來看看這個后置處理器做了什么,從宣告上看,它實作了BeanDefinitionRegistryPostProcessor、EnvironmentAware、ResourceLoaderAware以及BeanClassLoaderAware介面,熟悉spring都知道,這四個介面都是spring留給我們開發人員擴展spring的寶貴途徑,
public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
}
我們可以輕易拿到當前spring背景關系的environment、resourceLoader 以及classLoader三個資源,為我們后續深入處理時,提供便捷,這里順帶解釋一下這三者,Environment、ClassLoader不難理解,我們系統都會分環境,加載類時一定會用到類加載器來加載對應類,對于ResourceLoader 資源加載器又為何需要呢?在spring實作中,spring在掃描的類要將其轉為beanDefinition 前,還有一步操作是將其轉為一個個Resource,
// ResourceLoaderAware
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
// ResourceLoaderAware
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
// BeanClassLoaderAware
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
接下來我們最關鍵的一個介面實作–BeanDefinitionRegistryPostProcessor,重寫postProcessBeanDefinitionRegistry方法
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 處理包路徑的空格,并決議轉換包路徑的占位符
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
// 注冊resolvedPackagesToScan包路徑下帶有@service的bean到BeanDefinitionRegistry
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掃描器物件
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
// 構建beanNameGenerator.分兩步
// 第一步:如果當前是registry是SingletonBeanRegistry型別,則構建spring內建的org.springframework.context.annotation.internalConfigurationBeanNameGenerator
// 第二步:第一步不成立的話,則直接new AnnotationBeanNameGenerator()來構建
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
// 給dubbo掃描器設定beanname生成器
scanner.setBeanNameGenerator(beanNameGenerator);
// 給dubbo掃描器設定過濾鏈,告訴掃描器只掃apacha的@Service的
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
// 這里是修復issue -- 打阿里的@service不生效而加的
scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
// 遍歷包,逐一掃描
for (String packageToScan : packagesToScan) {
scanner.scan(packageToScan);
// 查找所有@service的beanDefinitionHolders ,不管是否在spring的@ComponentScan指定掃描范圍內
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
// 第三步:遍歷beanDefinitionHolders 并將其注冊到registry
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
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繼承了ClassPathBeanDefinitionScanner,呼叫父類ClassPathBeanDefinitionScanner的scan掃描,真正的掃描邏輯在doScan中,作用是掃描,并將其決議為BeanDefinition添加到registry中,由于這塊是spring的內容了,不再過多贅述,
第二步:查找所有@service的beanDefinitionHolders ,不管是否在spring的@ComponentScan指定掃描范圍內
private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(
ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,
BeanNameGenerator beanNameGenerator) {
// 通過dubbo掃描器在對應包路徑下獲取組件候選者們
Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageToScan);
// 創建一個BeanDefinitionHolder 的集合
Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<>(beanDefinitions.size());
// 遍歷
for (BeanDefinition beanDefinition : beanDefinitions) {
// 通過beanname生成器為當前的beanDefinition生成bean的name
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
// 包裝成BeanDefinitionHolder
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
// 添加到BeanDefinitionHolder 集合中
beanDefinitionHolders.add(beanDefinitionHolder);
}
return beanDefinitionHolders;
}
不知讀者是否有跟筆者有這么一個疑問–第一步交由spring的掃描與第二步dubbo自己查找掃描,都是獲取到@service的beanDefinition并將其注冊到registry,這兩部像是重復操作?其實不然,第二步會在第三步的配合下,其beanDefinition將具體為Dubbo內建抽象的SviceBean的beanDefinition
思考:findServiceBeanDefinitionHolders可否用scanner.doScan替代,或者這兩者選一?
第三步:registerServiceBean進行注冊
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {
// 得到當前beanDefinitionHolder對應的bean的class
// 底層是org.springframework.util.ClassUtils#resolveClassName
Class<?> beanClass = resolveClass(beanDefinitionHolder);
// 獲取apacha或者阿里的@service的Annotation物件
Annotation service = findServiceAnnotation(beanClass);
// 獲取其注解屬性
AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
// 處理獲取Dubbo service 的介面型別 InterfaceClass
// 等下著重分析
Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
// 獲取beanname
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
//包裝成Dubbo抽象的ServiceBean的BeanDefinition
// 等下著重分析
AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);
// 生成ServiceBean 的beanname
String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);
// 檢查是否重復候選者
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) {
// 注冊
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
// 省略部分代碼
}
我們繼續深入分析resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass)
public static Class<?> resolveServiceInterfaceClass(AnnotationAttributes attributes, Class<?> defaultInterfaceClass)
throws IllegalArgumentException {
ClassLoader classLoader = defaultInterfaceClass != null ? defaultInterfaceClass.getClassLoader() : Thread.currentThread().getContextClassLoader();
// 獲取注解的interfaceClass的屬性值,默認為void.class
Class<?> interfaceClass = getAttribute(attributes, "interfaceClass");
// 如果為默認值
if (void.class.equals(interfaceClass)) {
interfaceClass = null;
// 取注解interfaceName的屬性值
String interfaceClassName = getAttribute(attributes, "interfaceName");
// 如果interfaceClassName 有值
if (hasText(interfaceClassName)) {
// 如果interfaceClassName存在并能被classLoader加載
if (ClassUtils.isPresent(interfaceClassName, classLoader)) {
// 決議其interfaceClassName所對應的interfaceClass
interfaceClass = resolveClassName(interfaceClassName, classLoader);
}
}
}
// 如果interfaceClass屬性沒有指定并且interfaceClassName也沒指定或者所指定的類不存在或者不可加載
if (interfaceClass == null && defaultInterfaceClass != null) {
// 獲取beanDefinitionHolder對應的bean Class的所實作的所有介面,并取第一個
// 看到這一步有沒有突然發現我們的@service是打在實作類上,然后在注冊中心看到的介面類,為其介面的權限定名的原因,
// 因此這里有個坑,我們的實作類實作的介面的順序切記,業務介面先放,再放其他介面
Class<?>[] allInterfaces = getAllInterfacesForClass(defaultInterfaceClass);
if (allInterfaces.length > 0) {
interfaceClass = allInterfaces[0];
}
}
return interfaceClass;
}
從原始碼就可以清楚看出,interfaceClass的優先級高于interfaceName,所以在指定interface時要注意!
我們再繼續分析buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName),其作用是將BeanDefinition包裝成ServiceBean的BeanDefinition,包含ref、interface、parameters、provider、monitor、application、module等資訊,ref屬性持有是傳入的beanDefinitionHolder對應的beanname所對應的bean參考,
ServiceBean實作了ApplicationListener監聽,會在ServiceBean被注冊到spring背景關系時,觸發監聽,監聽器里的實作就是export暴露
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 {@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();
}
我們來具體看看ServiceBean類定義
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
ApplicationEventPublisherAware {
其中實作了監聽介面,監聽一個spring背景關系重繪事件,我們看看監聽器實作
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
里面進行export進行服務暴露,我們來看看如何服務暴露,原始碼跟進去一看究竟,
兩步:1、呼叫父類ServiceConfig的export; 2、發布一個ServiceBeanExportedEvent事件
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
ServiceConfig的export方法的關鍵就是呼叫checkAndUpdateSubConfigs、doExport方法,
public synchronized void export() {
// 一些列檢查并啟動注冊中心
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return;
}
// 是否延時暴露
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 暴露
doExport();
}
}
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
}
前面都是做一些校驗,真正實作在doExportUrls方法上,這個方法主要做的是遍歷RegistryConfig集合registries,這個屬性的就是注冊中心,得到List registryURLs并配合協議,執行protocoldoExportUrlsFor1Protocol(protocolConfig, registryURLs)完成暴露
由于具體暴露也是相對有點復雜,所以另起篇幅,詳細分析export具體實作,我們先暫時有這么個宏觀理解,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/242374.html
標籤:java
下一篇:斗地主發牌Java課程設計
