輔助連結
Dubbo系列之 (一)SPI擴充套件
Dubbo系列之 (二)Registry註冊中心-註冊(1)
Dubbo系列之 (三)Registry註冊中心-註冊(2)
Dubbo系列之 (四)服務訂閱(1)
介紹
dubbo的服務訂閱可以通過2種方式: 1)通過xml檔案的標籤<dubbo:reference /> ;2)通過註解@DubboReference。
這2種服務訂閱在使用上基本沒區別,因為標籤<dubbo:reference />上的屬性欄位都可以在註解@DubboReference上對應的找到。一般使用XML的配置檔案方式來訂閱服務。
但是這2種的原始碼實現上有一定的區別和公用。
首先,這2種的實現最終都是呼叫ReferenceBean#get()方法來產生代理物件和訂閱服務。所以ReferenceBean#get()方法是我們分析的重點。
其次,標籤<dubbo:reference />的實現方式是通過Spring自定義的標籤來實現的,當一個<dubbo:reference />標籤被DubboBeanDefinitionParser處理轉化後,會變成一個RootBeanDefinition,接著註冊到Spring容器中。而這個RootBeanDefinition對應的類就是ReferenceBean,這個ReferenceBean 實現了工廠Bean介面FactoryBean,所以在具體建立這個Bean得物件時候,會呼叫FactoryBean#getObject()來返回具體類物件。剛好這個ReferenceBean的getObject()方法就是呼叫ReferenceBean#get()來返回具體引用服務型別物件和訂閱服務。
再次,註解@DubboReference的實現方式則有所不同,它是通過BeanPostProcessor來實現的。一般我們把註解@DubboReference打在某個被Spring託管的類的成員變數上,例如:
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
@DubboReference(check = false, mock = "true" ,version = "2.0.0")
private DemoService demoService;
......
}
所以可以知道@DubboReference註解和@Resource,@Autowired等注入註解類似,都是注入一個DemoService這種型別的物件。
而Dubbo框架正是通過自己編寫類似於AutowiredAnnotationBeanPostProcessor的處理器ReferenceAnnotationBeanPostProcessor,通過這個處理器來處理@DubboReference註解。讀者可以2者結合起來看。
ReferenceAnnotationBeanPostProcessor 分析
通過xml配置的方式的解析沒有太多的價值,所以我們直接分析註解的方式。接下來我們分析Spring擴充套件點和dubbo框架的相結合內容。
1、Spring相關:一個Bean屬性依賴注入的擴充套件點在哪
在遠端服務引用的解析前,我們需要先了解下Spring的一個特殊的BeanPostProcessor,即
InstantiationAwareBeanPostProcessor,該後置處理器主要有三個作用:
1)、Bean例項化前的一個回撥:postProcessBeforeInstantiation。
2)、Bean例項化後屬性顯式填充和自動注入前的回撥:postProcessAfterInstantiation
3)、給Bean物件屬性自動注入的機會:postProcessPropertyValues
顯而易見,被打上@DubboReference 註解的屬性值注入,就是利用了這個後置處理器的第三個作用(給Bean物件屬性自動注入的機會postProcessPropertyValues)。就是在這個方法內處理Bean物件屬性成員的注入。
有了以上Spring擴充套件點的基礎,我們來看下AbstractAnnotationBeanPostProcessor的postProcessPropertyValues。
因為ReferenceAnnotationBeanPostProcessor繼承了AbstractAnnotationBeanPostProcessor.
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
/**
* 查詢bean 下需要注入的相關成員(包括成員變數和方法,即被@DubboReference標註的成員,並把這些這些屬性集合封裝為一個物件InjectionMetadata,)
* InjectionMetadata 物件內部for 迴圈,一次注入相關的屬性值。
*/
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()得到查詢需要注入的相關成員。接著通過Spring內建的物件InjectionMetadata來注入bean的相關屬性。這裡需要注意點,metdata物件封裝了該bean 需要注入的所有屬性成員,在inject內部for迴圈一致注入。檢視程式碼類似如下:
public void inject(Object target, String beanName, PropertyValues pvs){
Collection<InjectedElement> elementsToIterate =
(this.checkedElements != null ? this.checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
element.inject(target, beanName, pvs);
}
}
}
2、Spring相關:一個引用了遠端服務的Bean,是如何找到打上@DubboReference註解的成員
通過第一個問題,我們知道當一個Spring Service的物件引用了遠端物件(通過註解@DubboReference),是通過postProcessPropertyValues注入的。接著我們需要找到它是如何需要注入的屬性成員。
/**
* 找到相關的需要注入的成員元資訊,並封裝為InjectionMetadata
*
* @param beanName 當前需要被注入的 beanName
* @param clazz 當前需要被注入的 類的Class
* @param pvs 當前 需要被注入bean 的引數
* @return
*/
private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
//獲取快取key,預設為beanName,否則為className
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
//從快取中拿,如果未null 或者注入屬性後設資料所在的目標類和當前需要注入屬性的bean的型別不一致時,需要重寫獲取
AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey); //雙層判斷鎖定
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) { //原來的目標類不一致,先clear下引數屬性,但排除需要的引數pvs
metadata.clear(pvs);
}
try {
metadata = buildAnnotatedMetadata(clazz); //通過需要注入的類的位元組碼clazz,得到需要被注入的屬性的元資訊。
this.injectionMetadataCache.put(cacheKey, metadata); //放入快取。
} catch (NoClassDefFoundError err) {
throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +
"] for annotation metadata: could not find class that it depends on", err);
}
}
}
}
return metadata;
}
該註釋已經非常清楚了,其實就是通過buildAnnotatedMetadata()方法構建注入的後設資料資訊,然後放入快取injectionMetadataCache中。而buildAnnotatedMetadata()方法的如下:
private AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
/**
*
* 1、查詢 需要注入的成員的元資訊
*/
Collection<AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
/**
*
* 2、查詢 需要注入的方法的元資訊
*/
Collection<AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
/**
*
* 組合返回元資訊
*/
return new AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}
其中findFieldAnnotationMetadata()和findAnnotatedMethodMetadata()方法其實就是分別在clazz上的成員和方法上找是否有被@DubboReference打標的屬性。
3、真正的屬性注入是怎麼實現的
Dubbo框架為了實現屬性的注入,分別定義了2個注入類,一個AnnotatedFieldElement和一個AnnotatedMethodElement。很顯然,一個是整對成員,一個整對方法。這個2個類都繼承了
Spring的InjectionMetadata.InjectedElement,然後實現inject方法。接著我們來看下
這2個類的inject 是如何實現:
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);
}
}
private class AnnotatedMethodElement extends InjectionMetadata.InjectedElement {
private final Method method;
private final AnnotationAttributes attributes;
private volatile Object object;
protected AnnotatedMethodElement(Method method, PropertyDescriptor pd, AnnotationAttributes attributes) {
super(method, pd);
this.method = method;
this.attributes = attributes;
}
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Class<?> injectedType = pd.getPropertyType();
Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);
ReflectionUtils.makeAccessible(method);
method.invoke(bean, injectedObject);
}
}
其實就是通過反射,把需要的屬性物件注入進來。成員屬性就通過field.set()。而方法就通過method.invoke().那麼注入的injectedObject物件是如何獲取的呢。從上面的inject()方法知道,通過呼叫getInjectedObject()方法來實現,而該方法其實只是從快取injectedObjectsCache中獲取注入的物件,如果不存在,就呼叫doGetInjectedBean().接著放入快取中。
doGetInjectedBean()方法的作用見註釋:
/**
*
*
* 該方法是個模板方法,用來得到一個需要注入型別的的物件。
*
* @param attributes ReferenceBean註解屬性
* @param bean 需要被注入的物件,一般是Spring Bean
* @param beanName 需要被注入的物件名
* @param injectedType 注入物件的型別
* @param injectedElement 注入物件描述元資訊
* @return
* @throws Exception
*/
@Override
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
/**
* The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
*/
/**
*
* 得到需要被注入的物件的BeanName,生成規則預設是,檢視ServiceBeanNameBuilder.build()
* ServiceBean:${interfaceName}:${version}:${group}
*/
String referencedBeanName = buildReferencedBeanName(attributes, injectedType);
/**
* The name of bean that is declared by {@link Reference @Reference} annotation injection
*/
/**
* 得到引用物件@ReferenceBean的BeanName ,
* 如果有Id 就是Id,沒有通過generateReferenceBeanName()產生,產生的類名如下:
* @Reference(${attributes})${interface}
*/
String referenceBeanName = getReferenceBeanName(attributes, injectedType);
/**
* 構建一個ReferenceBean 物件,如果不存在的話。
*
*/
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
/**
* 判斷是否為本地ServiceBean,一般都是遠端引用dubbo服務。
*/
boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);
/**
* 然後註冊referenceBean
*/
registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);
/**
* 把referenceBean 放入快取中。
*/
cacheInjectedReferenceBean(referenceBean, injectedElement);
/**
*
* 通過referenceBean 建立動態代理建立一個injectedType型別的物件。核心,建立一個代理物件,用來代表需要引用遠端的Service服務
*/
return getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);
}
上面內部方法呼叫都比較簡單,邏輯也非常清楚,最終是通過getOrCreateProxy()方法來建立一個遠端的代理物件,然後通過InjectedElement.inject注入該代理物件。
接著我們最好看下getOrCreateProxy()方法。
private Object getOrCreateProxy(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean,
Class<?> serviceInterfaceType) {
/**
*
* 如果是本地就有服務Bean的話。
*/
if (localServiceBean) { // If the local @Service Bean exists, build a proxy of Service
//通過Proxy.newProxyInstance建立一個新的代理物件,在內部通過applicationContext獲取本地Service即可。
return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
newReferencedBeanInvocationHandler(referencedBeanName));
} else {
//如果是遠端Service,判斷被引用的服務referencedBeanName是否已經存在在applicationContext上,是的話,直接暴露服務,
// 基本上不太可能,因為在前面已經判斷了被引用的服務Bean在遠端,所以這裡僅僅是為了防止localServiceBean判斷錯誤。
exportServiceBeanIfNecessary(referencedBeanName); // If the referenced ServiceBean exits, export it immediately
//接著直接通過get()方法來建立一個代理,這get操作就會引入dubbo服務的訂閱等相關內容。
return referenceBean.get();
}
}
可以看到最終呼叫了referenceBean.get()方法來建立一個遠端本地代理物件。referenceBean.get()在下一個章節分析。
到這裡,我們已經分析了@DubboReference註解的處理過程,然後知道了referenceBean.get()在Spring的postProcessPropertyValues擴充套件點上被呼叫。