Every time we create a springboot Application, we will find that there will be an Application class (hereinafter referred to as the Application class directly) headed by the Application name in its directory structure, while other packages are under the same level or child level of this class, as shown in the figure below:
The Application class, as the Application startup class, is located in the root directory of the project source code. As for why the structure is so arranged, we will talk about it below.
As shown in the figure above, we can see that there are two key points of Application:
- @SpringBootApplication annotation
- SpringApplication.run() method
One 1@SpringBootApplication annotation
Open the source code of the annotation, we can see that it mainly consists of the following annotations:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
- @SpringBootConfiguration
@The spring bootconfiguration annotation is annotated by @ Configuration, which means that the Application class itself is a bean. Although the @ SpringBootConfiguration annotation says that the annotation can only be used once in an Application, it is not wrong to configure multiple annotations, but it is recommended to use it only once in an Application, and the annotation can also be interchanged with @ Configuration.
- @ComponentScan
@The function of ComponentScan annotation is the same as that of our previous xml file Configuration. The above also mentioned that the Application class is placed in the root directory of the source code, which is actually related to this annotation. @When the basePackages attribute is not specified in ComponentScan, all @ Component annotated classes under the package and its subpackages of the class where the annotation is located will be scanned by default, including @ Controller, @ Service, @ Configuration. That's why we can scan the classes in the springboot Application as bean s without any Configuration.
- @EnableAutoConfiguration
@The enable autoconfiguration annotation, as we can see from its name, enables self configuration. From the source code, we can see that there is a @ import (autoconfigu) in the annotation rationImportSelector.class )The AutoConfigurationImportSelector class is used for autoconfiguration.
@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 {
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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; } }
As can be seen from the source code of AutoConfigurationImportSelector, this class loads the definition of autoconfiguration classes through SpringFactoriesLoader, so as to further complete the default configuration through these autoconfiguration classes. So that's why we don't need to configure too much when we use spring boot. The reason is that spring boot loads the auto configuration classes already encapsulated in the jar package to generate bean s through auto configuration. As for the principle of spring factors loader, we will talk about it below.
1.2SpringApplication class
The Application class starts the Application through the static run method of the SpringApplication class. Open this static method. The statement method actually performs two parts:
- new SpringApplication()
- Execute object run() method
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = deduceWebApplicationType(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
Spring application construction method
In the construction method of SpringApplication, we can see the following steps:
- Infer whether the current environment is a web environment, in which spring boot 5.0 adds a reactive environment.
- Initialize the applicationcontextinitiator, the principle of which is also implemented by spring factorysloader.
- Initialize ApplicationListener, the principle is the same as above.
- Infers the class of the currently started main method.
- Infer current application environment
Spring boot needs to infer the current application environment at startup. Spring boot 5.0 defines three environments: none, servlet, and reactive. None indicates that the current application is neither a web application nor a reactive application, but a pure background application. Servlet indicates that the current application is a standard web application. Reactive is a new feature in spring 5, which represents a responsive web application. The judgment is based on the class loaded in Classloader. If it's a servlet, it means web; if it's a dispatcher handler, it means a reactive application; if neither of them exists, it means an application in a non web environment.
- Initialize ApplicationContextInitializer
Before introducing initialization, let's talk about the principle of spring facts loader. Enter the getSpringFactoriesInstances method:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
Go to s pringFactoriesLoader.loadFactoryNames (type,classLoader):
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //Get the spring.factories File path for Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { //Load one of them spring.factories file URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); //The spring.factories All key value pairs in the file Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { //A collection of implementation classes for one of the interfaces Entry<?, ?> entry = (Entry)var6.next(); List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue())); result.addAll((String)entry.getKey(), factoryClassNames); } } cache.put(classLoader, result); return result; } catch (IOException var9) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9); } } }
Spring factoiesloader will scan meta-inf in all jar packages/ spring.factoies File, the format of which is key=value. Key is the name of an interface, and value is the name of an implementation class. If value has multiple values, they are separated by commas. The above mentioned @ EnableAutoConfiguration also uses this principle to scan all auto configuration classes, taking the fully qualified class name of the annotation as the key and all default configuration classes as the values. There is a static double-layer ConcurrentMap cache inside the SpringFactoiesLoader, which is used to store these key-value. The key of the first layer map is classloader, the default classloader of the springboot is appClassLoader, and the value is a MultiValueMap, which is a hashmap implemented by spring itself. And the key of this multivaluemap is spring.factoies The key and value in the file are a list, that is spring.factoies Value in the file. Spring factoiesloader uses the caching mechanism to scan all meta-inf only for the first time/ spring.factoies When a file is created, all the key values are loaded into the map. Later, when getting the values from the map, you can get them directly from the map, and you don't need to scan again.
After the ApplicationContextInitializer is loaded, another work is to generate objects for all ApplicationContextInitializer implementation classes. There is a property in SpringApplication, initializers of type List, to store these instantiated objects. After these objects are stored, ApplicationContext will be initialized in the later startup process.
- Initialize ApplicationListener
The process of ApplicationListener is the same as that of ApplicationContextInitializer, but since springfactoriesloader has been scanned once, the value will be taken directly from the static map in springfactoriesloader during this execution. Similarly, all implementation classes need to be generated into objects and stored in the listener list attribute of the SpringApplication object.
Note: during the initialization of springboot, there are mainly two scans spring.factoies File, which defines the class in key value, is used in the whole startup process. One is in spring-boot-2.0.3.RELEASE and the other is in spring-boot-autoconfigure package. The files in these two jar packages define all the default configured classes to be executed throughout the startup process.
- Infer the class of the main method of the whole application
In fact, we have started from the main method. Why do we have to infer later? In fact, the main purpose of doing this is to store the objects of this class in the SpringApplication object, create a log logger and print logs. When we start, we will see the class name of the main class and other printing information, which are used to create a logger and print logs.
Summary: from the above steps, we can see that the new SpringApplication() method in the early stage mainly plays a function of preloading, judging the environment in the early stage and the objects to be used in the later stage are ready, and it's good to take them out and use them directly when the run is executed, which greatly facilitates the execution process of the later run method.
run method
Enter the run method:
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); //Get and start the SpringApplicationRunListeners listener SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { //These two sentences are used for property configuration. After execution, external parameters and application.properties The parameters in are loaded in and the application environment configuration events are published. ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
- stopWatch.start();
StopWatch does a simple monitoring of application startup, monitoring the time spent on application startup;
- SpringApplicationRunListeners listeners = getRunListeners(args);
Get SpringApplicationRunListener, the only implementation of this class is eventpublishingrunnlistener, which is used to publish some events during the context startup process to inform the listener of the start-up and running steps of the application. It contains the following methods, each using simpleapplica tionEventMulticaster.multicastEvent (application event) to publish the event.
starting()//The run method is executed immediately; the corresponding event type is ApplicationStartedEvent environmentPrepared(ConfigurableEnvironment environment) //Called before the ApplicationContext is created and the environment information is ready; the corresponding event type is ApplicationEnvironmentPreparedEvent contextPrepared(ConfigurableApplicationContext context)// ApplicationContext is created and called once before source is loaded; there is no specific corresponding event. contextLoaded(ConfigurableApplicationContext context)//AplicationContext is created and loaded and invoked before refresh; the type of the corresponding event is ApplicationPreparedEvent. started(ConfigurableApplicationContext context) //The run method is called before the end of the method; the type of the corresponding event is ApplicationReadyEvent or ApplicationFailedEven.
-
getOrCreateEnvironment() ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
Entering the method, you can find that this step is:
- Load servlet configuration, servlet context configuration, virtual machine configuration, operating system environment configuration, jndi configuration in getOrCreateEnvironment(),
- Then load the command-line variable configuration in prepareEnvironment() (put it first), and notify configfil eApplicationListener.onApplicationEvent () to load the Random configuration and "classpath:/,classpath:/config/,file:./,file:./config/ "Four places application.properties , application.xml , application.yml and application.yaml Configuration in. There will be spring.factories The default configuration performed by the AutoConfiguration class in.
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast( new MapPropertySource("defaultProperties", this.defaultProperties)); } if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(new SimpleCommandLinePropertySource( "springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
- context = createApplicationContext();
Create the application context, and create the application context object according to the application type inferred in the previous SpringApplication construction phase.
- prepareContext(context, environment, listeners, applicationArguments,printedBanner);
Initialize the application context. This is to initialize the application context object with the initialize() method of all the application context initializers loaded in the previous spring application construction phase. Then load the definition of your own XxxSpringApplication class into the SpringApplication object to facilitate printing logs.
- refreshContext(context);
Loading a Bean is the process of Spring loading a Bean definition and creating a single Bean. I will write a Spring source interpretation to introduce it separately later.
- afterRefresh(context, applicationArguments);
At the end of the run method in the spring boot process, there is an after refresh (context, application arguments). It mainly implements the implementation classes of the two interfaces of ApplicationRunner and CommandLineRunner. These two classes are a bit of code executed after the completion of springboot startup, similar to the init method in ordinary bean s, which is used for initialization. The only difference between the two is the way to get parameters.
Therefore, to summarize the run method, the following operations are mainly done:
- Start the simple monitor;
- Get and start SpringApplicationRunListener;
- Default and external configuration attribute loading;
- Create context;
- Initialize context, call initialize() of initializer;
- Initialize the container and load the Bean;
- Application post startup processing;
- Start up complete.
1.3@EnableAutoConfiguration
Reference source:
This section details EnableAutoConfiguration. As we mentioned earlier, @ EnableAutoConfiguration is actually handled by AutoConfigurationImportSelector. Springfactorysloader in AutoConfigurationImportSelector will find all spring.factories File, then query properties org.springframework.boot.autoconfigure.EnbleAutoConfiguration These values are stored in the cache, and later opened in spring The refresh method in the dynamic process loads the real configuration class bean. The value of EnableAutoConfiguration is actually the AutoConfiguration class of some starter s. Let's go to one of the AutoConfiguration classes to see how it loads the default configuration and creates the beans required for the component.
Take the RedisAutoConfiguration class RedisAutoConfiguration for example, as shown below.
@Configuration @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
There is a @ EnableConfigurationProperties({RedisProperties.class }), the redisproperties specified in this note is the POJO of Redis's default configuration property. By putting the default property into the POJO class, we can generate a type prompt and verify the provided value when configuring the property in the Springboot application:
@ConfigurationProperties( prefix = "spring.redis" ) public class RedisProperties { private int database = 0; private String url; private String host = "localhost"; private String password; private int port = 6379; private boolean ssl; private Duration timeout; private RedisProperties.Sentinel sentinel; private RedisProperties.Cluster cluster; private final RedisProperties.Jedis jedis = new RedisProperties.Jedis(); private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce(); //A series of setter/getter methods }
Therefore, when we want to create a starter ourselves, we only need to provide the following classes:
- XxxProperties
- XxxAutoConfiguration: and use EnableConfigurationProperties({XxxProperties.class }
- In META-INF/spring.factories Add the following to:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ XxxAutoConfiguration
- Then pack it.
When we go back to RedisAutoConfiguration, we see that there are several methods in this class, with some annotations on them. Where @ Bean tells spring that the method returns a Bean to the spring container for management, while @ ConditionalXxx annotation is the condition for executing the following methods, which can only be executed if the condition is met.
The principle of @ ConditionalXxx is to implement the matchOutcome method of the Conditional interface, then add a @ Conditional annotation on @ ConditionalXxx, and give the implementation class of the processing class - Conditional.
Through AutoConfiguration, there are several important @ Conditional:
@ConditionalOnBean: when there is a specified Bean in the container
@ConditionalOnClass: when there is a specified class in the class path
@ConditionalOnExpression: Based on spiel expression
@ConditionalOnJava: Based on JV version
@ConditionalOnJndi: poor at the specified position when JNDI exists
@ConditionalOnMissingBean: when no Bean is specified in the container
@ConditionalOnMissingClass: when no class is specified in the class path
@ConditionalOnNotWebApplication: if the current project is not a Web project
@ConditionalOnProperty: whether the specified property has the specified value
@ConditionalOnResource: whether the class path has the specified value
@ConditionalOnSingleCandidate: when there is only one specified Bean in the container, or there are multiple specified preferred beans
@ConditionalOnWebApplication: when the current project is a Web project.