What is the role of MergedBeanDefinitionPostProcessor in Spring?

What is the role of MergedBeanDefinitionPostProcessor in Spring?

  • introduction
  • call timing
  • Several ways to load bean definitions
  • postProcessMergedBeanDefinition interface function
  • summary

Introduction

MergedBeanDefinitionPostProcessor is a Bean post-processor that you may pay less attention to, and it only provides a bean lifecycle callback interface:

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {<!-- -->
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
}

Although this bean life cycle callback interface may not play a key role, understanding the role of this interface will still play an important role in our understanding of the entire Bean initialization process.

So this article will briefly talk about the significance of this interface.

When to call

Let’s first take a look at the timing of calling this interface:

postProcessMergedBeanDefinition callback interface is called after MergeBeanDefintion and instantiation, the purpose is to post-process the merged BeanDefintion, then What logic does configuration processing specifically contain?

Several ways to load bean definitions

SpringBoot provides four ways to load bean definitions, as follows:

For beans loaded in different ways, they will be encapsulated into different types of BeanDefintion:

  • XML Definition Bean: GenericBeanDefinition
  • @Component and derived annotations define Bean: ScannedGenericBeanDefinition
  • Import beans with the help of @Import: AnnotatedGenericBeanDefinition
  • The method defined by @Bean: ConfigurationClassBeanDefinition private static class (inner class of ConfigurationClassBeanDefinitionReader)

For more information about BeanDefinition, please refer to: SpringIOC’s BeanDefinition related type relationship

For bean definitions imported in different ways, if there is a repeated definition of the same bean, it will judge whether to allow the overriding of the bean definition according to whether the allowBeanDefinitionOverriding property is set to true. If not, then an exception is thrown.

Before the Bean is instantiated, the BeanDefinition type will be normalized, that is, mergeBeanFintion , which will be uniformly converted to RootBeanFintion for subsequent processing. Of course, the merge here refers more to the merging of parent-child Bean definitions.

postProcessMergedBeanDefinition interface function

We can declare the definition of Bean in the above several ways, and perform runtime dependency injection through annotations such as @Autowired in the specific Bean class, then there will be a problem here:

  • When we declare the bean definition through the xml configuration file, we can also declare the dependency injection point through the xml configuration. If the dependency injection point declared by the xml configuration and the dependency injection point declared by the annotation overlap at this time, then whose What about higher priority?

In order to deal with this problem, Spring provides the MergedBeanDefinitionPostProcessor, a bean post-processor, which is responsible for dealing with the overlap of dependency injection points of xml configuration and dependency injection points of annotation configuration.

Here, take the AutowiredAnnotationBeanPostProcessor that handles @Autowired and @Value annotations as an example to see what its postProcessMergedBeanDefinition method does:

 @Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {<!-- -->
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata. checkConfigMembers(beanDefinition);
}

The findAutowiringMetadata method is responsible for finding the dependency injection points declared using @Autowired and @Value annotations on the fields and methods of the current bean, and encapsulating an InjectElement for each dependency injection point, and then creating an InjectionMetadata for the current bean, responsible for managing all InjectElements on the current bean :

 private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs){<!-- -->
// InjectionMetadata will be cached -- key is beanName
String cacheKey = (StringUtils. hasLength(beanName) ? beanName : clazz. getName());
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
//Double lock mechanism to ensure singleton
if (InjectionMetadata. needsRefresh(metadata, clazz)) {<!-- -->
synchronized (this. injectionMetadataCache) {<!-- -->
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata. needsRefresh(metadata, clazz)) {<!-- -->
if (metadata != null) {<!-- -->
metadata. clear(pvs);
}
//Build InjectionMetadata for the current bean
metadata = buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
   
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {<!-- -->
List<InjectionMetadata. InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {<!-- -->
final List<InjectionMetadata. InjectedElement> currElements = new ArrayList<>();
//Traverse all attributes on the current class to find attributes with relevant annotations
ReflectionUtils.doWithLocalFields(targetClass, field -> {<!-- -->
AnnotationAttributes ann = findAutowiredAnnotation(field);
//Exclude injection of static properties
if (ann != null) {<!-- -->
if (Modifier. isStatic(field. getModifiers())) {<!-- -->
if (logger.isWarnEnabled()) {<!-- -->
logger.warn("Autowired annotation is not supported on static fields: " + field);
}
return;
}
/ / Take the required attribute from the annotation--indicate whether the injection must be successful
boolean required = determineRequiredStatus(ann);
//Encapsulate as AutowiredFieldElement and return
currElements.add(new AutowiredFieldElement(field, required));
}
});
            // Traversing all the methods of the current bean, looking for methods with relevant annotations, and the methods are not static, encapsulated as AutowiredMethodElement and returned
ReflectionUtils.doWithLocalMethods(targetClass, method -> {<!-- -->
...
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null & amp; & amp; method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {<!-- -->
if (Modifier. isStatic(method. getModifiers())) {<!-- -->
...
return;
}
...
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils. findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements. addAll(0, currElements);
//Inject dependency injection and process related dependency injection points marked on the current parent class
targetClass = targetClass. getSuperclass();
}
while (targetClass != null & amp; & amp; targetClass != Object. class);
        //Create an InjectionMetadata return, InjectionMetadata manages all dependency injection points in the current bean
return new InjectionMetadata(clazz, elements);
}
}

metadata.checkConfigMembers(beanDefinition); The method considers that there may be multiple annotations (such as @Autowired, @Resource) marked on the same property at the same time, so it is necessary to avoid repeated processing question.

In Spring, multiple annotations can be marked on the same property at the same time to specify different dependency injection methods or configuration information. However, this can cause the same property to be processed repeatedly when dealing with dependency injection, causing errors or inconsistent behavior.

In order to avoid repeated processing, the checkConfigMembers() method checks the member elements in the configuration class, and marks the processed members as externally managed configuration members through the registerExternallyManagedConfigMember() method of RootBeanDefinition. In this way, during the subsequent processing of the Spring container, if the same member is marked multiple times, the Spring container will ignore the repeated processing and maintain consistency.

For example, suppose there is a property myDependency in a configuration class annotated with both @Autowired and @Resource annotations:

@Autowired
@Resource
private MyDependency myDependency;

When the checkConfigMembers() method is called, it checks whether the myDependency attribute has been marked as an externally managed configuration member. If not marked, it will register it as an externally managed configuration member. In this way, during the subsequent processing of the Spring container, if repeated dependency injection tags are encountered, for example, another place uses the @Resource annotation to mark myDependency, the Spring container will ignore the repeated processing and maintain consistency.

Summary: One of the functions of the checkConfigMembers() method is to consider that there may be multiple annotations marked on the same attribute at the same time, so as to avoid repeated processing. By marking processed members as externally managed configuration members, it ensures that the Spring container does not process the same property repeatedly when processing dependency injection.

The specific source code of this method is as follows:

 public void checkConfigMembers(RootBeanDefinition beanDefinition) {<!-- -->
Set<InjectedElement> checkedElements = new LinkedHashSet<>(this. injectedElements. size());
for (InjectedElement element : this. injectedElements) {<!-- -->
Member member = element. getMember();
if (!beanDefinition.isExternallyManagedConfigMember(member)) {<!-- -->
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements. add(element);
}
}
this. checkedElements = checkedElements;
}

Summary

The MergedBeanDefinitionPostProcessor post processor plays two roles in the actual application of Spring:

  • Initialize the InjectionMetadata cache of the current bean
  • Filter out already processed dependency injection points

Of course, this is just the application given in Spring, and we can also play more tricks in this interface.