一、前言
在没有springBoot之前,我们新建一个项目,要做很多的事情,像配置一堆依赖,配置web.xml、数据库连接、视图解析等等。往往创建一个新项目,配置这些信息,都要花费大半天,也很容易出错,如加入的依赖有冲突或者漏掉某些依赖,导致项目启动失败。
使用springBoot,大大简化了配置的难度,像pom文件中的依赖,直接继承自springBoot,还有各种starter可以引用。另外,springBoot使用内嵌服务器,打包后是一个jar包,可以用java命令直接启动,不再需要把项目打包成war,部署到额外的服务器中。
二、springBoot项目例子
maven依赖
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<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>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.19</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
那些xxx-starter的包,会自动帮我们引入了相关的jar包,简化我们的配置内容。
例如 spring-boot-starter-web,帮我们引入了web模块开发需要的jar包,也不需要我们像以前那样,配置web.xml,配置视图解析…
项目启动类代码
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
springBoot项目的启动类,基本都是这样,有用的信息就是 @SpringBootApplication注解和 SpringApplication.run(Application.class, args)——启动应用。
那接下来,就从这两点,去研究一下SpringBoot启动过程中都做了哪些事情。
源码分析
@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
注解的属性可以不用关注,只是可以排除指定“自动配置类”和指定扫描的范围。
关键是组合的几个注解:@SpringBootConfiguration、@EnableAutoConfiguration和 @ComponentScan
从这里可以看出来,@SpringBootApplication注解基本等价于SpringBootConfiguration+EnableAutoConfiguration+ComponentScan,用这三个注解,也能启动springBoot应用
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
接下来,分析一下这三个注解的代码和作用:
- @SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
@SpringBootConfiguration 注解没有定义任何新的内容,单纯只是应用了 @Configuration注解,可以把它与 @Configuration作用等同。
@Configuration注解大家应该都不陌生,有该注解的类,会成为一个配置类,用来代替以前spring的xml配置文件。
- @ComponentScan 注解应该是大家最为熟悉的一个spring注解之一,它的作用就是自动扫描并加载符合条件的组件,比如 @Component 和 @Repository 等,最终将这些bean定义加载到spring容器中。代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
......
该注解最重要的属性就是basePackages,通过配置该属性,来控制自动扫描的范围,如果没有指定的话,就会从声明了 @ComponentScan 注解的的类所在的包进行扫描。这就意味着,springBoot项目,默认会从启动类所在的包进行扫描。
- @EnableAutoConfiguration 注解是最重要的,maven依赖中,xxx-start的包能够起到那么神奇的作用,都是靠这个注解。
在spring框架中,有很多名字是以 @Enable 开头的注解,例如 @EnableScheduling、@EnableCaching、@EnableMBeanExport等,这些都是借助@Import的帮助,将所有符合要求的bean加载到ioc容器中。
而 @EnableAutoConfiguration就会通过扫描类路径中jar包里的自动配置类。
先看看 @EnableAutoConfiguration的代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@EnableAutoConfiguration注解引入了 @AutoConfigurationPackage和 @Import这两个注解。@AutoConfigurationPackage的作用就是自动配置的包,@Import导入需要自动配置的组件。
AutoConfigurationImportSelector类的代码很长,只看几个重要的方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 获取自动配置的信息
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);
// 调用getCandidateConfigurations方法加载自动配置类名列表
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 用SpringFactoriesLoader来加载所有jar包中META-INF/spring.factories文件配置的自动化配置类的类名
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;
}
可以看出,最终是调用SpringFactoriesLoader.loadFactoryNames方法从所有的jar包中读取META-INF/spring.factories文件信息。
SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。
public abstract class SpringFactoriesLoader {
//...
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
...
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
....
}
}
配合 @EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据 @EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组 @Configuration类,并通过反射实例化对应的标注了
所以,@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中 @Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
下面是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实列。
如果我们想自己写一个xxx-starter的包,那有两点需要注意:
1、在META-INF目录下创建一个spring.factories文件,在里面配置上需要加载的自动配置类。
2、自动配置类要注意是否需要加 org.springframework.boot.autoconfigure.condition包下的条件类,如 @ConditionalOnClass在满足条件的情况下,才使配置生效。