一 Spring Boot 入门
1.1 Spring Boot 简介
Spring Boot来简化Spring应用开发,约定大于配置,去繁从简,just run就能创建一个独立的,产品级别的应用。
即:
- 简化Spring应用开发的一个框架;
- 整个Spring技术栈的一个大整合;
- J2EE开发的一站式解决方案;
1.2 背景
J2EE笨重的开发、繁多的配置、低下的开发效率、复杂的部署流程、第三方技术集成难度大。
1.3. 解决
“Spring全家桶”时代;
Spring Boot ------>J2EE一站式解决方案;
Spring Cloud ------->分布式整体解决方案;
优点:
– 快速创建独立运行的Spring项目以及与主流框架集成。
– 使用嵌入式的Servlet容器,应用无需打成WAR包。
– starters自动依赖与版本控制。
– 大量的自动配置,简化开发,也可修改默认值。
– 无需配置XML,无代码生成,开箱即用。
– 准生产环境的运行时应用监控。
– 与云计算的天然集成。
二 微服务
2.1 简介
微服务,最早是有Martin Fowler与James Lewis于2014年共同提出的一个概念。是当前比较主流的一种软件设计架构。
简而言之,就是将一个大型的单机应用系统拆分成若干个小的服务,这些小的服务独立部署,服务与服务之间采用rpc/http轻量协议传输数据,服务间不具有强耦合性,从而实现了单个服务的高内聚,服务与服务之间低耦合的效果。这些一个一个细分的小服务就称之为微服务。
2.2 优点
优点:
- 便于开发,降低复杂度:各个团队中分别维护各自服务,不会出现等待的无用功的间隙便于开发,降低复杂度;各个团队中分别维护各自服务,不会出现等待的无用功的间隙。并且更新迭代的时候,不必再学习整个项目的业务代码,仅关注所在的服务模块即可。
- 业务解耦:各个服务间通过协议互相通讯,代码没有强耦合性,互不干扰。
- 独立部署:每个模块都可以单独部署,所以在上线新功能的时候只需发布相应的模块即可,既降低测试的工作量也降低了服务发布的风险(单服务情况下,新增需求可能就得把整个的流程测试再回归一下,并且上线失败的话整个项目都要回滚,而微服务则只需要回滚相应的模块即可)。
- 稳定性强;单个服务出现问题,其他服务仍可继续工作,这在技术潮流中是非常重要的一个进步,结合集群部署,我们完美的实现不停机更新。
- 扩展性强;可以根据不同服务的流量和压力,进行自主扩展即商品服务流量高,而订单服务流量低,则可以部署两个商品服务,一个订单服务,更加灵活。
详细参考微服务文档:https://martinfowler.com/articles/microservices.html#MicroservicesAndSoa
三 环境准备
3.1 环境约束
- jdk1.8:Spring Boot 推荐jdk1.7及以上;java version “1.8.0_112”
- maven3.x:maven 3.3以上版本;Apache Maven 3.8.4
- IntelliJIDEA2020:IntelliJ IDEA 2020.1 x64、STS
- SpringBoot 1.5.9.RELEASE:1.5.9;
统一环境;
3.2 MAVEN设置
给maven 的settings.xml配置文件的profiles标签添加
<profile>
<id>jdk‐1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
3.3 IDEA设置
整合maven进来
文件–>设置–>构建,执行,部署–>Maven
为了使项目所需依赖导入更快,我们可以让Maven导入阿里云,将原本的mirrors标签中的mirror标签内容注释掉,添加以下脚本。
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
四 Spring Boot HelloWorld
需求:浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串;
4.1 创建一个maven工程(jar)
4.2 导入spring boot相关的依赖
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
4.3 编写一个主程序并启动Spring Boot应用
package com.tedu.java;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/6/28 11:39
* @Description :@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
*/
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
// Spring应用启动起来
SpringApplication.run(HelloWorldApplication.class,args);
}
}
4.4 编写相关的Controller
package com.tedu.java.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/6/28 11:40
* @Description :
*/
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
4.5 运行主程序测试
启动成功:
4.6 简化部署
在pom.xml添加依赖
<!--这个插件,可以将应用打包成一个可执行的jar包;-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
</plugin>
</plugins>
</build>
打包:
在控制台进入到target目录下,使用java -jar命令运行该jar包:
运行成功:
五 Hello World探究
5.1 POM文件
5.1.1 父项目
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.1.RELEASE</version>
</parent>
它的父项目是:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
它来真正管理Spring Boot应用里面的所有依赖版本;
Spring Boot的版本仲裁中心;以后我们导入依赖默认是不需要写版本;(没有在dependencies里面管理的依赖自然需要声明版本号)。
5.1.2 启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring-boot-starter-web:springboot中web模块的启动器。
spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件;
Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器。
即:
- Spring Boot为我们提供了简化企业级开发绝大多数场景的starter pom(启动器),只要引入了相应场景的starter pom,相关技术的绝大部分配置将会消除(自动配置),从而简化我们开发。业务中我们就会使用到Spring Boot为我们自动配置的bean。
- 参考https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#using-boot-starter
- 这些starters几乎涵盖了javaee所有常用场景,Spring Boot对这些场景依赖的jar也做了严格的测试与版本控制。我们不必担心jar版本合适度问题。
- spring-boot-dependencies里面定义了jar包的版本。
5.2 主程序类,主入口类
package com.tedu.java;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/6/28 11:39
* @Description :@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
*/
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class,args);
}
}
@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
@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 {
}
@Configuration
public @interface SpringBootConfiguration {
}
@SpringBootConfiguration:Spring Boot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类;
@Configuration:配置类上来标注这个注解;即配置类 ----- 配置文件;配置类也是容器中的一个组件(@Component);
@EnableAutoConfiguration:开启自动配置功能;以前我们需要配置的东西,Spring Boot帮我们自动配置,@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@AutoConfigurationPackage:自动配置包;
@Import(AutoConfigurationPackages.Registrar.class):Spring的底层注解@Import,给容器中导入一个组件;导入的组件由AutoConfigurationPackages.Registrar.class将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;
@Import(EnableAutoConfigurationImportSelector.class):给容器中导入组件;
EnableAutoConfigurationImportSelector:导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中,会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
重点看@AutoConfigurationPackage注解和@Import(AutoConfigurationImportSelector.class)注解:
@AutoConfigurationPackage注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
看其@Import进来的类AutoConfigurationPackages.Registrar类:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
我们看registerBeanDefinitions方法中的register方法,在其方法上打断点:
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
可见是在启动类中配置的scanBasePackages属性的包。
方法进入到else代码块:
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
该步骤就是创建BeanDefinition对象,然后将其注册到BeanDefinition注册器中。我们看其注册的BeanDefinition对像的详情:
可以看到,这个BeanDefinition封装的是一个Package对象。所以,BeanDefinition不只能封装类的属性,还可以封装整个包的属性。
这样,就把包下的类,全部注册到了Spring容器中。
@Import(AutoConfigurationImportSelector.class):
看导入的AutoConfigurationImportSelector类:
这个方法中,将jar包中spring.factories文件信息加载到了cache缓存中。 这些配置的类,都是加入Spring容器中的。所以,配置的这些类,自动就在Spring容器中有了,无需我们再进行配置,这就是自动配置的原理。
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;以前我们需要自己配置的东西,自动配置类都帮我们;
J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-1.5.9.RELEASE.jar;
六 使用Spring Initializer快速创建Spring Boot项目
6.1 IDEA:使用 Spring Initializer快速创建项目
- 文件–>new–>Module
- Spring Initializr -->选择Jdk–> 选择阿里云仓库(https://start.aliyun.com/)–>下一步
- 填写组id等信息–>下一步
- 先默认添加spring web依赖,点击下一步
- 设置模块名称和路径
- 项目创建成功
IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目;
选择我们需要的模块;向导会联网创建Spring Boot项目;
默认生成的Spring Boot项目;
- 主程序已经生成好了,我们只需要我们自己的逻辑。
- resources文件夹中目录结构:
a) static:保存所有的静态资源; js css images。
b) templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf)。
c) application.properties:Spring Boot应用的配置文件;可以修改一些默认设置。
6.2 配置文件
6.2.1 配置文件
Spring Boot使用一个全局的配置文件,配置文件名是固定的:
- application.properties
- application.yml
配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;
以前的配置文件;大多都使用的是 xxxx.xml文件,YAML:以数据为中心,比json、xml等更适合做配置文件。
YAML:配置例子:
server:
port: 8081
XML:配置例子:
<server>
<port>8081</port>
</server>
6.2.2 YAML语法
- 基本语法
k:(空格)v:表示一对键值对(空格必须有)。
以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的。
server:
port: 8081
path: /hello
属性和值也是大小写敏感。
- 值的写法
字面量:普通的值(数字,字符串,布尔)。
字符串默认不用加上单引号或者双引号。
“”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思。
name: "zhangsan \n lisi":输出;zhangsan 换行 lisi
‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据。
name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
对象、Map(属性和值)(键值对):
k: v:在下一行来写对象的属性和值的关系;注意缩进。
对象还是k: v的方式。
friends:
lastName: zhangsan
age: 20
行内写法:
friends: {lastName: zhangsan,age: 18}
数组(List、Set):
用- 值表示数组中的一个元素
pets:
‐ cat
‐ dog
‐ pig
行内写法:
pets: [cat,dog,pig]
6.2.3 配置文件值注入
配置文件:application.yml
# 应用服务 WEB 访问端口
person:
lastName : 张三
age : 19
boss : false
birth : 2022-06-28
maps :
k1 : v1
k2 : v2
lists :
- lisi
- zhaoliu
dog :
name : 小狗
age : 8
server:
port: 8080
Person类:
package com.tedu.java.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/6/28 16:13
* @Description :将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "person":配置文件中哪个下面的所有属性进行一一映射
* 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private String birth;
private Map<String,Object> maps;
private List lists;
private Dog dog;
public Person() {
}
public Person(String lastName, Integer age, Boolean boss, String birth, Map<String, Object> maps, List lists, Dog dog) {
this.lastName = lastName;
this.age = age;
this.boss = boss;
this.birth = birth;
this.maps = maps;
this.lists = lists;
this.dog = dog;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getBoss() {
return boss;
}
public void setBoss(Boolean boss) {
this.boss = boss;
}
public String getBirth() {
return birth;
}
public void setBirth(String birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List getLists() {
return lists;
}
public void setLists(List lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"lastName='" + lastName + '\'' +
", age=" + age +
", boss=" + boss +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
Dog类:
package com.tedu.java.pojo;
import org.springframework.stereotype.Component;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/6/28 16:15
* @Description :
*/
@Component
public class Dog {
private String name;
private Integer age;
public Dog() {
}
public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
若有提示:
其实这个并不影响代码的编译,但是如果你想去掉它,可以在pom.xml中加入spring-boot-configuration-processor依赖即可!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
测试:
package com.tedu.java;
import com.tedu.java.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringBoot02ConfigApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
注:properties配置文件在idea中默认utf-8可能会乱码
6.2.4 @Value获取值和@ConfigurationProperties获取值比较
- | @ConfigurationProperties | @Value |
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
松散绑定(松散语法) | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
配置文件yml还是properties他们都能获取到值; | ||
总结: |
- 如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
- 如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;
6.2.5 @PropertySource&@ImportResource&@Bean
- @PropertySource:加载指定的配置文件;
package com.tedu.java.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/7/4 16:09
* @Description :
*/
@Component
@PropertySource("classpath:cat.properties")
public class Cat {
@Value("${cat.lastName}")
private String lastName;
@Value("${cat.age}")
private Integer age;
public Cat() {
}
public Cat(String lastName, Integer age) {
this.lastName = lastName;
this.age = age;
}
public String getLastName() {
return lastName;
}
@Override
public String toString() {
return "Cat{" +
"lastName='" + lastName + '\'' +
", age=" + age +
'}';
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Integer getAge() {
return age;
}
public void setId(Integer age) {
this.age = age;
}
}
在resource目录下创建cat.properties文件
cat.lastName=keNi
cat.age=12
测试:
@Autowired
private Cat cat;
@Test
public void test01(){
System.out.println(cat);
}
- @ConfigurationProperties
@ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
prefix = “person”:配置文件中哪个下面的所有属性进行一一映射
只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
@ConfigurationProperties(prefix = “person”)默认从全局配置文件中获取值; - @ImportResource
@ImportResource:导入Spring的配置文件,让配置文件里面的内容生效;
Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上
package com.tedu.java.pojo;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/7/4 16:20
* @Description :
*/
public class Pig {
private String pName;
private Integer pAge;
public Pig() {
}
public Pig(String pName, Integer pAge) {
this.pName = pName;
this.pAge = pAge;
}
public String getpName() {
return pName;
}
public void setpName(String pName) {
this.pName = pName;
}
public Integer getpAge() {
return pAge;
}
public void setpAge(Integer pAge) {
this.pAge = pAge;
}
@Override
public String toString() {
return "Pig{" +
"pName='" + pName + '\'' +
", pAge=" + pAge +
'}';
}
}
在启动类上加上@ImportResource(locations = “classpath:pig.xml”),使其配置文件生效
package com.tedu.java;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
@ImportResource(locations = "classpath:pig.xml")/*导入spring的配置文件让其生效*/
public class SpringBoot02ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoot02ConfigApplication.class, args);
}
}
在resource目录下创建pig.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pig" class="com.tedu.java.pojo.Pig">
<property name="pName" value="SmallPig"></property>
<property name="pAge" value="22"></property>
</bean>
</beans>
测试:
@Autowired
private ApplicationContext ac;
@Test
public void test02(){
System.out.println(ac.getBean("pig"));
}
- SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式
- 配置类@Configuration------>Spring配置文件。
- 使用@Bean给容器中添加组件。
package com.tedu.java.pojo;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/7/4 17:13
* @Description :
*/
public class HelloService {
}
package com.tedu.java.config;
import com.tedu.java.pojo.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Create with IntelliJ IDEA
*
* @author : zyy
* @date : 2022/7/4 17:14
* @Description :@Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件
* 在配置文件中用<bean><bean/>标签添加组件
*/
@Configuration
public class MyConfig {
//将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
@Bean
public HelloService helloService(){
System.out.println("配置类@Bean给容器中添加组件了...");
return new HelloService();
}
}
@Autowired
private HelloService helloService;
@Test
public void test03(){
System.out.println(helloService);
}
6.3 配置文件占位符
6.3.1 随机数
${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}
6.3.2 占位符获取之前配置的值,如果没有可以是用:指定默认值
person.last‐name=张三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15
6.4 Profile
6.4.1 多Profile文件
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml;
默认使用application.properties的配置;
6.4.2 yml支持多文档块方式
server:
port: 8081
spring:
profiles: prod
---
server:
port: 8083
spring:
profiles: dev
---
server:
port: 8084
spring:
profiles: test
6.4.3 激活指定profile
- 在配置文件中指定 spring.profiles.active=dev
- 命令行:java -jar xxxx.jar --spring.profiles.active=dev;
- 虚拟机参数;-Dspring.profiles.active=dev
6.5 配置文件加载位置
Springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
- –file:./config/
- –file:./
- –classpath:/config/
- –classpath:/
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件,互补配置;
我们还可以通过spring.config.location来改变默认的配置文件位置;
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=D:/application.properties
6.5.1. 外部配置加载顺序
SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配
- 命令行参数
所有的配置都可以在命令行上进行指定:
java -jar xxx.jar --server.port=8087 --server.context-path=/abc
#多个配置用空格分开; --配置项=值
- 来自java:comp/env的JNDI属性。
- Java系统属性(System.getProperties())。
- 操作系统环境变量。
- RandomValuePropertySource配置的random.*属性值。
由jar包外向jar包内进行寻找;优先加载带profile - jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件。
- jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件。
再来加载不带profile - jar包外部的application.properties或application.yml(不带spring.profile)配置文件。
- jar包内部的application.properties或application.yml(不带spring.profile)配置文件。
- @Configuration注解类上的@PropertySource。
- 通过SpringApplication.setDefaultProperties指定的默认属性。
所有支持的配置加载来源访问官方文档:https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#boot-features-external-config
6.5.1 测试
分别在 –file:./config/, –file:./, –classpath:/config/,–classpath:/创建application.properties文件,端口号分别为 8084,8083,8082,8081
resources下的application.properties加入server.context-path = /hello
server.port = 8081
server.context-path = /hello
controller下的HelloServer类
@RestController
public class HelloServer {
@RequestMapping("/hello")
public String hello(){
return "配置文件具有互补性";
}
}
访问:locahost:8084/hello 和 locahost:8084/hello/hello
6.5.2 源码解析
SpringBoot对于配置文件的加载是利用ConfigFileApplicationListener监听器来完成的。
对于每一个SpringBoot项目,都会有一个项目主程序启动类,在该类的main方法中调用SpringApplication.run();方法来启动SpringBoot程序,在启动过程中,便会有SpringBoot的一系列内部加载和初始化过程,因此该类对于我们的分析尤为重要。
点击进入到SpringApplication的主类中,观察其构造函数:
可以看到在构造器中对监听器进行了设置。其中getSpringFactoriesInstances方法用于从spring.factories文件中加载ApplicationListener实现类。该方法的加载原理如下所示,一直深入调用过程找到该方法,可以看到该方法主要就是去META-INF/spring.factories路径下加载spring.factories文件,并对文件进行解析。对该文件下的每一个接口及其实现类,以接口名为Key,包含的实现类名为value进行保存,形成一个Map>的数据结构进行返回。
然后对调用过程回推,在对spring.factories文件加载完,并保存了文件下每个接口及其实现类的全限定类名后,getSpringFactoriesInstances方法会根据传递的参数Class type来从上面得到的Map结构中拿出该接口名对应的所有实现类的集合。并对集合中的所有实现类,利用反射生成对应的实例进行保存:
到这里我们可以理清构造函数中setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));方法的过程和作用,即在SpringBoot启动初始化时,通过读取META-INF/spring.factories文件,并对其进行解析,生成示例化对象,然后从中取出ApplicationListener接口对应的所有实现类的实例对象,注入监听器中。
经过上面的分析我们可以看到,SpringBoot在启动时会初始化一系列监听器,而这些监听器都是:ApplicationListener接口下的,因此我们取到META-INF/spring.factories看一下有哪些实现类:
这里最关键的实现类就是ConfigFileApplicationListener,该监听器会监听ApplicationEnvironmentPreparedEvent事件,当监听到该事件后,会调用load方法,去上面说的四个默认路径检索配置文件,如果检索到了,则进行加载封装供上层方法调用。
这是它的一个大致的整体流程,接下来我们深入源码中,按步骤对其进行分析
6.5.3 发送事件与监听器的触发
6.5.3.1 ApplicationStartingEvent事件
首先进入到run方法中:
该方法中首先调用getRunListeners方法,同样是从spring.factories文件中加载SpringApplicationRunListeners接口下的实现类org.springframework.boot.context.event.EventPublishingRunListener,接下来调用该监听器的starting()方法
该starting()方法内,会创建一个ApplicationStartingEvent的事件,并利用multicastEvent方法进行广播该事件给应用中包含的所有监听器,这里的应用就是参数this.application,也就是现在的SpringApplication,它所包含的监听器也就是上文中最初加载的ApplicationListener下的11个监听器。可以看到,每个event对象下都包含一个source源,这个源表示了事件最初在其上发生的对象,这里的source源就是SpringApplication。
接下来,会进入到SimpleApplicationEventMulticaster类下的multicastEvent方法,这里比较重要的一个方法就是getApplicationListeners方法,该方法内部会根据该事件的类型,以及事件所包含的源里的监听器,筛选出对该事件感兴趣的监听器集合 。
选出来getApplicationListeners方法内的重要方法: retrieveApplicationListeners方法,该方法就是实际检索给定事件和源类型的应用程序侦听器,返回的listeners对象即包含了监听该事件的应用程序监听器集合。
这里的listeners即包含了最初ApplicationListeners接口下的11个监听器。
方法中的supportsEvent方法即判断给定监听器是否支持给定事件(或者说是否监听该事件)。由于目前这里的eventType表示的是ApplicationStartingEvent,该事件触发的监听器包括11个中的:
- BackgroundPreinitializer
- org.springframework.boot.context.logging.LoggingApplicationListener
- org.springframework.boot.context.config.DelegatingApplicationListener
- org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
最终当getApplicationListeners方法拿到监听器对象集合后,遍历得到每个监听器,然后调用invokeListener(listener, event);方法,再利用listener.onApplicationEvent(event)方法,通过调用相应监听器的onApplicationEvent(event)方法来唤醒监听器对象,执行相应的触发操作。
6.5.3.2 ApplicationEnvironmentPreparedEvent事件
执行完相应监听器的操作后,会继续回到run方法中执行prepareEnvironment方法,该方法同样是利用监听器和事件的机制,来触发监听完成环境准备的工作。
这里的listeners仍然是EventPublishingRunListener,因此这里的prepareEnvironment相当于是调用了该监听器的不同方法,来产生不同的事件类型,可以看到,这一次创建的事件类型为ApplicationEnvironmentPreparedEvent,也就是我们最开始说的加载配置文件的监听器所监听的事件类型,因此到这里我们就离探究配置文件加载原理又近了一步。创建该事件类型后,同样是利用multicastEvent将该事件广播给该应用程序下的所有监听器,其实它的流程就跟上面是一样的了,只是产生的事件不同。
因此,这里不在赘述该事件的触发流程,同样的是在retrieveApplicationListeners方法里的supportEvent方法中,筛选出支持ApplicationEnvironmentPreparedEvent事件的监听器集合并返回,而这次触发的监听器就包括了org.springframework.boot.context.config.ConfigFileApplicationListener监听器。由于监听器的真正执行是通过调用listener.onApplicationEvent(event)方法来执行的,因此我们从该方法开始分析:
这里loadPostProcessors方法就是从spring.factories中加载EnvironmentPostProcessor接口对应的实现类,并把当前对象也添加进去(因为ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口,所以可以添加)。因此在下方遍历时,会访问该类下的postProcessEnvironment方法。
接下来进入到postProcessEnvironment方法
接下来就是要分析最重要的Loader方法 :
该方法中,首先SpringFactoriesLoader.loadFactories从spring.factories中加载PropertySourceLoader接口对应的实现类,也就是
这两个实现类分别用于加载文件名后缀为properties和yaml的文件。
接下来最核心的方法是load方法,这里会最终加载我们的配置文件,因此我们进行深入探究:
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
首先调用了getSearchLocations方法
//获得加载配置文件的路径
//可以通过spring.config.location配置设置路径,如果没有配置,则使用默认
//默认路径由DEFAULT_SEARCH_LOCATIONS指定:
// CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"
// CONFIG_LOCATION_PROPERTY = "spring.config.location";
// DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"
private Set<String> getSearchLocations() {
Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
locations.addAll(getSearchLocations(CONFIG_LOCATION_PROPERTY));
}
else {
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
}
return locations;
}
该方法用于获取配置文件的路径,如果利用spring.config.location指定了配置文件路径,则根据该路径进行加载。否则则根据默认路径加载,而默认路径就是我们最初提到的那四个路径。接下来,再深入asResolvedSet方法内部分析一下:
private Set<String> asResolvedSet(String value, String fallback) {
List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
(value != null) ? this.environment.resolvePlaceholders(value) : fallback)));
Collections.reverse(list);
return new LinkedHashSet<>(list);
}
这里的value表示ConfigFileApplicationListener初始化时设置的搜索路径,而fallback就是DEFAULT_SEARCH_LOCATIONS默认搜索路径。StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray())方法就是以逗号作为分隔符对"classpath:/,classpath:/config/,file:./,file:./config/"进行切割,并返回一个字符数组。而这里的Collections.reverse(list);之后,就是体现优先级的时候了,先被扫描到的配置文件会优先生效。
这里我们拿到搜索路径之后,load方法里对每个搜索路径进行遍历,首先调用了getSearchNames()方法:
// 返回所有要检索的配置文件前缀
// CONFIG_NAME_PROPERTY = "spring.config.name"
// DEFAULT_NAMES = "application"
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
return asResolvedSet(property, null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
该方法中如果我们通过spring.config.name设置了要检索的配置文件前缀,会按设置进行加载,否则加载默认的配置文件前缀即application。
拿到所有需要加载的配置文件前缀后,则遍历每个需要加载的配置文件,进行搜索加载,加载过程如下:
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
//下面的if分支默认是不走的,除非我们设置spring.config.name为空或者null
//或者是spring.config.location指定了配置文件的完整路径,也就是入参location的值
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
//检查配置文件名的后缀是否符合要求,
//文件名后缀要求是properties、xml、yml或者yaml
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException("File extension of config file location '" + location
+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
+ "a directory, it must end in '/'");
}
Set<String> processed = new HashSet<>();
//propertySourceLoaders属性是在Load类的构造方法中设置的,可以加载文件后缀为properties、xml、yml或者yaml的文件
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
关注下面的两个for循环,this.propertySourceLoaders既包含了上面提到的两个PropertiesPropertySourceLoader和YamlPropertySourceLoader,PropertiesPropertySourceLoader可以加载文件扩展名为properties和xml的文件,YamlPropertySourceLoader可以加载文件扩展名为yml和yaml的文件。获取到搜索路径、文件名和扩展名后,就可以到对应的路径下去检索配置文件并加载了。
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) {
// Try profile-specific file & profile section in profile file (gh-340)
//在文件名上加上profile值,之后调用load方法加载配置文件,入参带有过滤器,可以防止重复加载
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// Also try the profile-specific section (if any) of the normal file
//加载不带profile的配置文件
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
// 加载配置文件
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
try {
//调用Resource类到指定路径加载配置文件
// location比如file:./config/application.properties
Resource resource = this.resourceLoader.getResource(location);
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped missing config ", location, resource,
profile);
this.logger.trace(description);
}
return;
}
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped empty config extension ", location,
resource, profile);
this.logger.trace(description);
}
return;
}
String name = "applicationConfig: [" + location + "]";
//读取配置文件内容,将其封装到Document类中,解析文件内容主要是找到
//配置spring.profiles.active和spring.profiles.include的值
List<Document> documents = loadDocuments(loader, name, resource);
//如果文件没有配置数据,则跳过
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
profile);
this.logger.trace(description);
}
return;
}
List<Document> loaded = new ArrayList<>();
//遍历配置文件,处理里面配置的profile
for (Document document : documents) {
if (filter.match(document)) {
//将配置文件中配置的spring.profiles.active和
//spring.profiles.include的值写入集合profiles中,
//上层调用方法会读取profiles集合中的值,并读取对应的配置文件
//addActiveProfiles方法只在第一次调用时会起作用,里面有判断
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
if (this.logger.isDebugEnabled()) {
StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
this.logger.debug(description);
}
}
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
}
}
该方法首先调用this.resourceLoader.getResource(location);用来判断location路径下的文件是否存在,如果存在,会调用loadDocuments方法对配置文件进行加载:
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
throws IOException {
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
再内部根据不同的PropertySourceLoader调用相应的load方法和loadProperties(resource)方法
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Map<String, ?> properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
return Collections
.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
return new OriginTrackedPropertiesLoader(resource).load();
}
由于我们目前的配置文件只有application.properties,也就是文件结尾不是以xml作为扩展名。因此loadProperties方法会进入到new OriginTrackedPropertiesLoader。因此再进入到new OriginTrackedPropertiesLoader(resource).load();。
Map<String, OriginTrackedValue> load(boolean expandLists) throws IOException {
try (CharacterReader reader = new CharacterReader(this.resource)) {
Map<String, OriginTrackedValue> result = new LinkedHashMap<>();
StringBuilder buffer = new StringBuilder();
while (reader.read()) {
String key = loadKey(buffer, reader).trim();
if (expandLists && key.endsWith("[]")) {
key = key.substring(0, key.length() - 2);
int index = 0;
do {
OriginTrackedValue value = loadValue(buffer, reader, true);
put(result, key + "[" + (index++) + "]", value);
if (!reader.isEndOfLine()) {
reader.read();
}
}
while (!reader.isEndOfLine());
}
else {
OriginTrackedValue value = loadValue(buffer, reader, false);
put(result, key, value);
}
}
return result;
}
}
CharacterReader(Resource resource) throws IOException {
this.reader = new LineNumberReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.ISO_8859_1));
}
private String loadKey(StringBuilder buffer, CharacterReader reader) throws IOException {
buffer.setLength(0);
boolean previousWhitespace = false;
while (!reader.isEndOfLine()) {
// 判断读取到的字节是否为'=' 或者为 ':',如果是则直接返回读取都的buffer内容
if (reader.isPropertyDelimiter()) {
reader.read();
return buffer.toString();
}
if (!reader.isWhiteSpace() && previousWhitespace) {
return buffer.toString();
}
previousWhitespace = reader.isWhiteSpace();
buffer.append(reader.getCharacter());
reader.read();
}
return buffer.toString();
}
private OriginTrackedValue loadValue(StringBuilder buffer, CharacterReader reader, boolean splitLists)
throws IOException {
buffer.setLength(0);
while (reader.isWhiteSpace() && !reader.isEndOfLine()) {
reader.read();
}
Location location = reader.getLocation();
while (!reader.isEndOfLine() && !(splitLists && reader.isListDelimiter())) {
buffer.append(reader.getCharacter());
reader.read();
}
Origin origin = new TextResourceOrigin(this.resource, location);
return OriginTrackedValue.of(buffer.toString(), origin);
}
在这个方法里,首先CharacterReader方法将我们的resource也就是配置文件转为了输入流,然后利用reader.read()进行读取,在loadKey方法中我们看到,这里判断读取到的是否为’=’ 或者为 ‘:’,也就是我们在配置文件中以’=‘或者’:'分割的key-value。因此看到这里,我们可以直观的感受到这里应该是读取配置文件,并切分key和value的地方。
最终,对配置文件读取完成后,会将其以key-value的形式封装到一个Map集合中进行返回,然后封装到OriginTrackedMapPropertySource中作为一个MapPropertySource对象。再层层往上回退发现会最终封装成一个asDocuments(loaded);Document对象。最后回到最上层的load方法中,loadDocuments(loader, name, resource);方法即返回我们加载好的配置文件Document对象集合。并对集合中的每一个配置文件document对象进行遍历,调用loaded.forEach((document) -> consumer.accept(profile, document));
6.5.3.3 总结
- SpringBoot在启动加载时,会利用事件-监听器模式,就像发布-订阅模式,在不同的阶段利用不同的事件唤醒相应的监听器执行对应的操作。对于配置文件加载关键的监听器是ConfigFileApplicationListener,该监听器会监听ApplicationEnvironmentPreparedEvent事件。
- 每个事件event都会包含一个source源来表示该事件最先发生在其上的对象,ApplicationEnvironmentPreparedEvent事件包含的source源是SpringApplication,包含了一组listeners监听器。SpringBoot会根据事件对监听器进行筛选,只筛选出那些支持该事件的监听器,并调用方法唤醒这些监听器执行相应逻辑。
- 当ApplicationEnvironmentPreparedEvent事件发生时,会唤醒ConfigFileApplicationListener监听器执行相应逻辑。最主要的加载方法load中,首先会获取到配置文件的搜索路径。如果设置了spring.config.location则会去指定目录下搜索,否则就去默认的搜索目录下classpath:/,classpath:/config/,file:./,file:./config/。
- 拿到所有待搜索目录后,遍历每个目录获取需要加载的配置文件。如果指定了spring.config.name,则加载指定名称的配置文件。否则使用默认的application作为配置文件的前缀名。然后,会利用PropertiesPropertySourceLoader和YamlPropertySourceLoader加载后缀名为properties、xml、yml或者yaml的文件。
- 拿到文件目录和文件名后,就可以去对应的路径下加载配置文件了。核心的过程是利用输入流读取配置文件,并根据读到的分隔符进行判断来切分配置文件的key和value。并将内容以key-value键值对的形式封装成一个OriginTrackedMapPropertySource,最后再将一个个配置文件封装成Document。最后遍历这些Documents,调用consumer.accept(profile, document));供上层调用访问。
七 自动配置原理
7.1 自动配置原理
springboot自动配置关键在于@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 {
}
前四个注解:是元注解,用来修饰当前注解,没有实际功能,实际上重要的是剩余三个注解。
7.1.1 @ComponentScan
@ComponentScan的功能其实就是自动扫描加载符合条件的组件(@Component,@Service,@Repository)或者用@Bean修饰加入到IOC容器中的组件。
我们可以通过basePackages等属性定制@ComponentScan自动扫描的范围,如果不指定,则默认会从启动类所在包开始扫描。
7.1.2 @SpringBootConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
起作用的只有@Configuration,所以@SpringBootConfiguration功能和@Configuration一样,将被注解类作为配置类,并交给spring容器管理。
7.1.3 @EnableAutoConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
除去元注解,还剩@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)
- @AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
/**
* Base packages that should be registered with {@link AutoConfigurationPackages}.
* <p>
* Use {@link #basePackageClasses} for a type-safe alternative to String-based package
* names.
* @return the back package names
* @since 2.3.0
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages to be
* registered with {@link AutoConfigurationPackages}.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the base package classes
* @since 2.3.0
*/
Class<?>[] basePackageClasses() default {};
}
起作用的是@Import(AutoConfigurationPackages.Registrar.class),继续查看源码:
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
我们是从启动类的@SpringBootApplication开始,所以元注解metadata中的数据可以定位到该启动类,而new PackageImports(metadata).getPackageNames()结果就是启动类所在的包,即在此包下springboot才会扫描各种文件,从而达到自动配置的目的。这也是为什么文件不能放在启动类上级目录中。
- @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector从字面意思看是自动配置导入选择器,即批量选择自动导入的内容。
- getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
private static final String[] NO_IMPORTS = {};
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
private ConfigurationClassFilter configurationClassFilter;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
- 调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
- 利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
- 从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件。
文件里面写死了spring-boot一启动就要给容器中加载的所有配置类:
# AutoConfigureCache auto-configuration imports
org.springframework.boot.test.autoconfigure.core.AutoConfigureCache=\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
# AutoConfigureDataJdbc auto-configuration imports
org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc=\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureDataJpa auto-configuration imports
org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa=\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureDataLdap auto-configuration imports
org.springframework.boot.test.autoconfigure.data.ldap.AutoConfigureDataLdap=\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration
# AutoConfigureDataMongo auto-configuration imports
org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo=\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureDataNeo4j auto-configuration imports
org.springframework.boot.test.autoconfigure.data.neo4j.AutoConfigureDataNeo4j=\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureDataR2dbc auto-configuration imports
org.springframework.boot.test.autoconfigure.data.r2dbc.AutoConfigureDataR2dbc=\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureDataRedis auto-configuration imports
org.springframework.boot.test.autoconfigure.data.redis.AutoConfigureDataRedis=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
# AutoConfigureJdbc auto-configuration imports
org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc=\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureTestDatabase auto-configuration imports
org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase=\
org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
# AutoConfigureJooq auto-configuration imports
org.springframework.boot.test.autoconfigure.jooq.AutoConfigureJooq=\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureJson auto-configuration imports
org.springframework.boot.test.autoconfigure.json.AutoConfigureJson=\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration
# AutoConfigureJsonTesters auto-configuration imports
org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters=\
org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration
# AutoConfigureWebClient auto-configuration imports
org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient=\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration
# AutoConfigureWebFlux auto-configuration imports
org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebFlux=\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
# AutoConfigureMockMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc=\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration
# AutoConfigureMockRestServiceServer
org.springframework.boot.test.autoconfigure.web.client.AutoConfigureMockRestServiceServer=\
org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerAutoConfiguration
# AutoConfigureRestDocs auto-configuration imports
org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs=\
org.springframework.boot.test.autoconfigure.restdocs.RestDocsAutoConfiguration
# AutoConfigureTestEntityManager auto-configuration imports
org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager=\
org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration
# AutoConfigureWebClient auto-configuration imports
org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient=\
org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration
# AutoConfigureWebMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
# AutoConfigureWebServiceClient
org.springframework.boot.test.autoconfigure.webservices.client.AutoConfigureWebServiceClient=\
org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
# AutoConfigureMockWebServiceServer
org.springframework.boot.test.autoconfigure.webservices.client.AutoConfigureMockWebServiceServer=\
org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerAutoConfiguration
# DefaultTestExecutionListenersPostProcessors
org.springframework.boot.test.context.DefaultTestExecutionListenersPostProcessor=\
org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener$PostProcessor
# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizerFactory,\
org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory
# Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\
org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener,\
org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener,\
org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener
7.2 按需开启自动配置项
虽然我们127个场景的所有自动配置启动的时候默认全部加载。但xxxxAutoConfiguration按照条件装配规则(@Conditional),最终会按需配置。
需要注意的是,SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先。
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}
即
自动配置类xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties
7.3 HttpEncodingAutoConfiguration(Http编码自动配置)
以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
* in web applications.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*/
//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)
/*启动指定类的,ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把
HttpEncodingProperties加入到ioc容器中*/
@EnableConfigurationProperties(ServerProperties.class)
/*Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效*/
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
/* 判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的;
即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
*/
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean//判断容器没有这个组件
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取
的,这些类里面的每一个属性又是和配置文件绑定的;
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就可以参照某个功
能对应的这个属性类。
@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件中获取指定的值和bean的属
性进行绑定
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF‐8");
}
7.4 细节
7.4.1 @Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
7.5 总结
- SpringBoot启动会加载大量的自动配置类;
- 我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
- 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了);
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;
八 日志
8.1 日志框架
市面上的日志框架:
JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j…
日志门面 (日志的抽象层) | 日志实现 |
---|---|
JCL(Jakarta Commons Logging) SLF4j(Simple LoggingFacade for Java) jboss-logging | Log4j JUL(java.util.logging)Log4j2 Logback |
SpringBoot:底层是Spring框架,Spring框架默认是用JCL;
SpringBoot选用 SLF4j和logback;
8.2 SLF4j使用
8.2.1 如何在系统中使用SLF4j
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统里面导入slf4j的jar和 logback的实现jar
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
图示:
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
8.2.2 不同的框架使用统一日志
a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx统一日志记录
如何让系统中所有的日志都统一到slf4j?
- 将系统中其他日志框架先排除出去;
- 用中间包来替换原有的日志框架;
- 导入slf4j其他的实现;
8.3 SpringBoot日志关系
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter</artifactId>
</dependency>
SpringBoot使用它来做日志功能:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐logging</artifactId>
</dependency>
底层依赖关系:
总结:
- SpringBoot底层也是使用slf4j+logback的方式进行日志记录;
- SpringBoot也把其他的日志都替换成了slf4j;
- 中间替换包;
@SuppressWarnings("rawtypes")
public abstract class LogFactory {
static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J =
"http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";
static LogFactory logFactory = new SLF4JLogFactory();
- 如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉。
Spring框架用的是commons-logging;
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring‐core</artifactId>
<exclusions>
<exclusion>
<groupId>commons‐logging</groupId>
<artifactId>commons‐logging</artifactId>
</exclusion>
</exclusions>
</dependency>
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;
8.4 日志使用
8.4.1 默认配置
SpringBoot默认帮我们配置好了日志:
Logger logger = LoggerFactory.getLogger(getClass());
@Test
void contextLoads() {
//日志的级别;
//由低到高 trace<debug<info<warn<error
//可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
logger.trace("这是trace日志...");
logger.debug("这是debug日志...");
//SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别
logger.info("这是info日志...");
logger.warn("这是warn日志...");
logger.error("这是error日志...");
}
SpringBoot修改日志的默认配置:
ogging.level.com.atguigu=trace
#logging.path=
# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
#logging.file=G:/springboot.log
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
logging.file.path=/spring/log
8.4.2 指定配置
给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了。
Logging System | Customization |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
logback.xml:直接就被日志框架识别了;
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能;
<springProfile name="staging">
<!‐‐ configuration to be enabled when the "staging" profile is active ‐‐>
可以指定某段配置只在某个环境下生效
</springProfile>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!‐‐
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%‐5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
‐‐>
<layout class="ch.qos.logback.classic.PatternLayout">
<springProfile name="dev">
<pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ‐‐‐‐> [%thread] ‐‐‐> %‐5level
%logger{50} ‐ %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} ==== [%thread] ==== %‐5level
%logger{50} ‐ %msg%n</pattern>
</springProfile>
</layout>
</appender>
如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误:
no applicable action for [springProfile]
8.5 切换日志框架
可以按照slf4j的日志适配图,进行相关的切换;
slf4j+log4j的方式:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐web</artifactId>
<exclusions>
<exclusion>
<artifactId>logback‐classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
<exclusion>
<artifactId>log4j‐over‐slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j‐log4j12</artifactId>
</dependency>
切换为log4j2:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring‐boot‐starter‐logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐log4j2</artifactId>
</dependency>