深入理解springboot的自动配置「源码分析/图文详解」

目录

一、前言

二、原理分析

1、spring.factories的扫描

2、配置信息定义

3、pom依赖

4、tomcat的自动配置

5、Conditional注解

6、tomcat启动

7、结论分析

三、自定义组件

1、自定义spring.factories

2、自定义MyAutoConfiguration

3、自定义属性信息

4、自定义工厂类

5、创建触发条件

6、启动测试

7、控制台信息


一、前言

在使用springboot时,有一个大的原则:约定大于配置

springboot内置了很多常用的组件,比如tomcat、jdbc、redis等,接下来从根本上分析自动配置的原理及集成过程。

为更好的理解,先提出这几个疑问,也是我们后续分析的思路:

1、tomcat作为默认的服务容器,springboot是如何自动找到的tomcat;

2、jdbc、redis都需要配置链接信息,链接错误服务启动会报错,但没有配置则正常启动

3、我们如何按照自动配置的原则,实现自定义的组件进行装配。

二、原理分析

1、spring.factories的扫描

在《深入理解为什么nacos配置信息要放到bootstrap.properties「源码分析/图文详解」》一文中有详细分析:项目启动的时候,先加载系统信息,所有pom依赖jar包中的META-INF/spring.factories都会在这时进行扫描并缓存。

项目启动的debug调试信息如下,可以看出扫描spring.factories的优先级非常靠前:

2、配置信息定义

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的嵌入配置。

3、pom依赖

当springboot应用作为web项目进行启动时,需要添加pom依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

其中就包含了tomcat的依赖信息,依赖关系如下:

这里就开始体现springboot约定大于配置的思想,指定tomcat作为默认服务容器。

4、tomcat的自动配置

在spring.factories中web容器的初始化在ServletWebServerFactoryAutoConfiguration进行。

源码:

源码第一句注释:

Auto-configuration for servlet web servers. 

从这里可以看出,这是自动配置web服务的地方,同时也出现了Tomcat 的信息。 

5、Conditional注解

关于@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容器中。

6、tomcat启动

从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中并且启动。

7、结论分析

到这里再重新梳理下流程:

  • springboot扫描spring.factories中的配置信息;
  • 根据@Conditional条件选择性注入;
  • 加载属性信息,创建组件对象,并启动或应用

三、自定义组件

了解了自动装备的原理,我们自己实现一个。

1、自定义spring.factories

创建自定义的spring.factories文件

# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.zhufeng.demo.config.MyAutoConfiguration

2、自定义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);
    }
}

3、自定义属性信息

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

4、自定义工厂类

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());
    }
}

5、创建触发条件

package com.zhufeng.demo;

/**
 * @ClassName: HelloWorld
 * @Description 用于触发自动装配
 * @author 月夜烛峰
 * @date 2022/9/8 15:51
 */
public class HelloWorld {

}

6、启动测试

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";
    }
}

 启动项目运行。

7、控制台信息

浏览器访问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.

项目启动报错,自动装备失败。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月夜烛峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值