Spring Boot学习笔记
Spring Boot工作原理解析
自动配置源码解析
- 使用 Spring Boot 开发较之以前的基于 xml 配置式的开发,要简捷方便快速的多。而这完全得益于 Spring Boot 的自动配置。下面就通过源码阅读方式来分析自动配置的运行原理。
解析@SpringBootApplication
- 打开启动类的 @SpringBootApplication 注解源码,发现@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 { //...略 }
元注解:前四个是专门(即只能)用于对注解进行注解的,称为元注解。
@SpringBootConfiguration
- 查看 @SpringBootConfiguration 注解的源码可知,该注解与 @Configuration 注解功能相同,仅表示当前类为一个 JavaConfig 类,其就是为 Spring Boot 创建的一个注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
@ComponentScan
- @ComponentScan 用于指定当前应用所要扫描的包。注意,其仅仅是指定包,而并没有扫描这些包,更没有装配其中的类,这个真正扫描并装配这些类是 @EnableAutoConfiguration 完成的。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* Alias for {@link #basePackages}.
* <p>Allows for more concise annotation declarations if no other attributes
* are needed — for example, {@code @ComponentScan("org.my.pkg")}
* instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
*/
@AliasFor("basePackages")
String[] value() default {};
// ...略
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
// ...略
}
- 这个注解有三个重要属性:
- basePackages:用于指定要扫描的组件包,若没有指定则扫描当前注解所标的类所在的包及其子孙包。
- includeFilters:用于进一步缩小要扫描的基本包中的类,通过指定过滤器的方式进行缩小范围。
- excludeFilters:用于过滤掉那些不适合做组件的类。
@EnableXxx
- @EnableXxx 注解一般用于开启某一项功能,是为了简化配置代码的引入。其是组合注解,一般情况下 @EnableXxx 注解中都会组合一个@Import 注解,而该@Import 注解用于导入指定的类,而被导入的类一般为配置类。其导入配置类的方式常见的有三种:
- 一、直接引入配置类:@Import 中指定的类一般为 Configuration 结尾,且该类上会注解@Configuration,表示当前类为 JavaConfig 类。例如:@EnableScheduling
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
- 二、根据条件选择配置类:@Import 中指定的类一般以 ConfigurationSelector 结尾,且该类实现了 ImportSelector 接口,表示当前类会根据条件选择不同的配置类导入。例如:@EnableCaching
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching { // ...略 }
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
// ...略
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
// ...略
}
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
// ...略
}
- 三、动态注册Bean:@Import 中指定的类一般以 Registrar 结尾,且该类实现了 ImportBeanDefinitionRegistrar 接口,用于表示在代码运行时若使用了到该配置类,则系统会自动将其导入。例如:@EnableAspectJAutoProxy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
解析@EnableAutoConfiguration
@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 {};
}
- 该注解用于完成自动配置,是 Spring Boot 的核心注解,是一个组合注解。所谓自动配置是指,将用户自定义的类及框架本身用到的类进行装配。其中最重要的注解有两个:
- @AutoConfigurationPackage:用于导入并装配用户自定义类,即自动扫描包中的类。
- @Import:用于导入并装配框架本身的类。
@Import
- 该注解用于导入指定的类。其参数 AutoConfigurationImportSelector 类,该类用于导入自动配置的类。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
// ...略
/**
* Return the auto-configuration class names that should be considered.
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// ....
return configurations;
}
}
- 这样我们就找到自动配置的核心文件 spring.factories:
- Debug启动一个SpringBoot工程,断点跟踪一下:
@AutoConfigurationPackage
- 再打开@AutoConfigurationPackage 的源码,其也包含一个@Import 注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
- 这个注解是要将 AutoConfigurationPackages 类的内部静态类 Registrar 导入。从前面的学习可知,其是一个动态注册 Bean。
application.yml的加载
- application.yml 文件对于 Spring Boot 来说是核心配置文件,至关重要,那么该文件是如何加载到内存的呢?需要从启动类的 run()方法开始跟踪。
启动方法run()跟踪
@SpringBootApplication
public class PrimaryApplication {
public static void main(String[] args) {
SpringApplication.run(PrimaryApplication.class, args);
}
}
// ↓↓↓↓↓
// SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
// ↓↓↓↓↓
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
准备运行环境
// SpringApplication
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
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;
}
让监听器监听环境准备过程
// SpringApplication
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 让监听器监听环境准备过程
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
// ↓↓↓↓↓
// SpringApplicationRunListeners
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
发布环境准备事件
// EventPublishingRunListener
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
// ↓↓↓↓↓
// SimpleApplicationEventMulticaster
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
触发监听器
// SimpleApplicationEventMulticaster
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
// 触发监听器
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
// ↓↓↓↓↓
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
加载配置文件
- ConfigFileApplicationListener 配置文件的监听器加载配置文件:
- 继续跟踪 load 方法:可以查看到其要选择加载的配置文件的扩展名是 properties 或 yml
- 其中,properties 属性文件和yaml文件默认支持的文件后缀名可以查看源码得知:
- 接下来继续分析源码 loadForFileExtension 这个方法:
- Debug 断点看一下:配置文件最终被加载封装成一 List<PropertySource<?>>
Spring Boot与Redis的整合
- 在 spring.factories 中有一个 RedisAutoConfiguration 类,通过前面的分析我们知道,该类一定会被 Spring 容器自动装配。但是自动装配了就可以读取到 Spring Boot 配置文件中 Redis 相关的配置信息了?这个类与 Spring Boot 配置文件是怎么建立的联系?
- 我们知道,若要使代码可以操作 Redis,就需要获取到 RedisTemplate,并通过 RedisTemplate 获取到 Redis 操作对象 RedisOperations。没有 RedisTemplate 对象,没有 RedisOperations 接口,是无法操作 Redis 的。
- 查看 RedisAutoConfiguration 这个类的源码:加载了 Redis 配置、并创建了 RedisTemplate 对象。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class 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;
}
}
MyBatis与Spring Boot的整合
- 我们发现在 spring-boot-autoconfigure 中的 spring.factories 文件中并没有像 Redis 那样类似的 xxxAutoConfiguration 类的配置,因此我们只能去分析 mybatis-spring-boot-starter 依赖。而该依赖又依赖于 mybatis-spring-boot-autoconfigurigure。其 META-INF 中有 spring.factories 文件,打开这个文件我们找到了 Mybatis 的自动配置类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration { // ...略 }
- 这个自动配置类依赖两个工厂 Bean:SqlSessionFactory 和 SqlSessionFactoryBean,还依赖 DataSource 对象,加载 MyBatis 配置 MybatisProperties。这个类中包含两个@Bean 方法用于创建相应的 Bean:
自定义Starter
- 如果我们自己的某项功能代码也想将其应用到 Spring Boot 中,为 Spring Boot 项目提供相应支持,需要怎样实现呢?同样,我们需要定义自己的 Starter。
自定义 wrap-spring-boot-starter
- starter 工程的命名规范:Spring 官方定义的 starter 通常命名遵循的格式为 spring-boot-starter-{name},例如 spring-boot-starter-web。Spring 官方建议,非官方 starter 命名应遵循 {name}-spring-boot-starter 的格式,例如,dubbo-spring-boot-starter。
- 下面我们自定义一个自己的 Starter,实现的功能是:为用户提供的字符串添加前辍与后辍,而前辍与后辍定义在 yml 或 properties 配置文件中。例如,用户输入的字符串为 China,application.yml 配置文件中配置的前辍为$$$,后辍为+++,则最终生成的字符串为 $$$China+++。
- 创建一个 Spring Boot 工程,命名为 wrap-spring-boot-starter。
- 定义一个 Wrapper 类,核心功能在这个类中完成的。该类中的成员变量可以随意命名,但一般与在 Spring Boot 中使用的属性名同名。
@AllArgsConstructor
public class Wrapper {
private String prefix;
private String suffix;
/**
* 当前starter的核心功能实现方法
*/
public String wrap(String word) {
return prefix + word + suffix;
}
}
- 定义配置属性封装类:指定当前类用于封装来自于 Spring Boot 核心配置文件中的以 wrapper.service 开头的 prefix 与 surfix 属性值。
// 该类的对象是由系统自动创建,所以无需将其将给 Spring 容器管理
@Data
@ConfigurationProperties("wrapper.service")
public class WrapperProperties {
/**
* 对应配置文件中的wrapper.service.prefix属性
*/
private String prefix;
/**
* 对应配置文件中的wrapper.service.suffix属性
*/
private String suffix;
}
- 定义自动配置类:这里再增加一个开关配置
wrapper.service.enable
,当 enable 属性值为 true 时,或没有设置 some.service.enable 属性时才进行组装,若 enable 为 false,则不进行组装。
@Configuration
// 自由当前路径下存在SomeService类时,才会启用当前的JavaConfig配置类
@ConditionalOnClass(Wrapper.class)
// 指定配置文件中指定属性的封装类
@EnableConfigurationProperties(WrapperProperties.class)
public class WrapperAutoConfiguration {
@Resource
private WrapperProperties properties;
/**
* 以下两个方法的顺序不能颠倒
*/
@Bean
@ConditionalOnProperty(name = "wrapper.service.enable", havingValue = "true", matchIfMissing = true)
public Wrapper wrapper() {
return new Wrapper(properties.getPrefix(), properties.getSuffix());
}
@Bean
@ConditionalOnMissingBean
public Wrapper defaultWrapper() {
return new Wrapper("", "");
}
}
- 在 resources/META-INF 目录下创建一个名为 spring.factories 的文件。文件中内容:键是固定的,为 EnableAutoConfiguration 类的全限定性类名,而值则为我们自定义的自动配置类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.yw.springboot.example.config.WrapperAutoConfiguration
starter测试 09-wrapper-starter
- 创建一个 Spring Boot 工程,命名为 09-wrapper-starter,添加依赖:
<!--自定义Starter依赖-->
<dependency>
<groupId>com.yw.springboot.example</groupId>
<artifactId>wrap-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
- 定义 application.yml 文件:自定义 Starter 中的属性在配置文件中也是有自动提示功能的。
wrapper:
service:
# 设置开关状态,默认true开启
enable: true
# 指定前缀
prefix: $$$
# 指定后缀
suffix: +++
- 定义控制器类:
@RestController
public class WrapController {
/**
* byType方式的自动注入
*/
@Resource
private Wrapper wrapper;
@RequestMapping("/wrap/{param}")
public String wrap(@PathVariable("param") String param) {
return wrapper.wrap(param);
}
}
- 启动项目,测试接口:http://localhost:8080/wrap/test。