Springboot进阶知识

一.Springboot启动流程

由上图可以见到spring boot的整个启动流程和各个组件间的互相调用关系:

1.java程序由启动类主类调用main方法开始

2.调用SpringApplication的构造方法,实例一个Spring应用对象,在构造方法里主要完成启动环境初始化工作,如,推断主类,spring应用类型,加载配置文件,读取spring.factories文件等

3.调用run方法,所有的启动工作在该方法内完成,主要完成加载配置资源,准备上下文,创建上下文,刷新上下文,过程事件发布等

1.启动入口(SpringApplication)

java 复制代码
@SpringBootApplication
public class SummaryApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication();
        application.run(SummaryApplication.class, args); 
    }
}

1.1 @SpringBootApplication 注解

通过源码发现该注解只是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan 三个注解的组合

  • @EnableAutoConfiguration借助@Import的帮助,根据项目引入的 jar 依赖,筛选 Spring Boot 内置的自动配置类,把框架预设的功能型 Bean(如 Tomcat、数据源、MVC 核心组件)加载到 IoC 容器,实现 "有什么依赖就自动配什么功能"的自动化配置核心注解
  • @SpringBootConfiguration等价于@Configuration,就是将这个类标记韦配置类然后会被加载在容器中
  • @ComponentScan组件扫描,默认扫描当前引导类所在包及其子包

2.构造器(Constructor)

3.启动方法(RUN)

  1. new一个后序会陆续使用到的"启动上下文"BootstarpContext
  2. 启动无输入设备为true,表示缺少显示器键盘等输入设备也可以正常启动
  3. 启动"运行监听器"同时发布启动事件
  4. 环境准备阶段
  5. 创建springApplicationContext上下文
  6. 上下文初始化

二.SpringBoot自动配置原理

什么是自动装配:

我们现在提到自动装配的时候,一般会和 Spring Boot 联系在一起。但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。

SpringBoot自动装配可简单理解为:**根据项目中添加的依赖和其它因素,自动创建和配置Spring应用所需要的Bean。**这意味着开发者不需要编写大量的XML配置或Java配置类来设置Spring容器

由上一点可知@SpringBootApplication实际上是由三个注解组成,而与SpringBoot自动配置原理有关的注解是**@EnableAutoConfiguration**

下面我们来看@EnableAutoConfiguration的注解源码

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) //最为重要
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   Class<?>[] exclude() default {};

   String[] excludeName() default {};

}

@EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 **AutoConfigurationImportSelector**类。

那么AutoConfigurationImportSelector(加载自动装配类)到底做了什么?

java 复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}

public interface DeferredImportSelector extends ImportSelector {

}

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

可以看出,AutoConfigurationImportSelector 类实现了**ImportSelector**接口,也就实现了这个接口中的 selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中

java 复制代码
private static final String[] NO_IMPORTS = new String[0];

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // <1>.判断自动装配开关是否打开
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
          //<2>.获取所有需要装配的bean
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。

该方法的调用链如下:

现在我们结合**getAutoConfigurationEntry()**的源码来详细分析一下:

java 复制代码
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        //<1>.
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            //<2>.
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //<3>.
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //<4>.
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

第 1 步:

判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.propertiesapplication.yml 中设置

第 2 步

用于获取EnableAutoConfiguration注解中的 excludeexcludeName

第 3 步

获取需要自动装配的所有配置类,读取**META-INF/spring.factories**

第 4 步

到这里可能面试官会问你:"spring.factories中这么多配置,每次启动都要全部加载么?"。

很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了,因为,这一步有经历了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效

三.自定义starter

什么是starter?

SpringBoot里的starter就是用来简化开发的工具,主要解决依赖管理自动装配的问题;

它本质上是一个整合好的依赖包,里面包含了开发某个功能常用的所有依赖,不需要我们手动一个个去添加

**案例:**需求自定义redis-starter,要求当导入redis坐标时,SpringBoot会自动创建Jedis的Bean

案例:实现步骤

①创建redis-spring-boot-autoconfigure模块

②创建redis-spring-boot-starter模块,依赖redis-spring-boot-autoconfigure模块

③在redis-spring-boot-autoconfigure模块中初始化Jedis的Bean,并定义META-INF/spring.factories文件

具体步骤:

①创建两个spring-initialer的模块:redis-spring-boot-autoconfigure,redis-spring-boot-starter

②在redis-spring-boot-starter中引入redis-spring-boot-autoconfigure的依赖

XML 复制代码
        <!-- 引入我们自己的自动配置模块 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>redis-spring-boot-autoconfigure</artifactId>
            <version>1.0.0</version>
        </dependency>

③在redis-spring-boot-autoconfigure引入jedis的依赖

XML 复制代码
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.9.0</version>
        </dependency>

④编写RedisConfiguration.java

java 复制代码
package com.itheima.redis.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;

@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

    /**
     * 提供Jedis的bean
     */
    @Bean
    public Jedis jedis(RedisProperties redisProperties) {
        return new Jedis(redisProperties.getHost(), redisProperties.getPort());
    }
}

⑤用户的端口和主机地址需要用户自己指定(配置文件中),不应该写死,所以我们创佳RedisProperties 类的代码。这是一个简化版的 Redis 配置属性类:

java 复制代码
package com.itheima.redis.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "redis")
public class RedisProperties {

    private String host ="localhost";//当用户不配置的时候,我们自己给它一个默认值
    private int port =6379;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}
java 复制代码
# 你的申请单(application.yml)
redis:
  房间号: "101号房"    # ← 对应 host 字段
  楼层: 5              # ← 对应 port 字段

RedisAutoConfiguration 依赖 RedisProperties 来获取配置信息,然后创建 Jedis Bean 供其他组件使用。

⑥定义META-INF/spring.factories文件

java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.redis.autoconfigure.RedisConfiguration
相关推荐
你想知道什么?2 小时前
JNI简单学习(java调用C/C++)
java·c语言·学习
期待のcode2 小时前
Thymeleaf模板引擎
java·html·springboot
白宇横流学长2 小时前
基于SpringBoot实现的电子发票管理系统
java·spring boot·后端
白宇横流学长2 小时前
基于SpringBoot实现的智慧就业管理系统
java·spring boot·后端
weixin_462446232 小时前
EasyExcel 动态修改模板 Sheet 名称:自定义 SheetWriteHandler 拦截器
java·开发语言·easyexcel
赵庆明老师2 小时前
NET 使用SmtpClient 发送邮件
java·服务器·前端
苏小瀚2 小时前
[Java EE] HTML·CSS·JavaScript基础
java·java-ee
李拾叁的摸鱼日常2 小时前
Spring 框架中 RequestContextHolder 深度解析
java·架构
C++业余爱好者2 小时前
JVM优化入门指南:JVM垃圾收集器(GC)介绍
java·开发语言·jvm