Depth understanding SpringBoot start mechanism (starter mechanism)

I. Introduction

Used springboot students should already know, springboot help us greatly simplifies the development process and the project to build the initial default configuration through a lot of the framework of use. The purpose of this paper is a step by step analysis springboot startup process, this time is to analyze springboot characteristics of the automatic assembly. 

Well first of all we look at the past to lead our project is how to build web, usually we have to build a Spring-based Web applications, we need to do some of the following: 

1, pom jar package file related to the introduction, including Spring, SpringMVC, Redis , mybaits, log4j, MySQL -connector-related Java jar ... etc.  

2, configure web.xml, Listener configuration, Filter configuration, Servlet configuration, log4j configuration, error configuration ...  

3, configure a database connection configuration spring Affairs  

4, the configuration view resolver  

5, open the annotation, auto scan function  

6, after the completion of the deployment configuration tomcat, start debugging ......  

Spent to build an initial project, it may or half an hour later saved, but with the SpringBoot everything will then become very easy, let's first analyze SpringBoot start and auto-configuration dependent.

Second, start dependence

1. The introduction of the jar in our pom files inside:

<modelVersion>4.0.0</modelVersion>
   <parent>
   	<groupId>org.springframework.boot</groupId>
   	<artifactId>spring-boot-starter-parent</artifactId>
   	<version>2.0.4.RELEASE</version>
   	<relativePath /><!-- lookup parent from repository -->
   </parent>
   <groupId>com.example</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>demo</name>
   <description>Demo project for Spring Boot</description>

   <properties>
   	<java.version>1.8</java.version>
   </properties>

   <dependencies>
   	<dependency>
   		<groupId>org.springframework.boot</groupId>
   		<artifactId>spring-boot-starter-web</artifactId>
   	</dependency>

   	<dependency>
   		<groupId>org.springframework.boot</groupId>
   		<artifactId>spring-boot-starter-test</artifactId>
   		<scope>test</scope>
   	</dependency>

   	<!--mybatis 开发包 -->
   	<dependency>
   		<groupId>org.mybatis.spring.boot</groupId>
   		<artifactId>mybatis-spring-boot-starter</artifactId>
   		<version>1.3.2</version>
   	</dependency>
   	<!--springboot web模块支持 -->
   	<dependency>
   		<groupId>org.springframework.boot</groupId>
   		<artifactId>spring-boot-starter-web</artifactId>
   	</dependency>
   	<dependency>
   		<groupId>mysql</groupId>
   		<artifactId>mysql-connector-java</artifactId>
   		<scope>runtime</scope>
   	</dependency>
   </dependencies>

   <build>
   	<plugins>
   		<plugin>
   			<groupId>org.springframework.boot</groupId>
   			<artifactId>spring-boot-maven-plugin</artifactId>
   		</plugin>
   	</plugins>
   </build>


the Boot-the Spring -starter-web package automatically help us to introduce the relevant jar package web module development needs. the mybatis - the Spring-the Boot -starter introduced dao help us develop relevant jar package. the Boot-the Spring -starter-xxx is the official starter, xxx- the Spring-the Boot -starter starter is provided by third parties. Screenshot look at our the mybatis - the Spring-the Boot -starter

 

As can be seen the mybatis - the Spring-the Boot -starter do not have any source code, only a pom file, its role is to help us to introduce other jar.

Configuring Data Source

spring:
 datasource:
    type:com.zaxxer.hikari.HikariDataSource
    url:jdbc:mysql://127.0.0.1:3306/mybatis_test
    username:root
    password:root
    driver-class-name:com.mysql.jdbc.Driver
    hikari:
     # 最小空闲连接数量
     minimum-idle:5
     # 连接池最大连接数,默认是10
     maximum-pool-size:60
     # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
     auto-commit:true
     # 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟
     idle-timeout:600000
     # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
     max-lifetime:1800000
     # 数据库连接超时时间,默认30秒,即30000
     connection-timeout:60000


stater mechanism to help us complete the relevant jar package needed to start the project. That problem again, the traditional spring application configuration application.xml not to do in a lot of bean, such as dataSource configuration, transactionManager configuration ...  springboot how the bean is to help us complete these configurations? Let's analyze this process

Third, automatic configuration

1. Based on the configuration of the code bean java

To mybatis , for example, in the screenshot above, we find that mybatis - the Spring-the Boot -starter This package introduced to help us mybatis - the Spring-the Boot -autoconfigure this package, as shown below:

 

There are MybatisAutoConfiguration this class, open class and see some what.

 

Familiar @Configuration &, @ Bean bean two students probably already know. Annotations using these two together can create a java class code based on the configuration, it can be used to replace the corresponding xml configuration file. @Configuration annotation class can be seen as capable of producing so that the Spring IoC container management Bean instance factory. @Bean annotation tells Spring, with a @Bean annotation method returns an object that should be registered in the spring container. So the above MybatisAutoConfiguration this class, automatically help us generate significant examples of these Mybatis SqlSessionFactory and container management to the spring, thus completing the automatic registration bean.

2. autoconfiguration conditions depend

Annotations using MybatisAutoConfiguration from this class can be seen that, to complete the automatic configuration is dependent condition.

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
publicclass MybatisAutoConfiguration {

 privatestaticfinal Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

 privatefinal MybatisProperties properties;

 privatefinal Interceptor[] interceptors;

 privatefinal ResourceLoader resourceLoader;

 privatefinal DatabaseIdProvider databaseIdProvider;

 privatefinal List<ConfigurationCustomizer> configurationCustomizers;
 ......

First prep about Springboot is a common condition depends comment are:

@ConditionalOnBean, when there is only a single bean in the current context, it will instantiate Bean.

@ConditionalOnClass, a class on the classpath, will instantiate Bean.

@ConditionalOnExpression, when the expression is true, when will instantiate Bean.

@ConditionalOnMissingBean,仅在当前上下文中不存在某个bean时,才会实例化这个Bean。

@ConditionalOnMissingClass,某个class在类路径上不存在的时候,才会实例化这个Bean。

@ConditionalOnNotWebApplication,不是web应用时才会实例化这个Bean。

@AutoConfigureAfter,在某个bean完成自动配置后实例化这个bean。

@AutoConfigureBefore,在某个bean完成自动配置前实例化这个bean。

所以要完成Mybatis的自动配置,需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类,需要存在DataSource这个bean且这个bean完成自动注册。进入DataSourceAutoConfiguration这个类,可以看到这个类属于这个包:org.springframework.boot.autoconfigure.jdbc 这个包又属于spring-boot-autoconfigure-2.0.4.RELEASE.jar这个包,自动配置这个包帮们引入了jdbc、kafka、logging、mail、mongo等包。很多包需要我们引入相应jar后自动配置才生效。

 

3.Bean参数的获取

到此我们已经知道了bean的配置过程,但是还没有看到springboot是如何读取yml或者properites配置文件的的属性来创建数据源的?在DataSourceAutoConfiguration类里面,我们注意到使用了EnableConfigurationProperties这个注解。

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
		DataSourceInitializationConfiguration.class })
publicclass DataSourceAutoConfiguration {

	@Configuration
	@Conditional(EmbeddedDatabaseCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import(EmbeddedDataSourceConfiguration.class)
	protectedstaticclass EmbeddedDatabaseConfiguration {

	}
......


DataSourceProperties中封装了数据源的各个属性,且使用了注解ConfigurationProperties指定了配置文件的前缀。

@ConfigurationProperties(prefix = "spring.datasource")
publicclass DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

	private ClassLoader classLoader;

	/**
	 * Name of the datasource. Default to "testdb" when using an embedded database.
	 */
	private String name;

	/**
	 * Whether to generate a random datasource name.
	 */
	privateboolean generateUniqueName;

	/**
	 * Fully qualified name of the connection pool implementation to use. By default, it
	 * is auto-detected from the classpath.
	 */
	private Class<? extends DataSource> type;

	/**
	 * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
	 */
	private String driverClassName;

	/**
	 * JDBC URL of the database.
	 */
	private String url;

	/**
	 * Login username of the database.
	 */
	private String username;

	/**
	 * Login password of the database.
	 */
	private String password;

	/**
	 * JNDI location of the datasource. Class, url, username & password are ignored when
	 * set.
	 */
	private String jndiName;

	/**
	 * Initialize the datasource with available DDL and DML scripts.
	 */
	private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;

	/**
	 * Platform to use in the DDL or DML scripts (such as schema-${platform}.sql or
	 * data-${platform}.sql).
	 */
	private String platform = "all";

	/**
	 * Schema (DDL) script resource references.
	 */
	private List<String> schema;

	/**
	 * Username of the database to execute DDL scripts (if different).
	 */
	private String schemaUsername;

	/**
	 * Password of the database to execute DDL scripts (if different).
	 */
	private String schemaPassword;

	/**
	 * Data (DML) script resource references.
	 */
	private List<String> data;

	......

通过以上分析,我们可以得知:@ConfigurationProperties注解的作用是把yml或者properties配置文件转化为bean。@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在spring容器中是获取不到yml或者properties配置文件转化的bean的。

通过这种方式,把yml或者properties配置参数转化为bean,这些bean又是如何被发现与加载的?

3.Bean的发现

springboot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包的中的类,那么依赖包中的bean是如何被发现和加载的?

我们通常在启动类中加@SpringBootApplication这个注解,点进去看

@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 {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	 ......


实际上重要的只有三个Annotation:

@Configuration(@SpringBootConfiguration里面还是应用了@Configuration)

@EnableAutoConfiguration

@ComponentScan

@Configuration的作用上面我们已经知道了,被注解的类将成为一个bean配置类。@ComponentScan的作用就是自动扫描并加载符合条件的组件,比如@Component和@Repository等,最终将这些bean定义加载到spring容器中。

@EnableAutoConfiguration 这个注解的功能很重要,借助@Import的支持,收集和注册依赖包中相关的bean定义。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public@interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

如上源码,@EnableAutoConfiguration注解引入了@AutoConfigurationPackage和@Import这两个注解。@AutoConfigurationPackage的作用就是自动配置的包,@Import导入需要自动配置的组件。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public@interface AutoConfigurationPackage {

}


/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */
staticclass Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		register(registry, new PackageImport(metadata).getPackageName());
	}

	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new PackageImport(metadata));
	}

}


new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()

new AutoConfigurationPackages.PackageImport(metadata)

这两句代码的作用就是加载启动类所在的包下的主类与子类的所有组件注册到spring容器,这就是前文所说的springboot默认扫描启动类所在的包下的主类与子类的所有组件。

那问题又来了,要搜集并注册到spring容器的那些beans来自哪里?

进入 AutoConfigurationImportSelector类,

我们可以发现SpringFactoriesLoader.loadFactoryNames方法调用loadSpringFactories方法从所有的jar包中读取META-INF/spring.factories文件信息。

下面是spring-boot-autoconfigure这个jar中spring.factories文件部分内容,其中有一个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定义了需要自动配置的bean,通过读取这个配置获取一组@Configuration类。

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

每个xxxAutoConfiguration都是一个基于java的bean配置类。实际上,这些xxxAutoConfiguratio不是所有都会被加载,会根据xxxAutoConfiguration上的@ConditionalOnClass等条件判断是否加载;通过反射机制将spring.factories中@Configuration类实例化为对应的java实列。

到此我们已经知道怎么发现要自动配置的bean了,最后一步就是怎么样将这些bean加载到spring容器。

4.Bean 加载

如果要让一个普通类交给Spring容器管理,通常有以下方法:

1、使用 @Configuration与@Bean 注解

2、使用@Controller @Service @Repository @Component 注解标注该类,然后启用@ComponentScan自动扫描

3、使用@Import 方法

springboot中使用了@Import 方法 @EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector实现了DeferredImportSelector接口, DeferredImportSelector接口继承了ImportSelector接口,ImportSelector接口只有一个selectImports方法。

selectImports方法返回一组bean,@EnableAutoConfiguration注解借助@Import注解将这组bean注入到spring容器中,springboot正式通过这种机制来完成bean的注入的。

四、总结

我们可以将自动配置的关键几步以及相应的注解总结如下:

1、@Configuration&与@Bean------>>>基于java代码的bean配置

2、@Conditional-------->>>>>>设置自动配置条件依赖

3、@EnableConfigurationProperties与@ConfigurationProperties->读取配置文件转换为bean。

4、@EnableAutoConfiguration、@AutoConfigurationPackage 与@Import->实现bean发现与加载。

发布了50 篇原创文章 · 获赞 1628 · 访问量 203万+

Guess you like

Origin blog.csdn.net/zl1zl2zl3/article/details/104625903