跳到正文
返回

SpringBoot原理

发表于 更新于
浏览量: --

封面画师:adsuger     封面ID:77171064

1. SpringBoot 原理

参考链接:Spring Boot参考指南

1.1 Hello World

1.2 运行原理初探

2.1 启动器

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

2.2 主启动类

@SpringBootApplication      //使用SpringBootApplication来标注这是一个主程序类
//说明这是一个Springboot应用
public class Springboot01HelloworldApplication {

    public static void main(String[] args) {
        //run方法不仅启动了一个方法,还启动了一个服务
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }
}

2.3 注解分析

2.3.1 @SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = { @Filter(
        type = FilterType.CUSTOM, 
        classes = TypeExcludeFilter.class
    ),@Filter(
            type = FilterType.CUSTOM, 
            classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    //......
}

2.3.2 @ComponentScan

2.3.3 @SpringBootConfiguration

@Configuration
public @interface SpringBootConfiguration {...}

//点击进入Configuration
@Component
public @interface Configuration {...}

回到@SpringBootApplication 注解中,查看@EnableAutoConfiguration 注解

2.3.4 @EnableAutoConfiguration

点击进入@EnableAutoConfiguration 注解:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}

点击进入@AutoConfigurationPackage 注解:

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

@import :Spring 底层注解@import,给容器中导入一个组件。

Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到 Spring 容器。


AutoConfigurationImportSelector:自动配置导入选择器。那么它会导入哪些组件的选择器呢?

点击进入 AutoConfigurationImportSelector 类:

  1. 这个类中有这样的一个方法:
//获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                  AnnotationAttributes attributes) {
    /**getSpringFactoriesLoaderFactoryClass()方法
     * 返回的是启动自动导入配置文件的注解类:EnableAutoConfiguration
     */
    List<String> configurations =
        SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                               getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}
//getSpringFactoriesLoaderFactoryClass()方法
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
  1. 这个方法又调用了 SpringFactoriesLoader 类的静态方法,进入 SpringFactoriesLoader 类中 loadFactoryNames() 方法:
public static List<String> loadFactoryNames(Class<?> factoryType, 
                                            @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    //此处调用了loadSpringFactories()方法
    return loadSpringFactories(classLoader).getOrDefault(
        factoryTypeName, Collections.emptyList());
}
  1. 点击 loadSpringFactories() 方法进行查看:
private static Map<String, List<String>> loadSpringFactories(
    @Nullable ClassLoader classLoader) {
    //获得classLoader,这里得到的就是EnableAutoConfiguration标注的类本身
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        //public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        //获取一个资源 "META-INF/spring.factories"
        Enumeration<URL> urls = (classLoader != null ?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();

        //将读到的资源遍历,封装成一个properties
        while (urls.hasMoreElements()) {	//判断有没有更多的元素
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);

            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();

                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +	FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
  1. 在这个类中我们发现了多次出现的文件:spring.factories,进行全局搜索

spring.factories

  1. 打开这个文件我们可以看到很多自动配置的文件,而这就是自动配置根源的所在

WebMvcAutoConfiguration

  1. 在这些配置文件中我们选取一个我们熟悉的配置类打开,比如:WebMvcAutoConfiguration

WebMvcAutoConfiguration_class

我们可以看到这些都是一个个的 JavaConfig 配置类,同时注入了一些 Bean。

所以,自动配置真正实现是从 classpath 中搜寻所有的 META-INF/spring.factories 配置文件,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration 的 JavaConfig 形式的 IoC 容器配置类,然后将这些都汇总成为一个实例并加载到 IoC 容器中。

自动装配的核心: 可以用 JavaConfig 类取代 xml 配置,并可以用 yaml 文件对 JavaConfig(标记@ConfigProperties)类的属性进行修改。


我们没有导入 AOP 的相关依赖,所以我们找到 AOP 的自动配置并打开:

autoConfiguration

打开后我们会发现有这样一个注解:

ConditionalOnClass

我们可以看到,@ConditionalOnClass 注解爆红!这是为什么?

答案很简单,因为我们没有导入 AOP 相关的依赖,我们需要导入依赖(对应的 starter)后这个注解才不会爆红,而只有这个注解不爆红,SpringBoot 的 AOP 自动配置才会生效。

@ConditionalOnxxxx:只有里面的条件都满足,自动配置才会生效。

理一下注解:

SpringBoot中常用的注解

到此,我们就大概的了解了 SpringBoot 的运行原理!


2.4 SpringApplication.run

构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

1.3 yaml 配置注入

3.1 基本语法

server:
	port: 8081

3.2 其他语法

k:
	v1: 
	v2: 

行内写法:

person: {name: mofan, age: 18}

注意缩进和空格

person:
	- student
	- teacher
	- doctor

行内写法:

person: [student,teacher,doctor]

3.3 配置文件注入

​ 当我们需要给实体类注入匹配值时,根据 Spring 的学习,我们可以:使用@Component 将 bean 注册到容器中,然后使用@Value 注解给 bean 的每个属性注入值。现在我们还可以用 yaml 配置的方式进行注入:

  1. 首先编写一个实体类,Person 类
/*
	@ConfigurationProperties作用:
	将配置文件中配置的每一个属性的值,映射到这个组件中;
	告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
	参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    
    //有参无参构造、get、set方法、toString()方法  
}
  1. 编写一个 yaml 配置:
person:
  name: mofan
  age: 18
  happy: true
  birth: 2000/01/01
  maps: {k1: v1,k2: v2}
  lists:
   - game
   - music
  dog:
    name: 小黑
    age: 3

然后我们在 SpringBoot 的测试类中编写测试即可。

@Autowired
private Person person;

void contextLoads(){
    System.out.println(person);
}

<!--导入依赖后需要重启-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

3.4 yaml总结

@ConfigurationProperties@Value
功能批量注入配置文件中的属性一个一个地指定属性值
松散绑定支持不支持
SpEL不支持支持
JSR303 校验支持不支持
复杂类型封装支持不支持

1.4 JSR303 数据校验

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @NotNull(message="名字不能为空")
    private String name;
}

如果这时 name 为空,则会抛出异常,并显示设置的 default message。

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间之前  
@Future     验证 Date 和 Calendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

等等...
我们也可以自定义一些校验规则

1.5 多环境切换

5.1 多配置文件

例如:

application-test.properties:测试环境配置

application-dev.properties:开发环境配置

但是 Springboot 并不会直接启动这些配置文件,它 默认使用 application.properties 主配置文件,我们需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

5.2 yaml多文档块

server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev #配置环境的名称


---

server:
  port: 8084
spring:
  profiles: prod  #配置环境的名称

5.3 配置文件加载顺序

优先级1:file:./config/				项目路径下的config文件夹配置文件
优先级2:file:./					项目路径下配置文件
优先级3:classpath:/config/			资源路径下的config文件夹配置文件
优先级4:classpath:/				资源路径下配置文件

优先级由高到低,高优先级的配置会覆盖低优先级的配置。SpringBoot 会从这四个位置全部加载主配置文件,互补配置。

1.6 自动装配原理


6.1 原理分析

我们在 2. 运行原理初探 中已经找到了文件 spring.factories 所处的位置,

这时我们可以选取 HttpEncodingAutoConfiguration(Http 编码自动配置) 为例解释自动配置原理:

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@Configuration 

//启动指定类的ConfigurationProperties功能
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //......
}

简单总结:SpringBoot 根据当前不同的条件判断,决定这个配置文件是否生效。

点击进入 HttpProperties.class

//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
    //....
}

然后我们可以在配置文件中进行 httpencoding 的相关配置,并进行对比:

HttpProperties

到此,我们就明白了自动装配的核心!

6.2 原理总结

  1. SpringBoot 启动会加载大量的自动配置类

  2. 我们判断我们需要的功能有没有在 SpringBoot 默认写好的自动配置类当中;

  3. 我们再来看这个自动配置类中到底配置了哪些组件(只要我们要用的组件存在在其中,我们就不需要再手动配置了);

  4. 给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可。

6.3 @Conditional

@Conditional 拓展注解作用(判断是否满足当前指定条件)
@ConditionalOnJava系统的 Java 版本是否符合要求
@ConditionalOnBean容器中存在指定的 Bean
@ConditionalOnMissingBean容器中不存在指定的 Bean
@ConditionalOnExpression满足 SpEL 表达式的指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的 Bean,或者这个 Bean 是首选 Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定的资源文件
@ConditionalOnWebApplication当前是 Web 环境
@ConditionalOnNotWebApplication当前不是 Web 环境
@ConditionalOnJndiJNDI 存在指定项

我们可以通过启用 debug=true 属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true

控制台会输出三大项:

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

1.7 自定义 Starter

我们先明白 Starter 的一些基础知识:

明白命名规约有助于帮助我们命名自定义启动器。

7.1 编写启动器

  1. 在 IDEA 中新建一个空项目 spring-boot-starter-diy;
  2. 完成第一步后,新建一个普通 Maven 模块:yang-spring-boot-starter

yang-spring-boot-starter

  1. 新建一个 Springboot 模块:yang-spring-boot-starter-autoconfigure
yang-spring-boot-starter-autoconfigure
  1. 新建好两个 Module 后,基本结构为

projectConstruction

  1. 在我们的 starter 中 导入 autoconfigure 的依赖:
<!-- 启动器 -->
<dependencies>
    <!--  引入自动配置模块 -->
    <dependency>
        <groupId>com.yang</groupId>
        <artifactId>yang-spring-boot-starter-autoconfigure</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>
  1. 将 autoconfigure 项目下多余的文件都删掉,Pom 中只留下一个 starter,这是所有的启动器基本配置

    注意:记得将 test 目录也删除,否则会安装到 maven 仓库时会失败!

delectFile

  1. 编写我们自己的服务:
/**
 * @author Mofan_Yang
 */
public class HelloService {
    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name){
        return helloProperties.getPrefix() + name + helloProperties.getSuffix();
    }
}
  1. 编写 HelloProperties 配置类:
/**
 * @author Mofan_Yang
 */
// 设置前缀 yang.hello
@ConfigurationProperties(prefix = "yang.hello")
public class HelloProperties {

    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}
  1. 编写自动配置类并注入 bean:
/**
 * @author Mofan_Yang
 */
@Configuration
@ConditionalOnWebApplication //web应用生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }
}
  1. 在 resources 编写一个自己的 META-INF\spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yang.HelloServiceAutoConfiguration
  1. 编写完成后,安装到 Maven 仓库
installToMaven

注意: 代码只在 yang-spring-boot-starter-autoconfigure 中编写,启动器 Starter 中没有任何代码。

7.2 测试Starter

  1. 新建一个 SpringBoot 项目;
  2. 导入我们自定义的启动器:
<dependency>
    <groupId>com.yang</groupId>
    <artifactId>yang-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
  1. 编写一个 HelloController 控制器,测试自定义的启动器:
@RestController
@RequestMapping("/test")
public class HelloController {
    @Autowired
    HelloService helloService;

    @RequestMapping("/hello")
    public String hello(){
        return helloService.sayHello("我是内容 ");
    }
}
  1. 编写配置文件 application.yml
yang:
  hello:
    prefix: "我是前缀 "
    suffix: "我是后缀 "
  1. 启动项目测试,查看结果

testStarter

自定义启动器编写成功!

如果这篇文章对你有帮助,可以通过
支付宝
支付宝
微信
微信
请我喝杯 Coffee ☕


上一篇
从 0 开始的 SpringMVC 学习
下一篇
Spring IoC 巩固理解