SpringBoot学习笔记
Q&A
SpringBoot自动装配原理
Spring Boot关于自动配置的源码在spring-boot-autoconfigure-x.x.x.x.jar
中
SpringBoot 的核心注解 SpringBootApplication
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
<1.>@SpringBootConfiguration
<2.>@ComponentScan
<3.>@EnableAutoConfiguration
public @interface SpringBootApplication {
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //实际上它也是一个配置类
public @interface SpringBootConfiguration {
}
可以把 @SpringBootApplication
看作是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制@Configuration
:允许在上下文中注册额外的 bean 或导入其他配置类@ComponentScan
: 扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilter
和AutoConfigurationExcludeFilter
。
@EnableAutoConfiguration
是实现自动装配的重要注解,我们以这个注解入手。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //作用:将main包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()
方法通过SpringFactoriesLoader.loadFactoryNames()
扫描所有具有META-INF/spring.factories 的jar包。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
public interface DeferredImportSelector extends ImportSelector {
}
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
private static final String[] NO_IMPORTS = new String[0];
//获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// <1>.判断自动装配开关是否打开
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//<2>.获取所有需要装配的bean
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
getAutoConfigurationEntry()
方法,这个方法主要负责加载自动配置类的
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
//<1>.
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//<2>.
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//<3>.
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//<4>.
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
第 1 步:
判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true
,可在 application.properties
或 application.yml
中设置
第 2 步 :
用于获取EnableAutoConfiguration
注解中的 exclude
和 excludeName
。
第 3 步
获取需要自动装配的所有配置类,读取META-INF/spring.factories
spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
第 4 步 :
筛选,@ConditionalOnXXX
中的所有条件都满足,该类才会生效。
//举例: RabbitAutoConfiguration
@Configuration
// 检查相关的类:RabbitTemplate 和 Channel是否存在
// 存在才会加载
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}
spring.factories
文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration
类的全类名,而它的value是一个xxxxAutoConfiguration
的类名的列表,这些类名以逗号分隔;
在SpringApplication.run(...)
的内部就会执行selectImports()
方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
Spring Boot启动流程
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
从源码声明可以看出,@SpringBootApplication相当于 @SpringBootConfiguration + @ComponentScan + @EnableAutoConfiguration ,因此我们直接拆开来分析。
上面三个注解都在做一件事:注册bean到spring容器。他们通过不同的条件不同的方式来完成:
- @SpringBootConfiguration 通过与 @Bean 结合完成Bean的 JavaConfig 配置;
- @ComponentScan 通过范围扫描的方式,扫描特定注解注释的类,将其注册到Spring容器;
- @EnableAutoConfiguration 通过 spring.factories 的配置,并结合 @Condition 条件,完成bean的注册;
除了上面的三个注解,还可以使用@Import注解将bean注册到Spring容器
- @Import 通过导入的方式,将指定的class注册解析到Spring容器;
启动流程
SpringApplication的实例化
- 推断应用类型是否是Web环境
- 设置初始化器(Initializer)
- 设置监听器(Listener)
- 推断应用入口类(Main)
SpringApplication.run方法
- 获取SpringApplicationRunListeners
- 准备配置环境ConfigurableEnvironment
- 创建ApplicationContext应用上下文
- ApplicationContext前置处理
- ApplicationContext刷新
- ApplicationContext后置处理
构造方法解析
// SpringApplication.java
/**
* 资源加载器
*/
private ResourceLoader resourceLoader;
/**
* 主要的 Java Config 类的数组
*/
private Set<Class<?>> primarySources;
/**
* Web 应用类型
*/
private WebApplicationType webApplicationType;
/**
* ApplicationContextInitializer 数组
*/
private List<ApplicationContextInitializer<?>> initializers;
/**
* ApplicationListener 数组
*/
private List<ApplicationListener<?>> listeners;
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
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 = WebApplicationType.deduceFromClasspath();
// 初始化 initializers 属性
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化 listeners 属性
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
getSpringFactoriesInstances(...)
#getSpringFactoriesInstances(Class<T> type)
方法,获得指定类对应的对象们。代码如下:
// SpringApplication.java
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// <1> 加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// <2> 创建对象们
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// <3> 排序对象们
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
<1>
处,调用SpringFactoriesLoader#loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)方法,加载指定类型对应的,在META-INF/spring.factories里的类名的数组。
- 在
META-INF/spring.factories
文件中,会以 KEY-VALUE 的格式,配置每个类对应的实现类们。
- 在
<2>
处,调用#createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names)
方法,创建对象们。<3>
处,调用AnnotationAwareOrderComparator#sort(List<?> list)
方法,排序对象们。例如说,类上有@Order
注解。
run()方法解析
// SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
// <1> 创建 StopWatch 对象,并启动。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// <2> 设置java.awt.headless系统属性,默认为true
// Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
configureHeadlessProperty();
// 获得 SpringApplicationRunListener 的数组
SpringApplicationRunListeners listeners = getRunListeners(args);
// 通知监听者,开始启动
listeners.starting();
try {
// <3> 创建 ApplicationArguments 对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// <4> 加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括 application.properties 和外部的属性配置。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// <5> 打印 Spring Banner
Banner printedBanner = printBanner(environment);
// <6> 创建 Spring 容器
context = createApplicationContext();
// <7> 注册异常分析器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// <8> Spring上下文前置处理-主要是调用所有初始化类的 initialize 方法
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// <9> Spring上下文刷新-初始化 Spring 容器
refreshContext(context);
// <10> 执行 Spring 容器的初始化的后置逻辑。默认实现为空。
afterRefresh(context, applicationArguments);
// <11> 停止 StopWatch 统计时长
stopWatch.stop();
// <12> 打印 Spring Boot 启动的时长日志。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// <13> 通知 SpringApplicationRunListener 的数组,Spring 容器启动完成。
listeners.started(context);
// <14> 调用 ApplicationRunner 或者 CommandLineRunner 的运行方法。
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// <14.1> 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// <15> 通知 SpringApplicationRunListener 的数组,Spring 容器运行中。
listeners.running(context);
}
catch (Throwable ex) {
// <15.1> 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
<1>
处,创建 StopWatch 对象,并调用StopWatch#run()
方法来启动。StopWatch 主要用于简单统计 run 启动过程的时长。<2>
处,配置 headless 属性。这个逻辑,可以无视,和 AWT 相关。<3>
处,调用#getRunListeners(String[] args)
方法,获得 SpringApplicationRunListener 数组,并启动监听。代码如下:java// SpringApplication.java private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); }
<4>
处,调用#prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments)
方法,加载属性配置。执行完成后,所有的 environment 的属性都会加载进来,包括application.properties
和外部的属性配置<5>
处,调用#printBanner(ConfigurableEnvironment environment)
方法,打印 Spring Banner 。<6>
处,调用#createApplicationContext()
方法,创建 Spring 容器<7>
处,通过#getSpringFactoriesInstances(Class<T> type)
方法,进行获得 SpringBootExceptionReporter 类型的对象数组。SpringBootExceptionReporter ,记录启动过程中的异常信息。<8>
处,调用#prepareContext(...)
方法,主要是调用所有初始化类的#initialize(...)
方法。<9>
处,调用 ``#refreshContext(ConfigurableApplicationContext context)` 方法,启动(刷新) Spring 容器<10>
处,调用#afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args)
方法,执行 Spring 容器的初始化的后置逻辑。默认实现为空。<11>
处,停止 StopWatch 统计时长。<12>
处,打印 Spring Boot 启动的时长日志。<13>
处,调用SpringApplicationRunListeners#started(ConfigurableApplicationContext context)
方法,通知 SpringApplicationRunListener 的数组,Spring 容器启动完成。<14>
处,调用#callRunners(ApplicationContext context, ApplicationArguments args)
方法,调用 ApplicationRunner 或者 CommandLineRunner 的运行方法。<15>
处,调用SpringApplicationRunListeners#running(ConfigurableApplicationContext context)
方法,通知 SpringApplicationRunListener 的数组,Spring 容器运行中。
SpringBoot注解
条件注解
@ConditionalOnBean
:当容器里有指定 Bean 的条件下@ConditionalOnMissingBean
:当容器里没有指定 Bean 的情况下@ConditionalOnSingleCandidate
:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean@ConditionalOnClass
:当类路径下有指定类的条件下@ConditionalOnMissingClass
:当类路径下没有指定类的条件下@ConditionalOnProperty
:指定的属性是否有指定的值@ConditionalOnResource
:类路径是否有指定的值@ConditionalOnExpression
:基于 SpEL 表达式作为判断条件@ConditionalOnJava
:基于 Java 版本作为判断条件@ConditionalOnJndi
:在 JNDI 存在的条件下差在指定的位置@ConditionalOnNotWebApplication
:当前项目不是 Web 项目的条件下@ConditionalOnWebApplication
:当前项目是 Web 项 目的条件下
SpringBoot配置文件
加载顺序
当Spring Boot 项目中可以存在多个 application.properties 或 apllication.yml时,Spring Boot 启动时会扫描以下 5 个位置的 application.properties 或 apllication.yml 文件,并将它们作为 Spring boot 的默认配置文件。以下是加载默认配置文件的优先级顺序,从高到底,高优先级的配置可以覆盖低优先级的配置信息:
1> file:./config/*/
2> file:./config/
3> file:./
4> classpath:/config/
5> classpath:/
SpringBooot添加本地 jar
步骤
- 添加 jar 文件到项目中 resources/lib/xxx.jar
- 安装 jar 包到 maven 本地仓库
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
<executions>
<execution>
<id>install-demo-jar</id>
<phase>clean</phase>
<configuration>
<file>
${project.basedir}/src/main/resources/lib/demo.jar
</file>
<groupId>com.javalover</groupId>
<artifactId>demo</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<generatePom>true</generatePom>
</configuration>
<goals>
<goal>install-file</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 运行
mvn clean