preface
@configuration @component @bean @import and so on. I believe you will often use these annotations when using Spring. I wonder how these annotations are handled in Spring. Today we are going to explore the secret of the spring annotations – ConfigurationClassPostProcessor
The class diagram
Check ConfigurationClassPostProcessor class diagram, can clearly see it realized BeanDefinitionRegistryPostProcessor and PriorityOrdered respectively, if you have read my previous article, Can know this class execution time, here is the class code execution, entrusted to PostProcessorRegistrationDelegate execution,
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);
for (String ppName : postProcessorNames) {
/ / if the class is implemented PriorityOrdered and implements BeanDefinitionRegistryPostProcessor into temporary list
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// Execute here
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
Copy the code
// Execute code is also very simple, just a for loop
private static void invokeBeanDefinitionRegistryPostProcessors( Collection
postProcessors, BeanDefinitionRegistry registry) {
for(BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); }}Copy the code
Registration time
So the question comes, ConfigurationClassPostProcessor when is registered to the IOC container?
/ / AnnotationConfigApplicationContext constructor will initialize AnnotatedBeanDefinitionReader
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
Copy the code
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
this.registry = registry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
/ / AnnotatedBeanDefinitionReader registered in advance support component annotation
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
Copy the code
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
/ / get DefaultListableBeanFactory, this method is only a type conversion
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if(beanFactory ! =null) {
if(! (beanFactory.getDependencyComparator()instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
if(! (beanFactory.getAutowireCandidateResolver()instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
/ / register ConfigurationClassPostProcessor here
if(! registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
/ / registered AutowiredAnnotationBeanPostProcessor, processing the @autowired, bean with the properties of the injection
if(! registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
/ / register CommonAnnotationBeanPostProcessor, with some public comments, such as @ Resource, @ PostConstruct, @ PreDestroy
if(jsr250Present && ! registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
// The class will be registered if the jar package related to jPA is imported
if(jpaPresent && ! registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition();
try {
def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
AnnotationConfigUtils.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
}
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
}
/ / registered EventListenerMethodProcessor, handle @ EventListener
if(! registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
if(! registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}
return beanDefs;
}
Copy the code
The source code parsing
postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor BeanDefinitionRegistryPostProcessor interface is achieved, then we can simply find entrance to perform:
postProcessBeanDefinitionRegistry
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
// The above are some checks, directly look at the core code
processConfigBeanDefinitions(registry);
}
Copy the code
Before we look at the following source code, let’s take a look at the difference between the configuration class Full mode and Lite mode: Full mode configuration classes are proxied by Cglib, lite mode configuration classes are not proxied by Cglib, so @bean methods cannot be declared private and final in full mode.
Let’s take a test to see the difference
@Configuration // Use @configuration to distinguish between the full Configuration class and lite Configuration class
public class FullConfig {
@Bean
public String test(a) {
System.out.println(user().hashCode());
System.out.println(user().hashCode());
return "test";
}
@Bean
public User user(a) {
return newUser(); }}Copy the code
@Component
public class LiteConfig {
@Bean
public String test(a) {
System.out.println(user().hashCode());
System.out.println(user().hashCode());
return "test";
}
@Bean
public User user(a) {
return newUser(); }}Copy the code
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(LiteConfig.class); context.getBean(User.class); context.close(); }}Copy the code
Information printed in Lite mode
Information printed in full mode
Summary: In Lite mode, multiple calls to User () generate multiple objects, but not in Full mode
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// To save the full configuration class
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// Retrieve all names registered with beanDefinition in the current Registry
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass
// BeanDefinition is not empty, which means it has been set before
if(beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) ! =null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: "+ beanDef); }}// Check whether it is a configuration class and set the BeanDefinition property to Lite or full
// Set the lite and full property values for BeanDefinition here for later use
// See below for a detailed explanation of this method
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// No Configuration class to be processed (@Configuration) is found
if (configCandidates.isEmpty()) {
return;
}
// Sort by @order
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
// Check whether a naming policy is set
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if(generator ! =null) {
// Set the naming policy
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator; }}}// Set the environment variable, which was initialized earlier
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// Store all configuration classes
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// Store the parsed configuration classes
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
// Note: here is a big do... While loop, which exits after all configuration classes have been parsed
do {
// Delegate to ConfigurationClassParser to resolve these configuration classes
parser.parse(candidates);
// Check whether the scanned beanDefinition is valid
// 1. Whether the configuration class can be overridden if proxyBeanMethods = true (non-final, need to generate cglib proxy class)
// whether the method modified by 2.@Bean can be overridden (non-final, cglib proxy class generated)
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
/ / initialize a ConfigurationClassBeanDefinitionReader
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
Delegate the encapsulated ConfigurationClass object to BeanDefinitionReader
// Resolve ConfigurationClass to BeanDefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if(! oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName);// The parsed BeanDefinition is checked again to see if it is a Full or lite configuration class
// For example, a class imports a configuration class
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && ! alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(newBeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; }}while(! candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if(sbr ! =null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); }}Copy the code
Determine a fully configured class or a semi-configured class
public static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null|| beanDef.getFactoryMethodName() ! =null) {
return false;
}
AnnotationMetadata metadata;
// Classes that are not pre-registered with Spring will do this if
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
// Return false filters spring pre-registered classes
else if (beanDef instanceofAbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { Class<? > beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
try {
// the ASM technology gets information about this class
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
// There are two judgments here
// 1. The current class has the @Configuration annotation
// 2. In annotations, the proxyBeanMethods attribute is true
// Set it to the full configuration class
if(config ! =null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// Classes with @Configuration and true for proxyBeanMethods are evaluated
// There are two kinds of logic
// 1. Contains @configuration and proxyBeanMethods is false
// 2. Contains @bean, @Component, @ComponentScan, @import, @importResource annotations
// The above two types are set to Lite configuration classes
else if(config ! =null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
The Lite or full configuration class sets the order priority
Integer order = getOrder(metadata);
if(order ! =null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
Copy the code
// candidateIndicators Static constant contains four elements when initialized
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// This class is an interface that returns false directly
if (metadata.isInterface()) {
return false;
}
// candidateIndicators is a static constant that, when initialized, contains four elements
// are @bean, @Component, @ComponentScan, @import, @importResource respectively
// The class is a configuration class as long as one of the four annotations is added to it
// The configurationClass attribute in BeanDefinition is Lite
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true; }}// Look for methods annotated with @bean
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]." + ex);
}
return false; }}Copy the code
ConfigurationClass
What are the annotations in a configuration class, and how are they described? Spring uses ConfigurationClass to abstract a ConfigurationClass
final class ConfigurationClass {
// Configure the annotation information for the class
private final AnnotationMetadata metadata;
private final Resource resource;
@Nullable
private String beanName;
// Which configuration class is importing the current class
private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
// All the @bean-annotated methods of the current configuration class
private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
// @importResource related information, this is ignored
private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
new LinkedHashMap<>();
/ / @ Import annotations on the configuration class imported classes, if is to implement the @ ImportBeanDefinitionRegister interface, will be wrapped in here
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
new LinkedHashMap<>();
final Set<String> skippedBeanMethods = new HashSet<>();
}
Copy the code
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// Whether the configuration class has been resolved, such as the current class and imported by other classes
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if(existingClass ! =null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
// A configuration class is imported by multiple classes
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals); }}// sourceClass: encapsulates the metadata of the configuration class, such as what annotations and methods does the configuration class have
/ / the configClass here just instantiate a good object, was an empty shell, doProcessConfigurationClass configuration class for parsing, and filling
// configClass
SourceClass sourceClass = asSourceClass(configClass, filter);
// Do... The while loop deals with cases where a class has a parent, which continues to parse the parent
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while(sourceClass ! =null);
// Put the processed classes into configurationClasses
this.configurationClasses.put(configClass, configClass);
}
Copy the code
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// If the configuration class is decorated with @Component, deal with the inner class first
processMemberClasses(configClass, sourceClass);
}
// Handle @propertysource and @propertysources annotations
// Converts the file to an PropertySource object, which is then stored in the Environment object
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment"); }}// Handle @ComponentScan and @ComponentScans annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if(! componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// Process the scan, not the configuration of the class
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
//
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// Iterate over all the scanned classesparse(bdCand.getBeanClassName(), holder.getBeanName()); }}}}// Handle the @import annotation
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Handle the @importResource annotation
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if(importResource ! =null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); }}// Handle @bean annotations
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Since Java8 supports default methods defined in interfaces, the default methods of interfaces are handled here
processInterfaces(configClass, sourceClass);
// If the class has a parent, return the parent with a do... The while loop will run through this method again
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if(superclass ! =null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
returnsourceClass.getSuperClass(); }}// If there is no parent class, return null
return null;
}
Copy the code
Above is ConfigurationClassPostProcessor roughly the execution process, but the details of each annotation processing, half a reader should or half would know solution, below added
@Component
Take a look at some code to get a sense of how this annotation is used
@Component
public class ComponentConfig {
// An inner class
class InnerClass {
@Bean
public User user(a) {
System.out.println("component test");
return newUser(); }}}Copy the code
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate
filter)
throws IOException {
// Get all inner classes
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
// If inner class is included
if(! memberClasses.isEmpty()) { List<SourceClass> candidates =new ArrayList<>(memberClasses.size());
// Loop through the inner class
for (SourceClass memberClass : memberClasses) {
// There are two judgments here
// 1. Is the inner class a configuration class
// 2. The name of the inner class is inconsistent with that of the current class.
// Add the configuration class to the list
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
// Sort the classes to be configured
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
// Check if there is a looped import
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// Handle inner class, here is a recursion, again go through the process of parsing (see above)
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
this.importStack.pop();
}
}
}
}
}
Copy the code
@ComponentScan
Take a look at some code to get a sense of how this annotation is used
@ComponentScan(value = "com.example")
public class ComponentScanConfig {}Copy the code
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// Create a scanner
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
// @ComponentScan annotations can configure naming policies (nameGenerator)
// The default is true. If false, the naming policy uses the one you configured on the configuration class
. / / BeanUtils instantiateClass here will be initialized to command strategy class
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ......return scanner.doScan(StringUtils.toStringArray(basePackages));
}
Copy the code
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// Scan to find the class you want to load
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// Annotated classes will go through the if
if (candidate instanceof AnnotatedBeanDefinition) {
// Handle some common annotations, such as @lazy and @primary
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// Check whether the beanDefinition is registered. If so, it will not be registered again
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
/ / register beanDefinition
registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
}
Copy the code
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// Convert the package name to a path
// The path here refers to the path after compiling to bytecode
Com.example --> classpath*:com/example/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader describes the class information using ASM techniques
// Why use ASM to parse classes instead of class.forname reflection?
// Since using ASM is a non-invasive way, if you use reflection, you need to load the class file into the JVM first.
// Some of the class's initialization code (static) is executed, which is not what a framework should do
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// Remove classes that don't need to be loaded, such as excludeFilters, @condition annotations
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: "+ resource); }}}else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: "+ resource); }}}catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: "+ resource, ex); }}else {
if (traceEnabled) {
logger.trace("Ignored because not readable: "+ resource); }}}}catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
Copy the code
@Import
Take a look at some code to get a sense of how this annotation is used
public class ImportSelectorConfig implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String[] str = {"com.example.springdemo.bean.User"};
System.out.println("ImportSelectorConfig");
returnstr; }}Copy the code
public class ImportBeanDefinitionRegistrarConfig implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("ImportBeanDefinitionRegistrarConfig");
ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry); }}Copy the code
@Data
@Builder
public class Staff {
public Staff(a) {
System.out.println("staff"); }}Copy the code
@Configuration
@Import({Staff.class, ImportSelectorConfig.class, ImportBeanDefinitionRegistrarConfig.class})
public class Config {}Copy the code
Summary: The import annotation can import three types of classes
- ImportSelector type
- ImportBeanDefinitionRegistrar type
- Ordinary class
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// import annotations do not import any classes
if (importCandidates.isEmpty()) {
return;
}
// Check to see if there is a loop import
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// Import classes are handled here. There are three types of import classes
/ / 1. ImportSelector type
/ / 2. ImportBeanDefinitionRegistrar type
// 3
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine importsClass<? > candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if(selectorFilter ! =null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// There is a recursion for processImports. Why do I recurse only if the import class is an ImportSelector?
/ / ImportBeanDefinitionRegistrar and ordinary class can't?
/ / because ImportSelector might import multiple classes, these classes may have ImportSelector, ImportBeanDefinitionRegistrar
// Or a normal class that needs to be processed again
// The ImportSelector class itself is meaningless to Spring; its purpose is to import the class
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); }}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
/ / import classes are ImportBeanDefinitionRegistrar type
// It will be provisioned for ConfigurationClassClass<? > candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
/ / import the imported class is neither ImportBeanDefinitionRegistrar, nor ImportSelector
// Treat it as a configuration class, and then go through the configuration class process
this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); }}}catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop(); }}}Copy the code
loadBeanDefinitions
private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
// If the class is added to the container via @import or ImportSelector, this if is followed
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// If the class contains @bean methods, go if
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// Handle the @importResource annotation
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
/ / if the class of @ Import annotations, and Import the class implements the ImportBeanDefinitionRegistrar
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
Copy the code
@Bean
Take a look at some code to get a sense of how this annotation is used
@Configuration
public class BeanConfig {
@Bean
public User user(a) {
System.out.println("user");
return new User();
}
@Bean
public static Staff staff(a) {
System.out.println("staff");
return newStaff(); }}Copy the code
The main difference between @beans (static) and @beans (static) is that they are initialized at the same time
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {· · · · · · · · · ·// If the @bean method is static, set the beanClass to be called when the Bean is initialized
if (metadata.isStatic()) {
// static @Bean method
if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
}
else {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
}
/ / set FactoryMethodName
beanDef.setUniqueFactoryMethodName(methodName);
}
else {
// If the @bean method is non-static, set factoryBeanName to be called when the Bean is initialized
beanDef.setFactoryBeanName(configClass.getBeanName());
/ / set FactoryMethodNamebeanDef.setUniqueFactoryMethodName(methodName); } · · · · · · · · · ·/ / register beanDefinition
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
Copy the code
conclusion
This article is going to look boring, and I wanted to draw a graph to help me understand it, but the whole method has a lot of recursion, so it’s hard to draw a graph, so it’s just like this