This is the 20th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021″
preface
In the previous analysis we know:
- As long as it is @ the Import, so the final execution is necessarily ImportBeanDefinitionRegistrar, otherwise it will continue to recursion, down until it is this class.
Here we deal with the leftovers:
Class: [ImportBeanDefinitionRegistrar]
- How are subclasses implemented?
- Where is it processed?
- ConfigurationClass class, loadBeanDefinitionsForConfigurationClass where, when the called?
Subclass implementation and interface definition
Let’s start with the methods given in the interface:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}Copy the code
You can’t tell much from here, except that two methods are overloaded and the one above calls the one below, without using the bean name constructor above.
There are only two classes to rewrite, which are closely related to SpringData:
- AbstractRepositoryConfigurationSourceSupport
- RepositoryBeanDefinitionRegistrarSupport
- AbstractRepositoryConfigurationSourceSupport
This class is also not a concrete implementation class, but an abstract class, but it basically provides most of the functionality for storing data and integrating SpringData:
public abstract class AbstractRepositoryConfigurationSourceSupport implements ImportBeanDefinitionRegistrar.BeanFactoryAware.ResourceLoaderAware.EnvironmentAware {}Copy the code
So we’ll focus on the following subclass override of this method to see what happens.
Before looking at subclass method overrides, take a look at the comment for this method:
Register bean definitions as necessary based on the given annotation metadata of the importing @Configuration class. Note that BeanDefinitionRegistryPostProcessor types may not be registered here, due to lifecycle constraints related to @Configuration class processing. Copy the code
In simple terms, this method registers the bean definition as needed, based on the given annotation metadata imported into the @Configuration class.
Remember the beginning? Let’s take OpenFeign as an example.
FeignClientsRegisrar
One thing to note here is that this class is imported into the Spring container by @import in the EnableFeignClients annotation:
@Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { / /... } Copy the code
To get straight to the overriding method of the above subclass:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
Copy the code
Register the default configuration
registerDefaultConfiguration(metadata, registry);
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// The metadata interrupts refer to classes that are started and those that are collected as annotated by feignClient annotations
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if(defaultAttrs ! =null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration")); }}private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
Copy the code
The code here looks easy throughout:
- Configuration refers to the @enableFeignClients defaultConfiguration, if not configured.
- This is the process of building a BeanDefinition into the context based on the given metadata. The beanDefinition here corresponds to the corresponding configuration in the @EnableFeignClients annotation of the start class annotation. If it is not configured, this Configuration is useless (because it will be placed in the container, but the properties are empty).
Registered feign class
In this method, all interfaces in the package labeled ** @feignClient ** are initialized here.
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// This canner is a scan based on classpath
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// Here is the top-level directory of packages to scan
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
finalClass<? >[] clients = attrs ==null ? null: (Class<? >[]) attrs.get("clients");
// If you can't find clients in the enable annotation, use metadata to find the corresponding top-level directory name
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for(Class<? > clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter =new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll(\ \ "$".".");
returnclientClasses.contains(cleaned); }}; scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
// This is to find the interfaces corresponding to the feignClient according to the package of the top-level directory, and put the information of these interfaces into the container
// feignClient, the corresponding beanDefinition is derived from FeignClientFactoryBean
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// This method is converted and registered, and the interesting thing is that it also initializes the ConfigurationregisterFeignClient(registry, annotationMetadata, attributes); }}}}Copy the code
summary
You can see it here:
-
In effect, these importBdregistrArs receive parameters in the specified schema and register the corresponding concepts and information (such as feignClients above) as BeanDefinitions into the container.
This might involve scanning, factoryBeans, and so on.
-
Here are some points to explore further, including:
- Now that Feign’s beanDefinition is actually registered as a FeignClientFactoryBean, how is it invoked?
- At which step of bean instantiation is this BDRegistrar called?