SpringBoot(二)

底层注解

1. @Configuration详解

基本使用

  • Full模式与Lite模式
  • 示例
    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
    /**
    * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
    * 2、配置类本身也是组件
    * 3、proxyBeanMethods:代理bean的方法
    * Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)全配置
    * Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)轻量级配置
    */
    @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
    public class MyConfig {

    /**
    * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
    * @return
    */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
    User zhangsan = new User("zhangsan", 18);
    //user组件依赖了Pet组件
    zhangsan.setPet(tomcatPet());
    return zhangsan;
    }

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

User.java

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
package com.nianxi.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author Jie.
* @description: TODO
* @date 2024/11/11
* @version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User {
private String name;
private Integer age;
private Pet pet;

public User(String zhangsan, int i) {
this.name = zhangsan;
this.age = i;
}
}

Pet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.nianxi.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author Jie.
* @description: TODO
* @date 2024/11/11
* @version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Pet {
private String name;
}

HelloApplication.java

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
package com.nianxi;

import com.nianxi.config.MyConfig;
import com.nianxi.entity.Pet;
import com.nianxi.entity.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
* @author Jie.
* @description: TODO
* @date 2024/10/21
* @version: 1.0
*/
@SpringBootApplication
public class HelloApplication {
// public static void main(String[] args) {
// SpringApplication.run(HelloApplication.class, args);
// }
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(HelloApplication .class, args);

//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

//3、从容器中获取组件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("组件:"+(tom01 == tom02));

//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);

//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
//保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);

User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);

System.out.println("用户的宠物:"+(user01.getPet() == tom));
}
}

以上就是我们Spring Boot 2.0最大的一个更新,即我们可以将配置类编写为轻量级模式和全模式这两种配置模式。

那么,将配置类编写为轻量级模式有什么优点呢?优点就是Spring Boot不会来检查组件注册的方法(例如user01方法)返回的东东在容器中有没有,相当于就跳过了检查,这样,我们整个Spring Boot应用启动并运行起来就非常快了。而如果是将配置类编写为全模式,那么外界对它里面的方法的每一次调用,Spring Boot都会检查容器中是不是有了该方法已经返回了的组件。

虽然Spring Boot给我们带来了Full和Lite这两种配置模式,但是在Spring Boot的最佳实践中,我们推荐的做法是这样子的——如果我们只是向容器中单单注册组件,而且也不存在组件依赖,那么一般使用Lite模式,因为这样的话,我们Spring Boot应用整个的启动速度就会非常快,加载起来也会非常快哟;如果存在组件依赖,那么一般使用Full模式,因为这样能保证某一个组件它所依赖的组件就是容器中的组件。

最佳实战

  • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
  • 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)

lite 英 [laɪt] 美 [laɪt]
adj. 低热量的,清淡的(light的一种拼写方法);类似…的劣质品

2. @Import导入组件

@Bean、@Component、@Controller、@Service、@Repository,它们是Spring的基本标签,在Spring Boot中并未改变它们原来的功能。

@ComponentScan 指定扫描路径

@Import({Pet.class, User.class})给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名

MyConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.nianxi.config;

import com.nianxi.entity.Pet;
import com.nianxi.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* @author Jie.
* @description: TODO
* @date 2024/11/11
* @version: 1.0
*/

@Import({Pet.class, User.class})
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}

HelloApplication.java

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(HelloApplication .class, args);
//5、获取组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);
for (String s : beanNamesForType) {
System.out.println(s);
}
Pet bean1 = run.getBean(Pet.class);
System.out.println(bean1);
}

3. @Conditional条件装配

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

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
@Configuration(proxyBeanMethods = true)
@ConditionalOnMissingBean(name = "tom")//没有tom名字的Bean时,MyConfig类的Bean才能生效。
public class MyConfig {

@Bean
public User user01(){
User zhangsan = new User("zhangsan", 18);
zhangsan.setPet(tomcatPet());
return zhangsan;
}

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

public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

boolean tom = run.containsBean("tom");
System.out.println("容器中Tom组件:"+tom);//false

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

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

4. @ImportResource导入Spring配置文件

比如,公司使用bean.xml文件生成配置bean,然而你为了省事,想继续复用bean.xml,@ImportResource粉墨登场。

bean.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="user" class="com.nianxi.entity.User">
<property name="name" value="zhangsan"/>
<property name="age" value="18"/>
</bean>
<bean id="pet" class="com.nianxi.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>

HelloApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(HelloApplication .class, args);
//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

boolean user = run.containsBean("user");
boolean pet = run.containsBean("pet");
System.out.println("user:"+user);//true
System.out.println("pet:"+pet);//true
}

5. @ConfigurationProperties配置绑定

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

传统方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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。
}
}
}

Spring Boot一种配置配置绑定:

@ConfigurationProperties + @Component

假设有配置文件application.properties

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

只有在容器中的组件,才会拥有SpringBoot提供的强大功能

1
2
3
4
5
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}

Spring Boot另一种配置配置绑定:

@EnableConfigurationProperties + @ConfigurationProperties

  1. 开启Car配置绑定功能
  2. 把这个Car这个组件自动注册到容器中
    1
    2
    3
    4
    @EnableConfigurationProperties(Car.class)
    public class MyConfig {
    ...
    }
    1
    2
    3
    4
    @ConfigurationProperties(prefix = "mycar")
    public class Car {
    ...
    }

自动配置[源码分析]

自动包规则原理

Spring Boot应用的启动类:

1
2
3
4
5
6
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}

分析下@SpringBootApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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 {
...
}

重点分析@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

1.@SpringBootConfiguration

1
2
3
4
5
6
7
8
9
10
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

@Configuration代表当前是一个配置类。

2.@ComponentScan

指定扫描哪些Spring注解。

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
---
}

3.@EnableAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

String[] excludeName() default {};
}

重点分析@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

1.@AutoConfigurationPackage

标签名直译为:自动配置包,指定了默认的包规则。

1
2
3
4
5
6
7
8
9
10
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)//给容器中导入一个组件
public @interface AutoConfigurationPackage {
String[] basePackages() default {};

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

2.@Import()之前讲过,用来给容器中自动创建对应类型组件

  1. 利用Registrar给容器中导入一系列组件
  2. 将指定的一个包下的所有组件导入进HelloApplication所在包下。

初始加载自动配置类

@Import(AutoConfigurationImportSelector.class)

  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

20241111194158

1
2
3
4
5
6
7
# 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
# spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...

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

AopAutoConfiguration类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = "auto",
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration {
public AopAutoConfiguration() {
}
...
}

自动配置流程

DispatcherServletAutoConfiguration的内部类DispatcherServletConfiguration为例子:

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

SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先

总结

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

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