This time, understand the core principles of springboot (automatic configuration, event driven, condition)

Don't talk at night 2022-02-13 07:26:00 阅读数:327

time understand core principles springboot

Preface

SpringBoot yes Spring Packaging , Automatic configuration makes SpringBoot It can be used out of the box , The starting cost is very low , But the cost of learning its implementation principle increases greatly , You need to be familiar with it first Spring principle . If it's not clear Spring Principle , You can check the previous articles of the blogger first , This article mainly analyzes SpringBoot Start of 、 Automatic configuration 、Condition、 Event driven principle .

Text

Start the principle

SpringBoot It's very easy to start , Because it has Tomcat, So you only need to start it in the following ways :

@SpringBootApplication(scanBasePackages = {
"cn.dark"})
public class SpringbootDemo {

public static void main(String[] args) {

// The first one is 
SpringApplication.run(SpringbootDemo .class, args);
// The second kind 
new SpringApplicationBuilder(SpringbootDemo .class)).run(args);
// The third kind of 
SpringApplication springApplication = new SpringApplication(SpringbootDemo.class);
springApplication.run();
}
}

You can see that the first is the simplest , It's also the most common way , Note that the class needs to be marked @SpringBootApplication annotation , This is the core implementation of automatic configuration , Analyze later , First look at it. SpringBoot What did the startup do ?
Before going down , You might as well guess ,run What needs to be done in the method ? contrast Spring Source code , We know ,Spring The startup of will create a ApplicationContext Application context object for , And call it refresh Method to start the container ,SpringBoot It's just Spring A shell of , It is certain that such an operation can not be avoided . On the other hand , I used to pass Spring Projects built , All need to be made into War Package release to Tomcat Talent , And now SpringBoot It's already built in Tomcat, Just make it Jar Just start the package , So in run The corresponding... Will certainly be created in the method Tomcat Object and start . The above is just our guess , Let's verify , Get into run Method :

 public ConfigurableApplicationContext run(String... args) {

// Tools for time statistics 
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// Acquisition realizes SpringApplicationRunListener Implementation class of interface , adopt SPI Mechanism loading 
// META-INF/spring.factories Class under file 
SpringApplicationRunListeners listeners = getRunListeners(args);
// First call SpringApplicationRunListener Of starting Method 
listeners.starting();
try {

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// Processing configuration data 
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// Print at startup banner
Banner printedBanner = printBanner(environment);
// Create context objects 
context = createApplicationContext();
// obtain SpringBootExceptionReporter The class of the interface , Exception report 
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] {
 ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// The core approach , start-up spring Containers 
refreshContext(context);
afterRefresh(context, applicationArguments);
// The statistics are over 
stopWatch.stop();
if (this.logStartupInfo) {

new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// call started
listeners.started(context);
// ApplicationRunner
// CommandLineRunner
// Get the implementation classes of these two interfaces , And call it run Method 
callRunners(context, applicationArguments);
}
catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {

// Last call running Method 
listeners.running(context);
}
catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

SpringBoot This is the way to start the process , First look at getRunListeners Method , The way is to get all SpringApplicationRunListener Implementation class , These classes are used for SpringBoot Event release , About event driven analysis later , Here we mainly look at the implementation principle of this method :

 private SpringApplicationRunListeners getRunListeners(String[] args) {

Class<?>[] types = new Class<?>[] {
 SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {

ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// Reflection instantiation after loading 
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {

String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {

return result;
}
try {

Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {

URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {

String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {

result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
}

Step by step, we can see that the final result is through SPI The mechanism starts from... According to the interface type META-INF/spring.factories Load the corresponding implementation class in the file and instantiate ,SpringBoot The automatic configuration of is also realized in this way . Why do we do this ? Can't you scan through annotations ? Of course not , These classes are in third-party jar In bag , Annotation scanning implementation is cumbersome , Of course you can too @Import Annotation import , However, this method is not suitable for the case where there are too many extension classes , So here we use SPI The advantages are obvious .
go back to run In the method , You can see the call createApplicationContext Method , See the name and know the meaning , This is to create an application context object :

 public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {

Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {

try {

switch (this.webApplicationType) {

case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {

throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

Note that a new context object is instantiated through reflection AnnotationConfigServletWebServerApplicationContext, This is SpringBoot Extended , Look at how it's constructed :

 public AnnotationConfigServletWebServerApplicationContext() {

this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

If you have seen Spring Implementation principle of annotation driven , These two objects are certainly not strange , An implementation that supports annotation parsing , The other is for scanning packages .
The context is created , The next natural step is to call refresh Method to start the container :


private void refreshContext(ConfigurableApplicationContext context) {

refresh(context);
if (this.registerShutdownHook) {

try {

context.registerShutdownHook();
}
catch (AccessControlException ex) {

// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {

Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}

First, it will be called into its parent class ServletWebServerApplicationContext

 public final void refresh() throws BeansException, IllegalStateException {

try {

super.refresh();
}
catch (RuntimeException ex) {

stopAndReleaseWebServer();
throw ex;
}
}

You can see that it is directly delegated to the parent class :

 public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {

// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {

if (logger.isWarnEnabled()) {

logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {

// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

This method is not strange , It's been analyzed before , No more details here , thus SpringBoot The container of , however Tomcat Where is the startup ?run Nor do you see in the method . actually Tomcat The start of is also in refresh In the process , One of the steps in this method is to call onRefresh Method , stay Spring This is an unimplemented template method , and SpringBoot Through this method Tomcat Start of :

 protected void onRefresh() {

super.onRefresh();
try {

createWebServer();
}
catch (Throwable ex) {

throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {

WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {

ServletWebServerFactory factory = getWebServerFactory();
// Mainly look at this method 
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {

try {

getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {

throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}

Here you get... First TomcatServletWebServerFactory object , Use this object to create and start Tomcat:

 public WebServer getWebServer(ServletContextInitializer... initializers) {

if (this.disableMBeanRegistry) {

Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {

tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}

Each of the above steps can be compared Tomcat Configuration file for , Note that only... Is supported by default http agreement :

 Connector connector = new Connector(this.protocol);
private String protocol = DEFAULT_PROTOCOL;
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";

If you want to extend it, you can do it for additionalTomcatConnectors Property settings , Note that this attribute does not correspond to setter Method , Only addAdditionalTomcatConnectors Method , That is, we can only achieve BeanFactoryPostProcessor Interface postProcessBeanFactory Method , Not through BeanDefinitionRegistryPostProcessor Of postProcessBeanDefinitionRegistry Method , Because the former can pass in BeanFactory Object gets... In advance TomcatServletWebServerFactory Object call addAdditionalTomcatConnectors that will do ; The latter can only get BeanDefinition object , The object can only pass through setter Method settings .

Event driven

Spring The event mechanism is provided originally , And in the SpringBoot It is extended in , By publishing and subscribing to events, different operations are carried out at different stages of the container's whole life cycle . Let's take a look first SpringBoot What events will be published by default during startup and shutdown , Use the following code :

@SpringBootApplication
public class SpringEventDemo {

public static void main(String[] args) {

new SpringApplicationBuilder(SpringEventDemo.class)
.listeners(event -> {

System.err.println(" Event received :" + event.getClass().getSimpleName());
})
.run()
.close();
}
}

This code will print all event names on the console , The order is as follows :

  • ApplicationStartingEvent: Container start up
  • ApplicationEnvironmentPreparedEvent: The environment is ready
  • ApplicationContextInitializedEvent: Context initialization complete
  • ApplicationPreparedEvent: The context is ready
  • ContextRefreshedEvent: Context refresh finished
  • ServletWebServerInitializedEvent:webServer Initialization complete
  • ApplicationStartedEvent: Container start up complete
  • ApplicationReadyEvent: Container ready
  • ContextClosedEvent: Container closure

The above is normal startup and shutdown , If an exception occurs, there is a release ApplicationFailedEvent event . Events are published throughout the container's startup and shutdown cycle , We have just seen that the event release object is through SPI Loaded SpringApplicationRunListener Implementation class EventPublishingRunListener, Similarly, the event listener is also in spring.factories Configured in the file , The following listeners are implemented by default :

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

You can see that there are... For file encoding (FileEncodingApplicationListener), There is a log loading framework (LoggingApplicationListener), And loading configuration (ConfigFileApplicationListener) Wait, a series of listeners ,SpringBoot That is, the necessary configurations and components are loaded into the container through this series of listeners , No more detailed analysis here , Interested readers can achieve onApplicationEvent Method to see which event each listener listens to , Of course, event publishing and monitoring can be extended by ourselves .

Auto configuration principle

SpringBoot The core is automatic configuration , Why can it be used out of the box , We don't need to use it manually anymore @EnableXXX Wait for comments to open ? The answer to all this is @SpringBootApplication In the annotations :

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
 @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

There are three important comments here :@SpringBootConfiguration、@EnableAutoConfiguration、@[email protected] There's no need to say ,@SpringBootConfiguration Equate to @Configuration, and @EnableAutoConfiguration Is to turn on automatic configuration :

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

@AutoConfigurationPackage The function of the annotation is to take the package where the class marked by the annotation is located as an automatically configured package , Just look at it , It mainly depends on AutoConfigurationImportSelector, This is the core class for automatic configuration , Note that this class is implemented DeferredImportSelector Interface .
In this class, there is one selectImports Method . This method is in my previous article This time I understand Spring Parsing of transaction annotations There have also been analyses of , Only the implementation classes are different , It will also be ConfigurationClassPostProcessor Class call , Let's first look at what this method does :

 public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!isEnabled(annotationMetadata)) {

return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// Get all auto configuration classes 
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {

if (!isEnabled(annotationMetadata)) {

return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// SPI obtain EnableAutoConfiguration by key All implementation classes of 
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// Filter out some autoconfiguration classes 
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
// Wrapped as an automatically configured entity class 
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

// SPI obtain EnableAutoConfiguration by key All implementation classes of 
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

The tracking source code can finally be seen from META-INF/spring.factories Get all the... From the file EnableAutoConfiguration Corresponding value ( stay spring-boot-autoconfigure in ) And instantiate it through reflection , Filter and package into AutoConfigurationEntry Object returns .
After seeing this, you should feel that the implementation of automatic configuration is through this selectImports Method , But in fact, this method is usually not called to , Instead, it calls the inner class of the class AutoConfigurationGroup Of process and selectImports Method , The former is also through getAutoConfigurationEntry Get all the auto configuration classes , The latter is filtered, sorted and wrapped back .
Let's analyze ConfigurationClassPostProcessor How to call here , Go straight into processConfigBeanDefinitions Method :

 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {

List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {

BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {

if (logger.isDebugEnabled()) {

logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {

configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {

return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {

int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {

sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {

BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {

this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
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);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {

parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {

this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
// Omit ....
}

The first section is mainly to get qualified Configuration Configuration class , The main logic is in ConfigurationClassParser.parse In the method , This method completes the analysis of @Component、@Bean、@Import、@ComponentScans And so on , The main point here is @Import Parsing , Other readers can analyze . Follow up step by step , It will eventually enter processConfigurationClass Method :

 protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {

if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {

return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {

if (configClass.isImported()) {

if (existingClass.isImported()) {

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);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {

sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}

Here we need to pay attention to this.conditionEvaluator.shouldSkip Method call , This method is to Bean Load filtered , According to @Condition The matching value of the annotation determines whether to load the annotation Bean, The specific implementation will be analyzed later , Continue to track the main process doProcessConfigurationClass

 protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {

Omit ....
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
Omit ....
return null;
}

Here is the support for completing a series of annotations , I omitted , It mainly depends on processImports Method , This method is to deal with @Import Annotated :

 private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

if (importCandidates.isEmpty()) {

return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {

this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {

this.importStack.push(configClass);
try {

for (SourceClass candidate : importCandidates) {

if (candidate.isAssignable(ImportSelector.class)) {

// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {

this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {

String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {

Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {

this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
}
}

I just reminded you AutoConfigurationImportSelector It's the realization of DeferredImportSelector Interface , If it is not the implementation class of the interface, it is called directly selectImports Method , On the contrary, it calls DeferredImportSelectorHandler.handle Method :

 private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {

DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
configClass, importSelector);
if (this.deferredImportSelectors == null) {

DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
}
else {

this.deferredImportSelectors.add(holder);
}
}

First, I created a DeferredImportSelectorHolder object , If it is the first execution, it is added to deferredImportSelectors Properties of the , wait until ConfigurationClassParser.parse The last call process Method :

 public void parse(Set<BeanDefinitionHolder> configCandidates) {

Omit .....
this.deferredImportSelectorHandler.process();
}
public void process() {

List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {

if (deferredImports != null) {

DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
}
finally {

this.deferredImportSelectors = new ArrayList<>();
}
}

On the contrary, it is directly executed , First, through register Get AutoConfigurationGroup object :

 public void register(DeferredImportSelectorHolder deferredImport) {

Class<? extends Group> group = deferredImport.getImportSelector()
.getImportGroup();
DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
(group != null ? group : deferredImport),
key -> new DeferredImportSelectorGrouping(createGroup(group)));
grouping.add(deferredImport);
this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getConfigurationClass());
}
public Class<? extends Group> getImportGroup() {

return AutoConfigurationGroup.class;
}

And then in processGroupImports Method :

 public void processGroupImports() {

for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {

grouping.getImports().forEach(entry -> {

ConfigurationClass configurationClass = this.configurationClasses.get(
entry.getMetadata());
try {

processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
catch (BeanDefinitionStoreException ex) {

throw ex;
}
catch (Throwable ex) {

throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
public Iterable<Group.Entry> getImports() {

for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {

this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}

stay getImports In the method, it is completed to process and selectImports Method call , Get the auto configuration class and call it recursively processImports Method to load the auto configuration class . thus , The loading process of automatic configuration is analyzed , Here's the sequence diagram :
 Insert picture description here

Condition Annotation principle

There are many in the autoconfiguration class Condition Related notes , With AOP For example :

Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {

}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {

}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {

ClassProxyingConfiguration(BeanFactory beanFactory) {

if (beanFactory instanceof BeanDefinitionRegistry) {

BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
}

You can see it here @ConditionalOnProperty、@ConditionalOnClass、@ConditionalOnMissingClass, And then there is @ConditionalOnBean、@ConditionalOnMissingBean Wait, a lot of conditional matching annotations . These annotations represent Only when the conditions match will the Bean, With @ConditionalOnProperty For example , It indicates that the corresponding... Will be loaded only if the conditions in the configuration file are met Bean,prefix Indicates the prefix in the configuration file ,name Indicates the name of the configuration ,havingValue Indicates that only when the value is configured to match ,matchIfMissing It means that there is no such configuration, and whether the corresponding... Is loaded by default Bean. Other notes can be compared to understanding memory , The following mainly analyzes the implementation principle of the annotation .
If you look at the annotation here, you will find that each annotation is marked with @Conditional annotation , also value Values correspond to a class , such as OnBeanCondition, And these classes all implement Condition Interface , Look at its inheritance system :
 Insert picture description here
Only a few implementation classes are shown above , But actually Condition There are many implementation classes , We can also implement this interface to extend @Condition annotation .
Condition There is one in the interface matches Method , This method returns true Match . The method in ConfigurationClassParser There are calls in many places , That's what I just reminded shouldSkip Method , The concrete realization is in ConditionEvaluator Class :

 public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {

if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {

return false;
}
if (phase == null) {

if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {

return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {

for (String conditionClass : conditionClasses) {

Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {

ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {

requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {

return true;
}
}
return false;
}

Look again. matches The implementation of the , but OnBeanCondition Class does not implement the method , But in its parents SpringBootCondition in :

 public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

String classOrMethodName = getClassOrMethodName(metadata);
try {

ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}

getMatchOutcome Method is also a template method , The specific matching logic is implemented in this method , The method returns ConditionOutcome Object contains match and Log message Two fields . Enter into OnBeanCondition Class :

 public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {

ConditionMessage matchMessage = ConditionMessage.empty();
MergedAnnotations annotations = metadata.getAnnotations();
if (annotations.isPresent(ConditionalOnBean.class)) {

Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {

String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {

Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {

return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
}
else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
spec.getStrategy() == SearchStrategy.ALL)) {

return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
}
matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {

Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (matchResult.isAnyMatched()) {

String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
}

You can see that this class supports @ConditionalOnBean、@ConditionalOnSingleCandidate、@ConditionalOnMissingBean annotation , The main matching logic is getMatchingBeans In the method :

 protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {

ClassLoader classLoader = context.getClassLoader();
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
if (spec.getStrategy() == SearchStrategy.ANCESTORS) {

BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
"Unable to use SearchStrategy.ANCESTORS");
beanFactory = (ConfigurableListableBeanFactory) parent;
}
MatchResult result = new MatchResult();
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
spec.getIgnoredTypes(), parameterizedContainers);
for (String type : spec.getTypes()) {

Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
parameterizedContainers);
typeMatches.removeAll(beansIgnoredByType);
if (typeMatches.isEmpty()) {

result.recordUnmatchedType(type);
}
else {

result.recordMatchedType(type, typeMatches);
}
}
for (String annotation : spec.getAnnotations()) {

Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
considerHierarchy);
annotationMatches.removeAll(beansIgnoredByType);
if (annotationMatches.isEmpty()) {

result.recordUnmatchedAnnotation(annotation);
}
else {

result.recordMatchedAnnotation(annotation, annotationMatches);
}
}
for (String beanName : spec.getNames()) {

if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {

result.recordMatchedName(beanName);
}
else {

result.recordUnmatchedName(beanName);
}
}
return result;
}

The logic here seems complicated , But actually did two things , First, through getNamesOfBeansIgnoredByType Method call beanFactory.getBeanNamesForType Get the corresponding... In the container Bean example , Then judge which ones according to the returned results Bean There is , Which? Bean non-existent (Condition Multiple values can be configured in the annotation ) And back to MatchResult object , and MatchResult Only one of them Bean If there is no match, it returns false, It determines the current Bean Whether instantiation is required .

summary

This article analyzes SpringBoot Implementation of core principles , Through this article, I believe that readers will be able to use and expand SpringBoot. In addition, there are some common components that I didn't analyze , Such as business 、MVC、 Automatic configuration of listener , We have these Spring If the source code is basic, you can see it by looking down , I won't repeat it here . Finally, readers can think about how we should customize starter starter , I believe it should not be difficult for you to read this article .

copyright:author[Don't talk at night],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/02/202202130725583732.html