• 深入理解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内容如下:

    1. # Auto Configure
    2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    3. ...省略...
    4. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
    5. org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
    6. org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
    7. org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    8. org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
    9. org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
    10. org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
    11. org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
    12. org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
    13. org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
    14. org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

    这里配置了es、redis、solr、mongodb等,其中也包含Tomcat的嵌入配置。

    3、pom依赖

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

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-webartifactId>
    4. 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容器中

    重点是这里

    1. @Bean
    2. @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    3. public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
    4. ServerProperties serverProperties) {
    5. return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    6. }

    意思是当org.apache.catalina.startup.Tomcat这个class类存在时,注入bean信息。

    从pom依赖关系中,已经倒入了tomcat相关依赖,所以这个@Bean自然会被注入到spring容器中。

    6、tomcat启动

    从Tomcat创建到启动的代码跟踪流程如下:

    TomcatServletWebServerFactory#getWebServer源码:

    1. @Override
    2. public WebServer getWebServer(ServletContextInitializer... initializers) {
    3. if (this.disableMBeanRegistry) {
    4. Registry.disableRegistry();
    5. }
    6. Tomcat tomcat = new Tomcat();
    7. File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    8. tomcat.setBaseDir(baseDir.getAbsolutePath());
    9. Connector connector = new Connector(this.protocol);
    10. connector.setThrowOnFailure(true);
    11. tomcat.getService().addConnector(connector);
    12. customizeConnector(connector);
    13. tomcat.setConnector(connector);
    14. tomcat.getHost().setAutoDeploy(false);
    15. configureEngine(tomcat.getEngine());
    16. for (Connector additionalConnector : this.additionalTomcatConnectors) {
    17. tomcat.getService().addConnector(additionalConnector);
    18. }
    19. prepareContext(tomcat.getHost(), initializers);
    20. return getTomcatWebServer(tomcat);
    21. }

    TomcatWebServer#initialize()源码:

    1. TomcatWebServer#initialize();
    2. private void initialize() throws WebServerException {
    3. logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    4. synchronized (this.monitor) {
    5. try {
    6. addInstanceIdToEngineName();
    7. Context context = findContext();
    8. context.addLifecycleListener((event) -> {
    9. if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
    10. // Remove service connectors so that protocol binding doesn't
    11. // happen when the service is started.
    12. removeServiceConnectors();
    13. }
    14. });
    15. // Start the server to trigger initialization listeners
    16. this.tomcat.start();
    17. // We can re-throw failure exception directly in the main thread
    18. rethrowDeferredStartupExceptions();
    19. try {
    20. ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
    21. }
    22. catch (NamingException ex) {
    23. // Naming is not enabled. Continue
    24. }
    25. // Unlike Jetty, all Tomcat threads are daemon threads. We create a
    26. // blocking non-daemon to stop immediate shutdown
    27. startDaemonAwaitThread();
    28. }
    29. catch (Exception ex) {
    30. stopSilently();
    31. destroySilently();
    32. throw new WebServerException("Unable to start embedded Tomcat", ex);
    33. }
    34. }
    35. }

    注意是这里:

    // Start the server to trigger initialization listeners
    this.tomcat.start();

    然后tomcat已经被内嵌到springboot中并且启动。

    7、结论分析

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

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

    三、自定义组件

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

    1、自定义spring.factories

    创建自定义的spring.factories文件

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

    2、自定义MyAutoConfiguration

    1. package com.zhufeng.demo.config;
    2. import com.zhufeng.demo.factory.HelloWorldFactory;
    3. import com.zhufeng.demo.prop.ZhufengProperties;
    4. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    5. import org.springframework.boot.context.properties.EnableConfigurationProperties;
    6. import org.springframework.context.annotation.Bean;
    7. import org.springframework.context.annotation.Configuration;
    8. /**
    9. * @ClassName: MyAutoConfiguration
    10. * @Description TODO
    11. * @author 月夜烛峰
    12. * @date 2022/9/8 15:32
    13. */
    14. @Configuration(proxyBeanMethods = false)
    15. @EnableConfigurationProperties(ZhufengProperties.class)
    16. public class MyAutoConfiguration {
    17. public MyAutoConfiguration(){
    18. System.out.println("MyAutoConfiguration 已经自动装配。。。");
    19. }
    20. @Bean
    21. @ConditionalOnClass(name = "com.zhufeng.demo.HelloWorld")
    22. public HelloWorldFactory showHelloWorld(
    23. ZhufengProperties zhufengProperties) {
    24. return new HelloWorldFactory(zhufengProperties);
    25. }
    26. }

    3、自定义属性信息

    1. package com.zhufeng.demo.prop;
    2. import org.springframework.boot.context.properties.ConfigurationProperties;
    3. /**
    4. * @ClassName: ZhufengProperties
    5. * @Description 自定义配置文件
    6. * @author 月夜烛峰
    7. * @date 2022/9/8 15:25
    8. */
    9. @ConfigurationProperties(prefix = "com.zhufeng")
    10. public class ZhufengProperties {
    11. private final ZhufengProperties.User user = new ZhufengProperties.User();
    12. private final ZhufengProperties.Msg msg = new ZhufengProperties.Msg();
    13. public User getUser() {
    14. return user;
    15. }
    16. public Msg getMsg() {
    17. return msg;
    18. }
    19. public static class User{
    20. private String id;
    21. private String name;
    22. public String getId() {
    23. return id;
    24. }
    25. public void setId(String id) {
    26. this.id = id;
    27. }
    28. public String getName() {
    29. return name;
    30. }
    31. public void setName(String name) {
    32. this.name = name;
    33. }
    34. @Override
    35. public String toString() {
    36. return "User{" +
    37. "id='" + id + '\'' +
    38. ", name='" + name + '\'' +
    39. '}';
    40. }
    41. }
    42. public static class Msg {
    43. private String type;
    44. private long length;
    45. public String getType() {
    46. return type;
    47. }
    48. public void setType(String type) {
    49. this.type = type;
    50. }
    51. public long getLength() {
    52. return length;
    53. }
    54. public void setLength(long length) {
    55. this.length = length;
    56. }
    57. @Override
    58. public String toString() {
    59. return "Msg{" +
    60. "type='" + type + '\'' +
    61. ", length=" + length +
    62. '}';
    63. }
    64. }
    65. }

    在application.properties中添加:

    1. com.zhufeng.user.id=user1001
    2. com.zhufeng.user.name=月夜烛峰
    3. com.zhufeng.msg.type=json
    4. com.zhufeng.msg.length=1024

    4、自定义工厂类

    1. package com.zhufeng.demo.factory;
    2. import com.zhufeng.demo.prop.ZhufengProperties;
    3. /**
    4. * @ClassName: MyFactory
    5. * @Description 工厂类
    6. * @author 月夜烛峰
    7. * @date 2022/9/8 15:43
    8. */
    9. public class HelloWorldFactory {
    10. private final ZhufengProperties zhufengProperties;
    11. public HelloWorldFactory(ZhufengProperties zhufengProperties){
    12. this.zhufengProperties = zhufengProperties;
    13. }
    14. public void showUserInfo(){
    15. System.out.println("用户信息:"+zhufengProperties.getUser());
    16. }
    17. public void showMsgInfo(){
    18. System.out.println("消息信息:"+zhufengProperties.getMsg());
    19. }
    20. }

    5、创建触发条件

    1. package com.zhufeng.demo;
    2. /**
    3. * @ClassName: HelloWorld
    4. * @Description 用于触发自动装配
    5. * @author 月夜烛峰
    6. * @date 2022/9/8 15:51
    7. */
    8. public class HelloWorld {
    9. }

    6、启动测试

    1. package com.zhufeng.demo;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. /**
    5. * @ClassName: MainTest
    6. * @Description 启动测试
    7. * @author 月夜烛峰
    8. * @date 2022/9/7 16:15
    9. */
    10. @SpringBootApplication
    11. public class MainTest {
    12. public static void main(String[] args) {
    13. SpringApplication.run(MainTest.class);
    14. }
    15. }

    创建测试Controller

    1. package com.zhufeng.demo.controller;
    2. import com.zhufeng.demo.factory.HelloWorldFactory;
    3. import org.springframework.web.bind.annotation.RequestMapping;
    4. import org.springframework.web.bind.annotation.RestController;
    5. import javax.annotation.Resource;
    6. /**
    7. * @ClassName: UserController
    8. * @Description TODO
    9. * @author 月夜烛峰
    10. * @date 2022/9/7 16:12
    11. */
    12. @RestController
    13. public class UserController {
    14. @Resource
    15. HelloWorldFactory showHelloWorld;
    16. @RequestMapping("view")
    17. public String view(){
    18. showHelloWorld.showUserInfo();
    19. showHelloWorld.showMsgInfo();
    20. return "success";
    21. }
    22. }

     启动项目运行。

    7、控制台信息

    浏览器访问http://127.0.0.1:8080/view

    控制台打印:

    1. MyAutoConfiguration 已经自动装配。。。
    2. showUserInfo:com.zhufeng.demo.service.impl.UserServiceImpl@63fd4873
    3. ......
    4. 用户信息:User{id='user1001', name='月夜烛峰'}
    5. 消息信息:Msg{type='json', length=1024}

    说明我们自定义的功能已经被自动装配,再把HelloWorld.java删除,使之不满足以下条件:

    @ConditionalOnClass(name = "com.zhufeng.demo.HelloWorld")

    1. ***************************
    2. APPLICATION FAILED TO START
    3. ***************************
    4. Description:
    5. A component required a bean of type 'com.zhufeng.demo.factory.HelloWorldFactory' that could not be found.
    6. The following candidates were found but could not be injected:
    7. - Bean method 'showHelloWorld' in 'MyAutoConfiguration' not loaded because @ConditionalOnClass did not find required class 'com.zhufeng.demo.HelloWorld'
    8. Action:
    9. Consider revisiting the entries above or defining a bean of type 'com.zhufeng.demo.factory.HelloWorldFactory' in your configuration.

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

  • 相关阅读:
    计算机考研 | 2016年 | 计算机组成原理真题
    【leetcode热题】杨辉三角 II
    只会grpc一把梭,可以开车上路吗?
    Nginx 面试 40 连问,顶不顶得住~~
    SSM出租车查询系统 毕业设计-附源码220915
    【操作系统导论】机制:受限直接执行 | 中断处理 | 陷阱 | 协作方式 | 非协作方式 | 上下文切换
    java进出口食品安全信息管理系统计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    面试-Redis-缓存雪崩
    [附源码]SSM计算机毕业设计社区医院管理系统JAVA
    世界港口数据获取
  • 原文地址:https://blog.csdn.net/yueyezhufeng/article/details/126766937