一, 前言
使用过 springboot 的同学应该已经知道, springboot 通过默认配置了很多框架的使用方式帮我们大大简化了项目初始搭建以及开发过程.
本文的目的就是一步步分析 springboot 的启动过程, 这次主要是分析 springboot 特性自动装配.
那么首先带领大家回顾一下以往我们的 web 项目是如何搭建的, 通常我们要搭建一个基于 Spring 的 Web 应用, 我们需要做以下一些工作:
pom 文件中引入相关 jar 包, 包括 spring,springmvc,Redis,mybaits,log4j,MySQL-connector-java 等等相关 jar ...
配置 Web.xml,Listener 配置, Filter 配置, Servlet 配置, log4j 配置, error 配置 ...
配置数据库连接, 配置 spring 事务
配置视图解析器
开启注解, 自动扫描功能
配置完成后部署 tomcat, 启动调试
......
花在搭建一个初始项目, 可能一个小时就过去了或者半天救过了, 但是用了 SpringBoot 之后一切都会变得非常便捷, 下面我们首先来分析一下 SpringBoot 的起步依赖以及自动配置.
二, 起步依赖
1. 在我们的 pom 文件里面引入以下 jar:
- <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>
spring-boot-starter-Web 包自动帮我们引入了 Web 模块开发需要的相关 jar 包.
mybatis-spring-boot-starter 帮我们引入了 dao 开发相关的 jar 包.
spring-boot-starter-xxx 是官方提供的 starter,xxx-spring-boot-starter 是第三方提供的 starter.
截图看一下我们的 mybatis-spring-boot-starter
可以看出 mybatis-spring-boot-starter 并没有任何源码, 只有一个 pom 文件, 它的作用就是帮我们引入其它 jar.
2. 配置数据源
- 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 机制帮我们完成了项目起步所需要的的相关 jar 包. 那问题又来了, 传统的 spring 应用中不是要在 application.xml 中配置很多 bean 的吗, 比如 dataSource 的配置, transactionManager 的配置 ... springboot 是如何帮我们完成这些 bean 的配置的?
下面我们来分析这个过程
三, 自动配置
1. 基于 java 代码的 bean 配置
以 mybatis 为例, 在上面的截图中, 我们发现 mybatis-spring-boot-starter 这个包帮我们引入了 mybatis-spring-boot-autoconfigure 这个包, 如下图:
里面有 MybatisAutoConfiguration 这个类, 打开这个类看看有些什么东西.
熟悉 @Configuration&,@Bean 这两个 bean 的同学或许已经知道了. 这两个注解一起使用就可以创建一个基于 java 代码的配置类, 可以用来替代相应的 xml 配置文件.
@Configuration 注解的类可以看作是能生产让 Spring IoC 容器管理的 Bean 实例的工厂. br/>@Bean 注解告诉 Spring, 一个带有 @Bean 的注解方法将返回一个对象, 该对象应该被注册到 spring 容器中.
所以上面的 MybatisAutoConfiguration 这个类, 自动帮我们生成了 SqlSessionFactory 这些 Mybatis 的重要实例并交给 spring 容器管理, 从而完成 bean 的自动注册.
2. 自动配置条件依赖
从 MybatisAutoConfiguration 这个类中使用的注解可以看出, 要完成自动配置是有依赖条件的.
- @org.springframework.context.annotation.Configuration
- @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
- @ConditionalOnBean(DataSource.class)
- @EnableConfigurationProperties(MybatisProperties.class)
- @AutoConfigureAfter(DataSourceAutoConfiguration.class)
- public class MybatisAutoConfiguration {
- private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
- private final MybatisProperties properties;
- private final Interceptor[] interceptors;
- private final ResourceLoader resourceLoader;
- private final DatabaseIdProvider databaseIdProvider;
- private final List<ConfigurationCustomizer> configurationCustomizers;
- ......
首先预习一下 Springboot 是常用的条件依赖注解有: br/>@ConditionalOnBean, 仅在当前上下文中存在某个 bean 时, 才会实例化这个 Bean.
@ConditionalOnClass, 某个 class 位于类路径上, 才会实例化这个 Bean.br/>@ConditionalOnExpression, 当表达式为 true 的时候, 才会实例化这个 Bean.
@ConditionalOnMissingBean, 仅在当前上下文中不存在某个 bean 时, 才会实例化这个 Bean.br/>@ConditionalOnMissingClass, 某个 class 在类路径上不存在的时候, 才会实例化这个 Bean.
@ConditionalOnNotWebApplication, 不是 Web 应用时才会实例化这个 Bean.br/>@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 是如何读取 YAML 或者 properites 配置文件的的属性来创建数据源的?
在 DataSourceAutoConfiguration 类里面, 我们注意到使用了 EnableConfigurationProperties 这个注解.
- @Configuration
- @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
- @EnableConfigurationProperties(DataSourceProperties.class)
- @Import({ DataSourcePoolMetadataProvidersConfiguration.class,
- DataSourceInitializationConfiguration.class })
- public class DataSourceAutoConfiguration {
- @Configuration
- @Conditional(EmbeddedDatabaseCondition.class)
- @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
- @Import(EmbeddedDataSourceConfiguration.class)
- protected static class EmbeddedDatabaseConfiguration {
- }
- ......
DataSourceProperties 中封装了数据源的各个属性, 且使用了注解 ConfigurationProperties 指定了配置文件的前缀.
- @ConfigurationProperties(prefix = "spring.datasource")
- public class 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.
- */
- private boolean 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;
- ......
通过以上分析, 我们可以得知: br/>@ConfigurationProperties 注解的作用是把 YAML 或者 properties 配置文件转化为 bean.
@EnableConfigurationProperties 注解的作用是使 @ConfigurationProperties 注解生效. 如果只配置 @ConfigurationProperties 注解, 在 spring 容器中是获取不到 YAML 或者 properties 配置文件转化的 bean 的.
通过这种方式, 把 YAML 或者 properties 配置参数转化为 bean, 这些 bean 又是如何被发现与加载的?
4.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:br/>@Configuration(@SpringBootConfiguration 里面还是应用了 @Configuration)
@EnableAutoConfigurationbr/>@ComponentScan
@Configuration 的作用上面我们已经知道了, 被注解的类将成为一个 bean 配置类. br/>@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.
- */
- static class 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 容器.
5.Bean 加载
如果要让一个普通类交给 Spring 容器管理, 通常有以下方法:
使用 @Configuration 与 @Bean 注解
使用 @Controller @Service @Repository @Component 注解标注该类, 然后启用 @ComponentScan 自动扫描
使用 @Import 方法
springboot 中使用了 @Import 方法 br/>@EnableAutoConfiguration 注解中使用了 @Import({AutoConfigurationImportSelector.class})注解, AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,
DeferredImportSelector 接口继承了 ImportSelector 接口, ImportSelector 接口只有一个 selectImports 方法.
selectImports 方法返回一组 bean,@EnableAutoConfiguration 注解借助 @Import 注解将这组 bean 注入到 spring 容器中, springboot 正式通过这种机制来完成 bean 的注入的.
四, 总结
我们可以将自动配置的关键几步以及相应的注解总结如下: br/>@Configuration & 与 @Bean------>>>基于 java 代码的 bean 配置
@Conditional-------->>>>>>设置自动配置条件依赖 br/>@EnableConfigurationProperties 与 @ConfigurationProperties->读取配置文件转换为 bean.
@EnableAutoConfiguration,@AutoConfigurationPackage 与 @Import->实现 bean 发现与加载.
欢迎大家一起交流, 喜欢文章记得点个赞哟, 感谢支持!
来源: http://www.bubuko.com/infodetail-3148397.html