目录
在使用springboot时,有一个大的原则:约定大于配置。
springboot内置了很多常用的组件,比如tomcat、jdbc、redis等,接下来从根本上分析自动配置的原理及集成过程。
为更好的理解,先提出这几个疑问,也是我们后续分析的思路:
1、tomcat作为默认的服务容器,springboot是如何自动找到的tomcat;
2、jdbc、redis都需要配置链接信息,链接错误服务启动会报错,但没有配置则正常启动
3、我们如何按照自动配置的原则,实现自定义的组件进行装配。
在《深入理解为什么nacos配置信息要放到bootstrap.properties「源码分析/图文详解」》一文中有详细分析:项目启动的时候,先加载系统信息,所有pom依赖jar包中的META-INF/spring.factories都会在这时进行扫描并缓存。
项目启动的debug调试信息如下,可以看出扫描spring.factories的优先级非常靠前:

springboot自动配置的信息,在系统所示的jar包中的spring.factoires中都进行的定义。

部分spring.factoryes内容如下:
- # Auto Configure
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- ...省略...
- org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
- org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
- org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
- org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
- org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
- org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
- org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
- org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
- org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
- org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
- org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
这里配置了es、redis、solr、mongodb等,其中也包含Tomcat的嵌入配置。
当springboot应用作为web项目进行启动时,需要添加pom依赖:
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
其中就包含了tomcat的依赖信息,依赖关系如下:

这里就开始体现springboot约定大于配置的思想,指定tomcat作为默认服务容器。
在spring.factories中web容器的初始化在ServletWebServerFactoryAutoConfiguration进行。
源码:

源码第一句注释:
Auto-configuration for servlet web servers.
从这里可以看出,这是自动配置web服务的地方,同时也出现了Tomcat 的信息。
关于@Conditional注解:
@Conditional注解是一个条件装配注解,主要用于限制@Bean注解在什么时候才生效,以指定的条件形式控制bean的创建
@Conditional可以自定义条件进行装配或者不装配
@Conditional本身是一个父注解,派生出很多子注解:@ConditionalOnClass和@ConditionalOnWebApplication就是其中的子项!
ServletWebServerFactoryAutoConfiguration上使用的注解较多,其它注解作用分别如下:
@EnableConfigurationProperties:
使配置文件中配置的信息生效,如端口配置 server.port=8081
@Import
通过快速导入的方式实现把实例加入spring的IOC容器中
重点是这里
- @Bean
- @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
- public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
- ServerProperties serverProperties) {
- return new TomcatServletWebServerFactoryCustomizer(serverProperties);
- }
意思是当org.apache.catalina.startup.Tomcat这个class类存在时,注入bean信息。
从pom依赖关系中,已经倒入了tomcat相关依赖,所以这个@Bean自然会被注入到spring容器中。
从Tomcat创建到启动的代码跟踪流程如下:

TomcatServletWebServerFactory#getWebServer源码:
- @Override
- public WebServer getWebServer(ServletContextInitializer... initializers) {
- if (this.disableMBeanRegistry) {
- Registry.disableRegistry();
- }
- Tomcat tomcat = new Tomcat();
- File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
- tomcat.setBaseDir(baseDir.getAbsolutePath());
- Connector connector = new Connector(this.protocol);
- connector.setThrowOnFailure(true);
- tomcat.getService().addConnector(connector);
- customizeConnector(connector);
- tomcat.setConnector(connector);
- tomcat.getHost().setAutoDeploy(false);
- configureEngine(tomcat.getEngine());
- for (Connector additionalConnector : this.additionalTomcatConnectors) {
- tomcat.getService().addConnector(additionalConnector);
- }
- prepareContext(tomcat.getHost(), initializers);
- return getTomcatWebServer(tomcat);
- }
TomcatWebServer#initialize()源码:
- TomcatWebServer#initialize();
- private void initialize() throws WebServerException {
- logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
- synchronized (this.monitor) {
- try {
- addInstanceIdToEngineName();
-
- Context context = findContext();
- context.addLifecycleListener((event) -> {
- if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
- // Remove service connectors so that protocol binding doesn't
- // happen when the service is started.
- removeServiceConnectors();
- }
- });
-
- // Start the server to trigger initialization listeners
- this.tomcat.start();
-
- // We can re-throw failure exception directly in the main thread
- rethrowDeferredStartupExceptions();
-
- try {
- ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
- }
- catch (NamingException ex) {
- // Naming is not enabled. Continue
- }
-
- // Unlike Jetty, all Tomcat threads are daemon threads. We create a
- // blocking non-daemon to stop immediate shutdown
- startDaemonAwaitThread();
- }
- catch (Exception ex) {
- stopSilently();
- destroySilently();
- throw new WebServerException("Unable to start embedded Tomcat", ex);
- }
- }
- }
注意是这里:
// Start the server to trigger initialization listeners
this.tomcat.start();
然后tomcat已经被内嵌到springboot中并且启动。
到这里再重新梳理下流程:
了解了自动装备的原理,我们自己实现一个。
创建自定义的spring.factories文件
- # Auto Configuration
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.zhufeng.demo.config.MyAutoConfiguration
- package com.zhufeng.demo.config;
-
- import com.zhufeng.demo.factory.HelloWorldFactory;
- import com.zhufeng.demo.prop.ZhufengProperties;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
- import org.springframework.boot.context.properties.EnableConfigurationProperties;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- /**
- * @ClassName: MyAutoConfiguration
- * @Description TODO
- * @author 月夜烛峰
- * @date 2022/9/8 15:32
- */
- @Configuration(proxyBeanMethods = false)
- @EnableConfigurationProperties(ZhufengProperties.class)
- public class MyAutoConfiguration {
-
- public MyAutoConfiguration(){
- System.out.println("MyAutoConfiguration 已经自动装配。。。");
- }
-
- @Bean
- @ConditionalOnClass(name = "com.zhufeng.demo.HelloWorld")
- public HelloWorldFactory showHelloWorld(
- ZhufengProperties zhufengProperties) {
- return new HelloWorldFactory(zhufengProperties);
- }
- }
- package com.zhufeng.demo.prop;
-
- import org.springframework.boot.context.properties.ConfigurationProperties;
-
- /**
- * @ClassName: ZhufengProperties
- * @Description 自定义配置文件
- * @author 月夜烛峰
- * @date 2022/9/8 15:25
- */
-
- @ConfigurationProperties(prefix = "com.zhufeng")
- public class ZhufengProperties {
-
- private final ZhufengProperties.User user = new ZhufengProperties.User();
- private final ZhufengProperties.Msg msg = new ZhufengProperties.Msg();
-
- public User getUser() {
- return user;
- }
-
- public Msg getMsg() {
- return msg;
- }
-
- public static class User{
- private String id;
-
- private String name;
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- @Override
- public String toString() {
- return "User{" +
- "id='" + id + '\'' +
- ", name='" + name + '\'' +
- '}';
- }
- }
-
- public static class Msg {
-
- private String type;
- private long length;
-
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
-
- public long getLength() {
- return length;
- }
-
- public void setLength(long length) {
- this.length = length;
- }
-
- @Override
- public String toString() {
- return "Msg{" +
- "type='" + type + '\'' +
- ", length=" + length +
- '}';
- }
- }
- }
在application.properties中添加:
- com.zhufeng.user.id=user1001
- com.zhufeng.user.name=月夜烛峰
-
- com.zhufeng.msg.type=json
- com.zhufeng.msg.length=1024
- package com.zhufeng.demo.factory;
-
- import com.zhufeng.demo.prop.ZhufengProperties;
-
- /**
- * @ClassName: MyFactory
- * @Description 工厂类
- * @author 月夜烛峰
- * @date 2022/9/8 15:43
- */
- public class HelloWorldFactory {
-
- private final ZhufengProperties zhufengProperties;
-
- public HelloWorldFactory(ZhufengProperties zhufengProperties){
-
- this.zhufengProperties = zhufengProperties;
- }
-
- public void showUserInfo(){
- System.out.println("用户信息:"+zhufengProperties.getUser());
- }
-
- public void showMsgInfo(){
- System.out.println("消息信息:"+zhufengProperties.getMsg());
- }
- }
- package com.zhufeng.demo;
-
- /**
- * @ClassName: HelloWorld
- * @Description 用于触发自动装配
- * @author 月夜烛峰
- * @date 2022/9/8 15:51
- */
- public class HelloWorld {
-
- }
- package com.zhufeng.demo;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- /**
- * @ClassName: MainTest
- * @Description 启动测试
- * @author 月夜烛峰
- * @date 2022/9/7 16:15
- */
- @SpringBootApplication
- public class MainTest {
- public static void main(String[] args) {
- SpringApplication.run(MainTest.class);
- }
- }
创建测试Controller
- package com.zhufeng.demo.controller;
-
- import com.zhufeng.demo.factory.HelloWorldFactory;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- import javax.annotation.Resource;
-
- /**
- * @ClassName: UserController
- * @Description TODO
- * @author 月夜烛峰
- * @date 2022/9/7 16:12
- */
- @RestController
- public class UserController {
-
- @Resource
- HelloWorldFactory showHelloWorld;
-
- @RequestMapping("view")
- public String view(){
- showHelloWorld.showUserInfo();
- showHelloWorld.showMsgInfo();
- return "success";
- }
- }
启动项目运行。
浏览器访问http://127.0.0.1:8080/view
控制台打印:
- MyAutoConfiguration 已经自动装配。。。
- showUserInfo:com.zhufeng.demo.service.impl.UserServiceImpl@63fd4873
- ......
- 用户信息:User{id='user1001', name='月夜烛峰'}
- 消息信息:Msg{type='json', length=1024}
说明我们自定义的功能已经被自动装配,再把HelloWorld.java删除,使之不满足以下条件:
@ConditionalOnClass(name = "com.zhufeng.demo.HelloWorld")
- ***************************
- APPLICATION FAILED TO START
- ***************************
-
- Description:
-
- A component required a bean of type 'com.zhufeng.demo.factory.HelloWorldFactory' that could not be found.
-
- The following candidates were found but could not be injected:
- - Bean method 'showHelloWorld' in 'MyAutoConfiguration' not loaded because @ConditionalOnClass did not find required class 'com.zhufeng.demo.HelloWorld'
-
-
- Action:
-
- Consider revisiting the entries above or defining a bean of type 'com.zhufeng.demo.factory.HelloWorldFactory' in your configuration.
项目启动报错,自动装备失败。