Phenomenon of the problem

In our project has a second party within the company, there is a class: MvcInterceptorAutoConfiguration, set a inside a Bean accessContextResolver. Generating this Bean requires automatic injection of another Bean: accessContextService. The code is as follows:

public class MvcInterceptorAutoConfiguration implements WebMvcConfigurer.ApplicationContextAware {

	   @Bean
 	   public AccessContextResolver accessContextResolver(@Autowired AccessContextService accessContextService, @Autowired WebAuthConfig webAuthConfig) {
       	 return newDefaultAccessContextResolver(webAuthConfig, accessContextService); }}Copy the code

In our project, we have another class: ProxyCenter, which defines the accessContextService with @dubboReference. The following code

@Component
public class ProxyCenter {

    @dubboReference (timeout = 10000, check = false, version = "1.0.0")
    privateAccessContextService accessContextService; . }Copy the code

However, the following error was reported during the project startup process

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of method accessContextResolver in cn.xxx.xxx.xxx.xxx.config.MvcInterceptorAutoConfiguration required a bean of type 'cn.xxx.xxx.xxx.xxx.service.AccessContextService' that could not be found.


Action:

Consider defining a bean of type 'cn.xxx.xxx.xxx.xxx.service.AccessContextService' in your configuration.
Copy the code

The accessContextResolver Bean needs to be automatically injected into the accessContextService Bean. But the Spring container could not find the Bean, so it failed to start.

Problem analysis

Dubbo version: 2.7.0

Analysis methods

  • The essence of the problem is that @AutoWired cannot inject beans declared by @DubboReference. The most important thing is to figure out what @DubboReference and @AutoWired do and when they do it.

  • If only @autowired is used, the above situation will not occur, so we will first look at the implementation logic of @DubboReference to locate the direction of the problem.

@dubboReference implements logical analysis

Background knowledge

A little background: We know that Spring creates a Bean by instantiating the object, populating the property, and initializing the object. The property populating is implemented in the populateBean method (code below). Get all BeanPostProcessor in the Bean factory, if it is InstantiationAwareBeanPostProcessor type, then call postProcessPropertyValues method.

Note: InstantiationAwareBeanPostProcessor is an abstract class, it does not provide postProcessPropertyValues implementation, the implementation of all are in subclasses.

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {... Iterator var5 =this.getBeanPostProcessors().iterator();
 
     BeanPostProcessor bp = (BeanPostProcessor)var9.next();
     if (bp instanceof InstantiationAwareBeanPostProcessor) {
        InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor)bp;
        pvs = ibp.postProcessPropertyValues((PropertyValues)pvs, filteredPds, bw.getWrappedInstance(), beanName);
        if (pvs == null) {
         return; }}... }Copy the code

Here are InstantiationAwareBeanPostProcessorAdapter implementation class, here only lists the problems related to subclass with us

InstantiationAwareBeanPostProcessorAdapter

​ |

AutowiredAnnotationBeanPostProcessor (Spring provides properties/methods into implementation)

​ |

】 AbstractAnnotationBeanPostProcessor 【 com. Alibaba. Spring…

​ |

ReferenceAnnotationBeanPostProcessor 【 org. Apache. Dubbo……” (@dubboReference provided by Dubbo, @Reference implementation)

From the above source code and class inheritance relationships we may safely draw the conclusion: the spring to fill, will call ReferenceAnnotationBeanPostProcessor postProcessPropertyValues methods of this class. And ReferenceAnnotationBeanPostProcessor this class is to provide the rear of the Bean processors Dubbo, @ DubboReference, @ Reference is implemented in this method.

Source code analysis

After understanding the above background knowledge, we began to enter the @dubboReference source code analysis. Listed below are ReferenceAnnotationBeanPostProcessor for postProcessPropertyValues implementation.

Note that the Bean being created at this time is proxyCenter. As for why proxyCenter is the Bean, this is very simple. Because the accessContextService is a property of the ProxyCenter class in this case, the population of the accessContextService property occurs when the ProxyCenter Bean is created.

 public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
       
     / / object
     InjectionMetadata metadata = this.findInjectionMetadata(beanName, bean.getClass(), pvs);

        try {
            // Perform injection
            metadata.inject(bean, beanName, pvs);
            return pvs;
        } catch (BeanCreationException var7) {
            throw var7;
        } catch (Throwable var8) {
            throw new BeanCreationException(beanName, "Injection of @" + this.getAnnotationType().getSimpleName() + " dependencies is failed", var8); }}Copy the code

PostProcessPropertyValues method mainly did two things:

1. Find @dubboReference, @Reference modifier properties, and encapsulate the metadata information in InjectionMetadata.

2. Perform injection. Here to inject method invocation is the inject AbstractAnnotationBeanPostProcessor method, also is the implementation of the parent class method to inject.

Dubbo generated the proxy object, but did not put it in the Spring container, so it was not found in the automatic injection. So, we can look at inject first. (findInjectionMetadata is not a problem, so it will not be analyzed here.)

 protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Class<? > injectedType =this.resolveInjectedType(bean, this.field);
          
            // Generate a proxy object
            Object injectedObject = AbstractAnnotationBeanPostProcessor.this.getInjectedObject(this.attributes, bean, beanName, injectedType, this);
            
     		     // reflection, set the value of the property
             / / bean: proxyCenter
             / / this. Field: accessContextService
            ReflectionUtils.makeAccessible(this.field);
            this.field.set(bean, injectedObject);
        }
Copy the code

The Inject method basically generates a proxy object and then sets values for the properties of the current object. The generated proxy object accessContextResolver is set to the properties of the current Bean, the proxyCenter Bean.

Analysis: The inject method does not involve putting the proxy object accessContextResolver into the Spring container. The inject method does not involve putting the accessContextResolver into the Spring container. So we can keep going. GetInjectedObject The core logic of this method is in doGetInjectedBean, just with the caching operation added. So it’s not listed here.)

 protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class
        injectedType, InjectionMetadata.InjectedElement injectedElement) throws Exception {... ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);// Check whether the current Service is defined locally using @dubboService or @service
        boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);

        // If the service is local and has not been registered, pre-registration of the service will be triggered
        prepareReferenceBean(referencedBeanName, referenceBean, localServiceBean);

        // Putting the service information into the bean factory does not involve getting the actual service
           //1. Local exposed services
           //2. Services that need to be read from the registry
        registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);

        // Get the remote service
        return referenceBean.get();
    }
Copy the code

The doGetInjectedBean method is the core of the @DubBoReference implementation, with annotations for each step.

This method is used to register the current Bean to the Bean factory, so the answer we need is in this method. ReferenceBean is an object that encapsulates applicationContext, the interface’s proxy object: ref, etc. Ref is the generated proxy object, e.g. @dubboReference AService AService; Ref is the proxy object for aService. This object is wrapped as a ReferenceBean, so it is rude to think of the ReferenceBean as a service-specific reference.

private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
                                       AnnotationAttributes attributes,
                                       booleanlocalServiceBean, Class<? > interfaceClass) {

        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        String beanName = getReferenceBeanName(attributes, interfaceClass);

        // @service is local
        if (localServiceBean) {  
            
            // All information about the service is stored locally.
            AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref");
            // the name of the bean corresponding to @service
            String serviceBeanName = runtimeBeanReference.getBeanName();
            
            // Instead of creating a new bean, use the bean generated by @service to avoid bean duplication
            beanFactory.registerAlias(serviceBeanName, beanName);
            
        } else { 
            
            // @service is remote
            if(! beanFactory.containsBean(beanName)) {// Spring dynamically registers beansbeanFactory.registerSingleton(beanName, referenceBean); }}}Copy the code

The registerReferenceBean method basically registers the ReferenceBean with Spring. So far, we have found our answer: @DubboReference will put the generated proxy object into the Spring container, and this is triggered during the creation of the Bean corresponding to the @DubboReference modifier property. That is, as long as the Bean is not created, the @DubboReference modified property will not be placed in the Spring container.

The above steps are represented by a flow chart:

conclusion

This is the time when @DubboReference is triggered during Spring startup. That is, during the property filling phase of Spring Bean creation, if the property modified by @DubboReference is found, Rear ReferenceAnnotationBeanPostProcessor this Bean processor will create the service proxy object references, and then in the Spring container.

Q: What is the meaning of the passage? When Spring creates the proxyCenter Bean, it instantiates the accessContextService object and places it in the Spring container. However, the Bean was not found when the accessContextService was injected using @AutoWired. The most likely reason is that Spring injected the accessContextService with @AutoWired before creating the proxyCenter Bean.

We know that when @AutoWired is injected automatically, if the Bean does not exist, then the process of creating the Bean will be triggered. Let’s analyze the @AutoWired implementation logic and why the object here is null.

Autowired implements logical analysis

Note: Due to the complexity of the @AutoWired implementation logic, the codes listed below are related to this case, and other codes will be omitted accordingly.

Analysis methods

  • The @Autowired annotation can modify attributes, methods, input parameters, etc. The timing of processing of the object used by @Autowired is different. For example, when @Autowired modifies a property or method, it is processed when the property is filled. In this case, @AutoWired is handled when the Bean is instantiated.
@Bean
 public AccessContextResolver accessContextResolver(@Autowired AccessContextService accessContextService, @Autowired WebAuthConfig webAuthConfig) {
   return new DefaultAccessContextResolver(webAuthConfig, accessContextService);
}
Copy the code

Source code analysis

In the Bean instantiation process, one step is createArgumentArray, where one case is to create an auto-injection parameter: ConstructorResolver#resolveAutowiredArgument, this is the entrance of this case analysis, because the following many logic and this case is not relevant, this part of the code is not listed, you can see by yourself. We’ll start with the doResolveDependency method.

Note that beanName refers to the name of the Bean being created, not the Bean being automatically injected. This example refers to an accessContextResolver, not an accessContextService. Look at the code above.

@Nullable
	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
                 
            //31 Handle normal bean key: automatically injected bean name; Value: class object or concrete bean
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			if (matchingBeans.isEmpty()) {
                // If the bean name is not available, @autowire (required=true) will raise an exception
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
                // If @autowire (required=false) then null is returned
				return null;
			}

			String autowiredBeanName; // The name of the auto-injected bean
			Object instanceCandidate; // Automatically injected objects

            // If more than one object is found based on the bean name
			if (matchingBeans.size() > 1) {
                // @primary -> @priority -> Method name or field name match
				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
				if (autowiredBeanName == null) {
					if(isRequired(descriptor) || ! indicatesMultipleBeans(type)) {return descriptor.resolveNotUnique(type, matchingBeans);
					}
					else {
						return null;
					}
				}
				instanceCandidate = matchingBeans.get(autowiredBeanName);
			}
			else {
				// Only one bean is found according to type, so this is the object we want
				Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
				autowiredBeanName = entry.getKey();
				instanceCandidate = entry.getValue();
			}

			if(autowiredBeanNames ! =null) {
				autowiredBeanNames.add(autowiredBeanName);
			}
            
            // This is used to determine whether the returned bean is an already created bean or just a class. If the returned bean is a class, you need to execute the logic to create the bean and get the real bean object
            // Class is useless because we need an object for injection
			if (instanceCandidate instanceof Class) {
                // Execute the getBean() logic
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
			Object result = instanceCandidate;
			if (result instanceof NullBean) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				result = null;
			}
			if(! ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
			}
			returnresult; }}Copy the code

DoResolveDependency is the core method of dependency injection. It does several things:

1. Handle @value modified arguments

2. Handle multiplebeans, which are List, Map, Array, and Set modified objects. For example: @autowire private List aServiceList; This is the case.

3. Handle ordinary injection

31. First: Find all bean names by type and place them in the map. FindAutowireCandidates returns a map with the key: Bean name, value: created Bean, and class object.

MatchingBeans = matchingBeans = matchingBeans = matchingBeans = matchingBeans = matchingBeans One is to generate multiple objects from the same class, such as many data sources, and another is to have multiple implementations of an interface, injecting only the interface at the time of injection. @primary -> @priority

MatchingBeans size =1: this is the object we need.

34. Determine if this object is an instance of class, and if so, proceed with the Bean creation process

4. Finally return this object.

RaiseNoMatchingBeanFound if @autowire (require=true) is the same as @autowire (require=true). This method raises an exception. AccessContextService does not find the corresponding Bean information, so the error is reported. Let’s move on to the findAutowireCandidates implementation logic. (Although there are some scenarios where errors are reported, they are not in this case.)

protected Map<String, Object> findAutowireCandidates(
			@NullableString beanName, Class<? > requiredType, DependencyDescriptor descriptor) {

        // Find all bean names of this type according to requiredType
		String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
				this, requiredType, true, descriptor.isEager());
    
		Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
    
		// If the bean has not been instantiated, it will not be instantiated in advance.return result;
	}
Copy the code

The findAutowireCandidates method is the process of finding all the Bean names and objects by type.

2. There are two possible problems:

The name of the Bean was not found.

Find the corresponding Bean or class object by name.

The first step is not to look up the Bean name by type. Because when we just analyzed @dubboReference, there was a piece of code that dynamically registered the Bean and put the Bean name into the manualSingletonNames object during the registration. But the time to put it in is when you create the proxyCenter.

beanFactory.registerSingleton(beanName, referenceBean); Public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { ... if (! this.beanDefinitionMap.containsKey(beanName)) { this.manualSingletonNames.add(beanName); }Copy the code

Let’s focus on how Spring finds all the Bean names. The main logic is in the doGetBeanNamesForType method.

// includeNonSingletons: whether includeNonSingletons include non-simple interest
//allowEagerInit: handles factoryBean
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
		List<String> result = new ArrayList<>();

		// The definition of all beans in the loop bean factory
		for (String beanName : this.beanDefinitionNames) {
		
			if(! isAlias(beanName)) {try {
                    
					RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                    
                    // Beans are non-abstract
					if(! mbd.isAbstract() &&//(allowEagerInit ||(mbd.hasBeanClass() || ! mbd.isLazyInit() || isAllowEagerClassLoading()) &&//factoryBean related processing! requiresEagerInitForType(mbd.getFactoryBeanName()))) {// Determine if it is a factoryBean
                        boolean isFactoryBean = isFactoryBean(beanName, mbd);
						BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
                      
                        // Condition 1: Non-factoryBean, exists in DBD and is not lazy to load, or already exists in simple interest pool
                        // Condition two: contains non-simple interest, or the bean is simple interest
                        // Condition 3: Type is the same as the type of the current bean
						booleanmatchFound = (allowEagerInit || ! isFactoryBean ||(dbd ! =null&&! mbd.isLazyInit()) || containsSingleton(beanName)) && (includeNonSingletons ||(dbd ! =null ? mbd.isSingleton() : isSingleton(beanName))) &&
								isTypeMatch(beanName, type);
                        
                        / / processing factoryBean
						if(! matchFound && isFactoryBean) { beanName = FACTORY_BEAN_PREFIX + beanName; matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type); }// If a match is found, put it into the set and return it later
						if(matchFound) { result.add(beanName); }}}catch (CannotLoadBeanClassException ex) {
					// Exception handling
					onSuppressedException(ex);
				}
				catch (BeanDefinitionStoreException ex) {
					// Exception handlingonSuppressedException(ex); }}}// Handle manually registered bean registerSingleton(beanName,Object)
      In addition to scanning for annotations such as @service and @compoment, Spring can also register them manually in code
      for (String beanName : this.manualSingletonNames) {
			....
		}

		return StringUtils.toStringArray(result);
	}
Copy the code

DoGetBeanNamesForType finds all matching Bean names based on their type. Note: Regular beans and FactoryBeans are included here, and the matching beans are both automatically registered and manually registered. We can see that there are two objects looping around: beanDefinitionNames and manualSingletonNames

BeanDefinitionNames contains Bean information generated by Spring’s initialization scanning for @compoment, @service, etc. Our AccessContextService will definitely not be in this object because the AccessContextService is not a Bean defined by the Spring-defined Bean specification, ManualSingletonNames are put in when the registerSingleton is called.

conclusion

At this point, the cause of the problem is clear: Spring starts with @AutoWired processing, which injects a Bean of type AccessContextService. However, it is not a Bean defined by @Service, @compoment, etc. The Spring container does not have any definition information for AccessContextService beans. At this time, the object proxyCenter has not been instantiated, the property filling has not occurred, the proxy object of the AccessContextService class has not been injected into the Spring environment, so the AccessContextService type object cannot be obtained. Spring startup error reported.

solution

1. The core problem of this case is that the Bean is used before the Bean is created, but the Bean is not defined according to the Spring specification, so there is no way to create it without automatic injection finding it. So we just need to make sure we create the Bean first and inject the Bean later.

Based on this, the solution can be: Let the proxyCenter Bean be instantiated before the accessContextResolver, since the properties are populated at creation time, which triggers the instantiation of the AccessContextService remote service. But the creation of Spring beans is out of order. How do you get the two beans to be created in a certain order?

An @dependson annotation can be used in Spring to have one Bean created before another, but in our case, the accessContextResolver is in a binary package and @dependson is not practical. So we can define a BeanFactoryPostProcessor and then manually modify the BeanDefinition of the accessContextResolver to solve the problem. The code is as follows:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("accessContextResolver");
        beanDefinition.setDependsOn("proxyCenter"); }}Copy the code

Individual thinking

@DubboReference is not a spring-defined Bean, so it will not generate a BeanDefinition. That is, createBean is not active and can only be triggered during property injection, which leads to problems like this article. I think a better implementation would be to create all the @dubboReference objects before Spring instantiates any beans, and then use them when Spring creates the Bean.

The above problem Dubbo has been fixed in a later version (3.0.0), so our previous problem can also be fixed by upgrading Dubbo. As for Dubbo behind is how to solve this problem, here is not specific about the implementation of the modified logic, we have the rise of their own can look at the source.