Reference: Dubbo Series (4) Service Subscription (1)
introduce
There are two ways to subscribe to dubbo’s services, the first via the tag
Thing in common: both are ultimately call com. Alibaba. Dubbo. Config. ReferenceConfig# the get method to generate the proxy objects and subscription services
The difference between:
-
The
tag is implemented through spring custom tags. Dubbo uses the ReferenceBean to parse the tag content. The ReferenceBean implements the FactoryBean interface, so concrete instance objects can be created using the getObject method, which calls the parent ReferenceConfig#get method. -
public class DubboNamespaceHandler extends NamespaceHandlerSupport { @Override public void init(a) { // omit some code... registerBeanDefinitionParser("service".new DubboBeanDefinitionParser(ServiceBean.class, true)); registerBeanDefinitionParser("reference".new DubboBeanDefinitionParser(ReferenceBean.class, false)); }}Copy the code
-
-
The @Reference annotation is implemented via a post-processor, similar to @Autowired, @Qualifier, etc., which inject specified properties into the Spring Bean object. Dubbo framework to write your own similar AutowiredAnnotationBeanPostProcessor post processor, called ReferenceAnnotationBeanPostProcessor, This post-processor is used to parse variables and methods annotated by the @Reference annotation table.
-
com.alibaba.dubbo.config.annotation.Reference
-
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) public @interfaceReference { Class<? > interfaceClass()default void.class; String interfaceName(a) default ""; String url(a) default ""; boolean check(a) default true; int retries(a) default 2; int timeout(a) default 0; // omit some information } Copy the code
-
conclusion
Main: postProcessPropertyValues – > findInjectionMetadata – > inject
Steps:
- Using spring provides extension points, InstantiationAwareBeanPostProcessor postProcessPropertyValues method, the method to trigger when spring bean object attribute assignment;
- Dubbo write ReferenceAnnotationBeanPostProcessor post processor, realize InstantiationAwareBeanPostProcessor postProcessPropertyValues method;
- First, obtain the meta information of the member variables and methods marked by ** @reference ** on the target class, and encapsulate the meta information set into the InjectionMetadata object;
- Iterates through annotated member variables and member methods, both of which inject property values via reflection. If it is a member variable, call
field.set()
If it is a member method, callmethod.invoke()
- Attribute valuesThe acquisition process is mainly divided into three steps:
- Create the ReferenceBean object and place it in the cache
- createReferenceBeanInvocationHandlerObject, and then execute
handler#init
Method, which is calledReferenceBean#get
Method to enter the actual service subscription- Finally, with the JDK dynamic proxy, a proxy object is returned
ReferenceAnnotationBeanPostProcessor parsing
Spring-related: Extension points for property dependency injection
References:
-
InstantiationAwareBeanPostProcessor interface is introduced
-
@autowired Principle of automatic injection
Before resolution remote service introduction, we’ll know a special post processor, InstantiationAwareBeanPostProcessor, source code is as follows:
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
/ * * * to perform in front of the bean is instantiated callback method, before postProcessBeforeInitialization execution * /
Object postProcessBeforeInstantiation(Class
beanClass, String beanName) throws BeansException;
/** * Callback */ before properties are explicitly populated and automatically injected after bean instantiation
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
/** * key! * Assign a value to the attribute */
PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException;
}
Copy the code
The word | meaning |
---|---|
Instantiation | Indicates instantiation. The object has not yet been generated |
Initialization | Indicates initialization. The object has been generated |
Dubbo is postProcessPropertyValues method to expand on the above, to dependent objects, create a proxy object, subscribe to the remote service.
First look at ReferenceAnnotationBeanPostProcessor inheritance relationships, and then focus on postProcessPropertyValues method.
Because ReferenceAnnotationBeanPostProcessor no postProcessPropertyValues method, so we are seeing is its parent class AnnotationInjectedBeanPostProcessor
AnnotationInjectedBeanPostProcessor#postProcessPropertyValues
@Override
public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
// Find meta information about the member to be injected into the current Bean object, including member variables and methods
// The @reference member encapsulates the metadata into an InjectionMetadata object
InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
try {
// Inject property values
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException("xxx");
}
return pvs;
}
Copy the code
The method uses findInjectionMetadata() to get the meta information of annotated member variables and methods, encapsulates these information sets into InjectionMetadata objects, and finally calls inject method, internally using for loop to inject attribute values one by one.
org.springframework.beans.factory.annotation.InjectionMetadata#inject
public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
Collection<InjectedElement> elementsToIterate =
(this.checkedElements ! =null ? this.checkedElements : this.injectedElements);
if(! elementsToIterate.isEmpty()) {boolean debug = logger.isDebugEnabled();
for (InjectedElement element : elementsToIterate) {
if (debug) {
logger.debug("Processing injected element of bean '" + beanName + "'." + element);
}
// Call the Inject method of the concrete implementation class, which will be examined nextelement.inject(target, beanName, pvs); }}}Copy the code
Spring-related: How do I find members of the @Reference annotation
Through the above section, we know that when a Spring Bean object need to introduce dubbo service, is through the @ Reference into service provider instance objects, principle is the method postProcessPropertyValues injection. Next, we need to know how to find the members of the @Reference annotation, including member variables and member methods.
AnnotationInjectedBeanPostProcessor#findInjectionMetadata
/** * Find the member metadata that needs to be injected and encapsulate it as InjectionMetadata **@paramBeanName Specifies the name of the object to be injected@paramClazz currently needs to inject class objects *@paramPVS currently requires the parameters of the injected object *@returnReturns meta information about the member to be injected */
public InjectionMetadata findInjectionMetadata(String beanName, Class
clazz, PropertyValues pvs) {
// Get the cache key. Default is beanName, otherwise claaName
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// The target class of the injected attribute metadata is not the same as that of the currently injected class and needs to be fetched again
AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
// Double judgment
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
// It is not consistent with the original target class, first clear parameter attributes, but exclude the required parameters
if(metadata ! =null) {
metadata.clear(pvs);
}
try {
// Find the attribute information that needs to be injected and put it in the cache
metadata = buildAnnotatedMetadata(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;
}
Copy the code
Get the metadata from the cache. If not, call the buildAnnotatedMetadata method.
AnnotationInjectedBeanPostProcessor#buildAnnotatedMetadata
private AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(finalClass<? > beanClass) {
// Find meta information about the tagged member variable
Collection<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> fieldElements =
findFieldAnnotationMetadata(beanClass);
// Find meta information about the tagged member method
Collection<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> methodElements =
findAnnotatedMethodMetadata(beanClass);
return new AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}
Copy the code
AnnotationInjectedBeanPostProcessor#findFieldAnnotationMetadata
/**
* Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link A} fields
*
* @param beanClass The {@link Class} of Bean
* @return non-null {@link List}
*/
private List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(finalClass<? > beanClass) {final List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> elements =
new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement>();
ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
// Iterate over each member variable, specify the annotation type (@reference) with getAnnotationType(), and store the result into the collection
A annotation = getAnnotation(field, getAnnotationType());
if(annotation ! =null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("@" + getAnnotationType().getName() + " is not supported on static fields: " + field);
}
return;
}
elements.add(newAnnotationInjectedBeanPostProcessor.AnnotatedFieldElement(field, annotation)); }}});return elements;
}
Copy the code
AnnotationInjectedBeanPostProcessor#findAnnotatedMethodMetadata
/**
* Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link A @A} methods
*
* @param beanClass The {@link Class} of Bean
* @return non-null {@link List}
*/
private List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(finalClass<? > beanClass) {final List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> elements =
new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement>();
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Method bridgedMethod = findBridgedMethod(method);
if(! isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;
}
// Iterate over each method to get the method referenced by @reference
A annotation = findAnnotation(bridgedMethod, getAnnotationType());
if(annotation ! =null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
// omit some exception check
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
elements.add(newAnnotationInjectedBeanPostProcessor.AnnotatedMethodElement(method, pd, annotation)); }}});return elements;
}
Copy the code
In general, dubbo uses AnnotationUtils#findAnnotation to get annotated @reference annotation member variables and methods.
How is dependency injection implemented
After obtaining the member meta information to be annotated by @Reference, the injection of attribute values is followed. Dubbo provides two injection classes, AnnotatedFieldElement and AnnotatedMethodElement, one for member variables and one for member methods, obviously.
AnnotatedFieldElement#inject
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
// Get the class object of the attributeClass<? > injectedType = field.getType();// Get the injected instance object
injectedBean = getInjectedObject(annotation, bean, beanName, injectedType, this);
// reflection allows access
ReflectionUtils.makeAccessible(field);
// Attribute assignment
field.set(bean, injectedBean);
}
Copy the code
AnnotatedMethodElement#inject
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
// Get the class object of the parameterClass<? > injectedType = pd.getPropertyType(); injectedBean = getInjectedObject(annotation, bean, beanName, injectedType,this);
ReflectionUtils.makeAccessible(method);
// Call the method
method.invoke(bean, injectedBean);
}
Copy the code
Get property values
Both methods need to get the injected instance object via getInjectedObject(), which first gets the result from the cache, and if not, creates the object by calling doGetInjectBean()
ReferenceAnnotationBeanPostProcessor#doGetInjectedBean
/** * this method is a template method that gets an object of the specified injection type **@param reference
* @param bean Current bean that will be injected
* @param beanName Current bean name that will be injected
* @param injectedType the type of injected-object
* @param injectedElement {@link InjectionMetadata.InjectedElement}
* @return
* @throws Exception
*/
@Override
protected Object doGetInjectedBean(Reference reference, Object bean, String beanName, Class
injectedType, InjectionMetadata.InjectedElement injectedElement) throws Exception {
// Build the ReferenceBean name
String referencedBeanName = buildReferencedBeanName(reference, injectedType);
// Build the ReferenceBean object
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName, reference, injectedType, getClassLoader());
// Put it in the cache
cacheInjectedReferenceBean(referenceBean, injectedElement);
// Create a proxy object
Object proxy = buildProxy(referencedBeanName, referenceBean, injectedType);
return proxy;
}
Copy the code
The doGetInjectedBean method builds the ReferenceBean object and then returns a proxy object through the JDK dynamic proxy.
Before building a proxy object, need to first create a ReferenceBeanInvocationHandler, then ReferenceBean# handler will call the get method, this method is the core of service subscription, next article will explain in detail. Once you have the InvocationHandler object, the proxy object is returned via the JDK dynamic proxy.
The source code is as follows:
// ReferenceAnnotationBeanPostProcessor#buildProxy
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;
}
// ReferenceAnnotationBeanPostProcessor#buildInvocationHandler
private InvocationHandler buildInvocationHandler(String referencedBeanName, ReferenceBean referenceBean) {
// Get it from cache first
ReferenceBeanInvocationHandler handler = localReferenceBeanInvocationHandlerCache.get(referencedBeanName);
if (handler == null) {
handler = new ReferenceBeanInvocationHandler(referenceBean);
}
if (applicationContext.containsBean(referencedBeanName)) { // Is local @Service Bean or not ?
// ReferenceBeanInvocationHandler's initialization has to wait for current local @Service Bean has been exported.
// Local services are cached first and then initialized when exposed
localReferenceBeanInvocationHandlerCache.put(referencedBeanName, handler);
} else {
// Remote Reference Bean should initialize immediately
/ / remote service need initialization, immediately call ReferenceBeanInvocationHandler init method
handler.init();
}
return handler;
}
Copy the code
ReferenceBeanInvocationHandler source as follows
private static class ReferenceBeanInvocationHandler implements InvocationHandler {
private final ReferenceBean referenceBean;
private Object bean;
private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) {
this.referenceBean = referenceBean;
}
// When a proxy object executes a target method, it is intercepted by invoke
@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()
// issue: https://github.com/apache/incubator-dubbo/issues/3429
init();
}
result = method.invoke(bean, args);
} catch (InvocationTargetException e) {
// re-throws the actual Exception.
throw e.getTargetException();
}
return result;
}
private void init(a) {
// Call the ReferenceBean's get method to enter the actual service subscription process
this.bean = referenceBean.get(); }}Copy the code