自动配置原理

@SpringBootApplication 解析

我们是在 @SpringBootApplication 中确定的 SpringBoot 的程序入口,也是 SpringBoot 的初始化开始的地方。那么 SpringBoot 是如何实现自动配置的呢,将 @SpringBootApplication 点开,可以看到是下面三个注解组成的:

1
2
3
@SpringBootConfiguration
@ComponentScan
@EnableAutoConfiguration

@SpringBootConfiguration 本质就是个 @Configuration 表示这个类是一个配置类(也就是可以当作 applicationContext.xml 使用的类),比如在里面使用注解 @Bean 代替 <bean> 来配置 IOC 容器的 Bean。

@ComponentScan 主要是用来扫描包组件的,它可以自定义扫描的 BasePackage,如果没有指定的话,就是 @SpringBootApplication 所在的地方为 BasePackage 。

@EnableAutoConfiguration 这个是核心,又包含了下面的两种注解:

1
2
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

@AutoConfigurationPackage

这里又套娃导入了一个 @Import({Registrar.class}) ,点开 Registrar.class 可以看到,代码里面主要干了下面的事情:

自动配置包类,注册了当前路径为下面的 PackageNames 作为配置的下属包(其实也对应 @ComponentScan):

1
AutoConfigurationPackages.register(getPackageNames().toArray()); // 有简略

@Import({AutoConfigurationImportSelector.class})

这里主要导入了 AutoConfigurationImportSelector.class 这个类,主要干的事情就是:

1
2
3
public String[] selectImports () {
return this.getAutoConfigurationEntry;
}

也就是获得 100 多个 web 的 starter 所需要配置的类的全类名(其实是在一个文件中写死的类名),然后返回。


SpringBoot 更厉害的是,它不是全部都真的将所有的预设类实例化并添加到 IOC 容器中的,而是根据 @Conditional 注解,根据里面的条件判断是否加载这个类。

比如虽然导入了 web-starter 的开发环境,可能需要 AOP 相关,但是 SpringBoot 是根据 @ConditionalOnClass(Advice.class) 来判断是否加载的,只有导入了 AspectJ 这个包(Advice.class 存在),SpringBoot 才会给你将 AOP 所需要的类都给加入到 IOC 容器中。

这就是传说中的按需加载。

自动装配

具体实现细节

上面大概说了说实利用 @Conditional 系列注解进行按需加载的,我们来结合一个熟悉的案例分析一下,DispatcherServlet:

上面说到了返回的 100 多个配置类的全类名统一格式都是 XXXXAutoConfiguration,通过运行这些类,来对相关内容进行自动配置。

我们点开 DispathcerServletAutoConfiguration,这个就是对 DispathcerServlet 进行自动配置的类,先看看程序头:

1
2
3
4
5
@ConditionalOnClass({DispatcherServlet.class})
@AutoConfigureAfter({ServletWebServerFactoryAutoConfiguration.class})
public class DispatcherServletAutoConfiguration {
...
}

@ConditionalOnClass表示导入了 DispathcerSevlet(也就是 SpringMVC) 后才会运行下面的自动装配类。@AutoConfigureAfter 表示先自动加载完服务器的 AutoConfiguration 之后再加载自己。这两个都是启动装配的起始条件。

再进一步就是子类 RegistrationConfiguration 了:

1
2
3
4
5
@EnableConfigurationProperties({WebMvcProperties.class})
@Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration {
...
}

@EnableConfigurationProperties 表示使用 WebMvcProperties.class 内容作为配置文件,而这个类是使用 spring.mvc 作为配置的 prefix 的。

前面都是讲了按需加载的一些东西,那么具体的 DispatcherServlet 的配置细节在那里呢?

1
2
3
4
5
6
7
public DispatcherServletRegistrationBean dispatcherServletRegistration() {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}

可以看到是将一系列的配置信息都设置到了 registration 中,最后返回,这里就是将获取的所有配置信息设置的地方。

更改设置信息

如何修改默认的设置信息呢,当然是在 application.properties 里面修改了,那么这些修改又是如何影响到 SpringBoot 的自动装配呢,我们以 CharacterEncodingFilter 的自动装配为例展示一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@EnableConfigurationProperties({ServerProperties.class}) //调用外部的 服务器设置
@ConditionalOnClass({CharacterEncodingFilter.class}) //有这个类才会进行自动初始化
@ConditionalOnProperty(
prefix = "server.servlet.encoding", //在调用的外部 ServerProperties 中 server.servlet.encoding 选项是否为enabled,如果是成立,如果不是失败,如果没有配置则默认为是。
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;

//从外部的 ServerProerties 中获取关于编码的设置,并放入本地变量 properties 中
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}

@Bean //在 IOC 中创建 Bean
@ConditionalOnMissingBean //IOC 中必须没有手动创建此实例才会自动创建
public CharacterEncodingFilter characterEncodingFilter() { //根据读取的 properties 配置信息创建 Bean 并放入 IOC 中。
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));
return filter;
}
}

根据上面的代码,我们能看到配置信息的最重要来源就是上一级的 ServerProperties.class 里面获取的,那么 ServerProperties 又是如何生成的呢?

上一篇文章前面提到了 @ConfigurationProperties(prefix="XXX") 标注了之后会从 application.properties 找到前缀为 XXX 的配置然后调用 set 方法进行配置注入。 ServerProperties.class 在创建之后会属性都是默认值,然后再经过 @ConfigurationProperties(prefix="XXX") 从配置文件中注入外部设置来覆盖默认设置。


具体从读取到 @ConfigurationProperties 再到读取 application.properties 注入设置的详细源码,一定会再详细介绍的。

现在对于 SpringBoot 的了解还是太浅了……

QAQ

自动装配总结

  1. SpringBoot 会首先获取所有自动装配运行类的名字,然后依次运行 XXXAutoConfiguration 的自动装配运行类,负责装配 XXX。
  2. 自动装配运行类通过 @Conditional 系列注解,判断满足要求之后开始从 XXXProperties.class 中拿到具体的配置信息。XXXProperties 和 application.properties 绑定。
  3. 自动装配运行类运行后会向 IOC 容器中添加很多配置好的组件,IOC 有了这些配置好的组件之后就拥有了对应的功能。
  4. 可以通过自己使用 @Bean 向 IOC 中添加配件来实现自定义配置,或者在 application.properties 中设置自定义配置。

自动装配开发技巧

  1. 开发的时候将对应的场景依赖引入即可,xxx-starter

  2. 查看场景依赖的哪些元素装配了,可以在设置中设置 debug=true,会打印日志,Positive 生效部分,Negative 不生效部分