SpringBoot2自动配置入门

SpringBoot特点

依赖管理

  • 父项目做依赖管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    依赖管理 
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.3</version>
    </parent>


    他的父项目
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.7.3</version>
    </parent>


    几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
  • 开发导入starter场景启动器

官网说明: https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

1
2
3
4
5
6
7
8
9
10
11
12
1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.3</version>
<scope>compile</scope>
</dependency>
  • 无需关注版本号,自动版本仲裁

    1
    2
    1、引入依赖默认都可以不写版本
    2、引入非版本仲裁的jar,要写版本号。
  • 可以修改默认版本号

    1
    2
    3
    4
    5
    1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
    2、在当前项目里面重写配置
    <properties>
    <mysql.version>5.1.43</mysql.version>
    </properties>

自动配置

  • 自动配置好的tomcat

    • 引入tomcat依赖
    • 配置tomcat
      1
      2
      3
      4
      5
      6
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.7.3</version>
      <scope>compile</scope>
      </dependency>
  • 自动配置好的SpringMVC

    • 引入SpringMVC全套组件
    • 自动配好SpringMVC常用组件(功能)

例如: 前端控制器DispatcherServlet:拦截所有的前端的请求;
字符编码characterEncodingFilter:解决返回中文字符串乱码问题;
视图解析器viewResolver:对返回的视图进行渲染呈现;
文件上传解析器multipatResolver:文件上传;

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
//1. 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(ManApplication.class, args);
//2. 查看容器中的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(Arrays.toString(names));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.22</version>
<scope>compile</scope>
</dependency>
  • 自动配好web常见的功能,如字符编码问题

    • Spring Boot 帮我们配置好了所有web开发时需要的场景
  • 默认包结构

    • 主程序所在的包都会被及其下边的所有子包里面的组件都会被扫描进来
    • 无需以前的包扫描配置
    • 如果想要改变扫描路径,那就可以使用 主程序上的注解 @SpringBootApplication(scanBasePackages = “需要扫描包的路径”)
      • 或者也可以 使用 注解 **@ComponentScan **扫描路径
        1
        2
        3
        4
        @SpringBootApplication 注解等同于下面三个注解
        @SpringBootConfiguration
        @EnableAutoConfiguration
        @ComponentScan({"指定的包扫描路径"})
  • 各种配置拥有默认值

    • 默认配置最终都是映射到某一个类上的 xxxProperties .java文件中
    • 配置文件的值最终会绑定到某个类上。这个类会在容器中有创建对象
  • 按需加载所有自动配置项

    • 非常多的starter
    • 引入了哪些场景这个场景的自动配置才会开启
    • Spring Boot 所有的自动配置功能都在spring-boot-autoconfigure包中
      1
      2
      3
      4
      5
      6
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.7.3</version>
      <scope>compile</scope>
      </dependency>
  • …..

容器功能

组件添加

@Configuration

可以理解为一个Configuration就是对应的一个Spring的xml版的容器
当@Configuration使用在类上时 注解中的属性 proxyBeanMethods 属性默认会为 true,则组件默认会为单例模式的 简单来说就是

proxyBeanMethods属性默认值是true,也就是说该配置类会被代理(CGLIB),在同一个配置文件中调用其它被@Bean注解标注的方法获取对象时会直接从IOC容器之中获取, 也开始单例模式;
注解的意思是proxyBeanMethods配置类是用来指定@Bean注解标注的方法是否使用代理,默认是true使用代理,直接从IOC容器之中取得对象;如果设置为false,也就是不使用注解,每次调用@Bean标注的方法获取到的对象和IOC容器中的都不一样,是一个新的对象,所以我们可以将此属性设置为false来提高性能;

Full 模式 和 Lite 模式 的区别
Full 全配置(proxyBeanMethods = true )
保证每个@Bean方法被调用多少次返回的组件都是单实例的
Lite 轻量级配置 (proxyBeanMethods = false)
每个@Bean方法被调用多少次返回的组件都是新创建的
配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#############################Configuration使用示例######################################################
/**
* @author 陶广
* @Configuration 告诉SpringBoot这是一个配置类 == ssm中的配置文件
* 1. 使用@Bean注解在方法上,在IOC容器中提供配置类方式祖册的组件默认都是单例模式
* 2. 被 @Configuration注解的类也会在SpringBoot中成为一个组件
* 3. proxyBeanMethods属性:代理bean的方法
* Full(全配置 proxyBeanMethods = true ) [保证每个@Bean方法被调用多少次返回的组件都是单实例的]、
* Lite(轻量级配置 proxyBeanMethods = false) [每个@Bean方法被调用多少次返回的组件都是新创建的]
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*/
@Configuration(proxyBeanMethods = false)
public class MyConfig {

/**
* 给容器中添加组件,以方法名作为组件的id,返回值就是组件类型,方法返回的对象就相当于在IOC容器中的实例
*/
@Bean()
public User user01() {
User zhangsan = new User("张三", 18);
//这里user组件依赖了pet组件
zhangsan.setPet(pet());
return zhangsan;
}


@Bean("tomcat")
public Pet pet() {
return new Pet("tomcat");
}

}
#############################Configuration测试代码######################################################
/**
* 主程序类
* @SpringBootApplication 告诉SpringBoot这是一个SpringBoot应用
*/
//@SpringBootApplication(scanBasePackages = "com.tao")
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan({"com.tao"})
public class ManApplication {
public static void main(String[] args) {
//1. 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(ManApplication.class, args);
//2. 查看容器中的组件
// String[] names = run.getBeanDefinitionNames();
// for (String name : names) {
// System.out.println(Arrays.toString(names));
// }
//3. 从容器中获取组件,在IOC容器中提供配置类方式祖册的组件默认都是单例模式
User user01 = run.getBean("user01",User.class);
User user02 = run.getBean("user01",User.class);
System.out.println(user01 == user02);

//4. com.tao.boot.config.MyConfig$$EnhancerBySpringCGLIB$$7cdb6c6c@648ee871
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);

//5. 如果 @Configuration 注解 的 属性 proxyBeanMethods = true 则SpringBoot总会检查这个组件是否在IOC容器中存在
//存在则不会创建新的对象,会使用之前的对象,保持单例模式
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);

//6.如果 组件 为 Lite模式 则 组件之间的依赖时不会相等的
//为Full模式之间之间的依赖会相等
User bean1 = run.getBean(User.class);
Pet pet = run.getBean(Pet.class);
System.out.println("组件依赖是否相等" + (bean1.getPet() == pet));
}
}

@Bean、@Component、@Controller、@Service、@Repository

@Import

导入装配:将需要的注册的bean对象组件注入(必须全名)

1
2
3
4
5
6
7
@Import({User.class, DBHelper.class})
//给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名

@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}

4.@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#############################Conditional使用示例######################################################
@Import({DBHelper.class})
@Configuration(proxyBeanMethods = true)
//@ConditionalOnBean(name = "user01") //当IOC容器中有 名为 user01 的组件时则 创建该组件
@ConditionalOnMissingBean(name = "user01")//当IOC容器中没有 名为 user01 的组件时则 创建该组件
public class MyConfig {

@Bean("tomcat")
public Pet pet() {
return new Pet("tomcat");
}

/**
* 给容器中添加组件,以方法名作为组件的id,返回值就是组件类型,方法返回的对象就相当于在IOC容器中的实例
*/
@ConditionalOnBean(name = "tomcat")
@Bean("user01")
public User user01() {
User zhangsan = new User("张三", 18);
//这里user组件依赖了pet组件
zhangsan.setPet(pet());
return zhangsan;
}

}

#############################Conditional测试代码#####################################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan({"com.tao"})
public class ManApplication {
public static void main(String[] args) {
//1. 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(ManApplication.class, args);

//7. 判断配置类(组件)中 是否包含某个组件
boolean tomcat = run.containsBean("tomcat");
System.out.println("容器中的tomcat组件:"+ tomcat);

boolean tomcat22 = run.containsBean("tomcat22");
System.out.println("容器中的tomcat22组件:"+ tomcat22);

boolean user01 = run.containsBean("user01");
System.out.println("容器中的user01组件:"+ user01);
}
}

原生配置(xml)导入

@ImportResource

原生配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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-3.0.xsd">

<bean id="test01" class="com.tao.boot.entity.User">
<property name="name" value="测试名称"/>
<property name="age" value="18"/>
<property name="pet" ref="test02"/>
</bean>
<bean id="test02" class="com.tao.boot.entity.Pet">
<property name="name" value="黄术"/>
</bean>

</beans>

导入并测试:

1
2
3
4
5
6
7
8
@ImportResource("classpath:beans.xml")
public class MyConfig {}

=============测试===================
boolean test01 = run.containsBean("test01");
boolean test02 = run.containsBean("test02");
System.out.println("test01:" + test01);
System.out.println("test02:" + test02);

配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封装到JavaBean。
}
}
}

car实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.tao.boot.entity;

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

public class Car {
private String brand;
private Double price;

public Car() {
}

public Car(String brand, Double price) {
this.brand = brand;
this.price = price;
}

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}

public Double getPrice() {
return price;
}

public void setPrice(Double price) {
this.price = price;
}

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}

在application.properties中配置实体类的几个字段

1
2
car.brand=BYD
car.price=100000

@ConfigurationProperties + @Component

在需要 Properties 的实体类上加上
先加上 @Component注册到IOC容器中成为组件,才能使用SpringBoot提供的功能
使用前@ConfigurationProperties,需要先指定配置前缀(属性 prefix)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//=========================配置绑定==================================
@Component
@ConfigurationProperties(prefix = "car")
public class Car {
private String brand;
private Double price;

public Car() {
}

public Car(String brand, Double price) {
this.brand = brand;
this.price = price;
}

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}

public Double getPrice() {
return price;
}

public void setPrice(Double price) {
this.price = price;
}

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
//=========================测试代码==================================
@RestController
public class HelloController {

@Resource
private Car car;

@RequestMapping("/getCar")
public Car car() {
return car;
}
}

@EnableConfigurationProperties + @ConfigurationProperties

用springboot开发的过程中,我们会用到**@ConfigurationProperties注解,主要是用来把properties或者yml配置文件转化为bean来使用的,而@EnableConfigurationProperties**注解的作用是@ConfigurationProperties注解生效。
如果只配置@ConfigurationProperties注解,在IOC容器中是获取不到properties配置文件转化的bean的,当然在@ConfigurationProperties加入注解的类上加@Component也可以使交于springboot管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//=============配置绑定==================
@ConfigurationProperties(prefix = "car")
public class Car {
private String brand;
private Double price;

public Car() {
}

public Car(String brand, Double price) {
this.brand = brand;
this.price = price;
}

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}

public Double getPrice() {
return price;
}

public void setPrice(Double price) {
this.price = price;
}

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}

//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {}




自动配置入门

引导加载自动配置类

@SpringBootConfiguration(SpringBoot配置)

标注是一个配置类
它是 @Configuration 注解的派生类注解
它与 @Configuration 注解功能一致
区别在于 @Configuration 是Spring 的注解 而@SpringBootConfiguration是SpringBoot的注解

@ComponentScan(组件扫描)

@Component 注解及其衍生注解 @RestController、@Controller、@Service、@Repository 都是组件注册注解
@ComponentScan 注解主要是从约定的扫描路径中,识别标注了组件注册注解的类,并且把这些类注入到IOC容器中去,这些类就是 Spring 中的bean
@Component 也会被IOC容器所托管

组件扫描路径
注解@ComponentScan 如果不设置value属性,默认扫描路径是启动类 XxxApplication.java 所在目录及其子目录,所以最好还是配置value属性,减少加载时间,提高系统启动速度。
比如启动类在包 com.test.web下面,那么项目启动时,会默认扫描web包及其子包下的所有类。也就是说,即便不明确标注@ComponentScan,Spring Boot也会自动搜索当前应用主入口目录及其下方子目录。如果其它包中的bean 不在当前主包路径下面,则应使用@ComponentScan设置value属性,配置扫描路径。如果定义了错误的扫描路径,那么在使用注解@Autowired自动装配Bean时会出错,报a bean of type that could not be found错误。

配置扫描路径
@ComponentScan注解既可以扫描包,也可以扫描指定的类。我们只需要指定一个需要扫描的路径,就可以达到更改扫描路径的目的。

  1. 包扫描

通过value属性设置需要扫描bean的包

@ComponentScan({“com.test.mapper”,”com.test.service”})
2. 类扫描

通过basePackageClasses属性指定需要扫描的类

@ComponenScan(basePackageClasses={xxxxMapper.class,xxxxController.class})

@EnableAutoConfiguration(启用自动配置)

指定了默认的包规则

1
2
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage

自动配置包,指定了默认的包规则

1
2
3
4
5
@Import(Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?xxxApplication.java 所在包下。

@AutoConfigurationPackage注解是指定了默认的包规则,即在该包下的组件才可以被Springboot扫描后自动装配(注册)进IOC容器中。
使用@import将AutoConfigurationPackages包下的Registrar类作为组件导入到容器中,然后使用Registrar中的方法批量完成组件的注册。

  1. 进入 源码后 Registrar.class debug registerBeanDefinitions 中去

image.png
image.png

  1. IDEA ctrl + f8 可以知道 new PackageImports(metadata).getPackageNames() 对应的值是 com.tao.boot 包

image.png

  1. 所以可以说明 @AutoConfigurationPackage 会被SpringBoot自动扫描指定包(如果没有指定会使用默认扫描xxxApplication.java同级包下所有的bean)路径下的所有bean 并注册到IOC容器中

@Import(AutoConfigurationImportSelector.class)

先理解源码怎么来的!!!

  1. 先进入 AutoConfigurationImportSelector.class 类中

image.png
image.png
image.png
image.png
image.png
image.png
image.png

1
2
3
4
5
6
7
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

按需开始自动配置项

虽然我们有100多个场景1所有自动配置启动的时候默认全部加载。xxxAutoConfiguration
按照条件装配规则(.@Conditional ),最终会按需配置

修改默认配置

1
2
3
4
5
6
7
8
9
10
@Bean
@ConditionalOnBean(MultipartResolver.class) //从IOC容器中找是否有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)// 从IOC容器中找是否有 名为 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方式传入了对象参数,这个参数就会去容器中找
//Spring MVC MultipartResolver 防止有些用户配置文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
//在容器中加入了文件上传解析器

Spring Boot 默认会在底层配好所有组件。但是如果用户自己配置了组件,那就以用户的为先

1
2
3
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {}

总结:

  • Spring Boot 先加载所有的自动配置类 xxxxAutoCongiguration
  • 每个自动配置类按照条件进行生效,默认绑定配置文件指定的值 。

@EnableConfigurationProperties(xxxxProperties.class) 这些配置文件的值都是从 xxxx Properties.class 中去拿
image.png

  • 生效的配置类就会给容器中装配很多的组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 只要用户有自己配置的,那么就以用户的优先
    • 定制化配置
      • 用户自己直接 使用@Bean替换底层的组件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        @Configuration
        @EnableConfigurationProperties(ServerProperties.class)
        public class MyCharset {

        private final Encoding properties;

        public MyCharset(ServerProperties properties) {
        this.properties = properties.getServlet().getEncoding();
        }

        @Bean
        public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("utf-8");
        filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
        return filter;
        }
        }

        xxxxxAutoConfiguration —> 自己定义的Bean里面拿值

      • 用户去看这个组件获取的配置文件实某些值,去Spring Boot(application.properties)文件中修改这些值

1
server.servlet.encoding.charset=utf-8

xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 —-> application.properties

最佳实践

  • 引入场景依赖
  • 查看自动配置了哪些(选做)
    • 自己分析,引入的场景对应的自动配置一般都生效了
    • 配置文件(application.properties)中开启debug=true 在 输出中有 Negative(不生效)\Positive(生效)
  • 是否需要修改
    • 参照文档修改配置项
      • 官网查找
      • 自己分析, xxxx Properties绑定的配置文件有哪些配置项

image.png

  • 自定义加入或者替换组件
    • 使用 @Bean 、@Configuration 、….自定义配置

image.png

  • 自定义器 xxxxCustomizer

简化开发