SpringBoot注解与自动配置原理

本文最后更新于:2022年7月19日 下午

概览:SpringBoot自动配置

SpringBoot的优点

  • 可以创建独立的Spring应用
  • 内嵌web服务器,可以将jar包直接在目标服务器上执行
  • 自动starter依赖,简化构建配置
  • 自动配置spring以及第三方功能
  • 整合了spring技术栈,也是简化spring技术栈的快速开发脚手架

Hello World

依赖

1
2
3
4
5
6
7
8
9
10
11
12
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>

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

主程序

1
2
3
4
5
6
7
8
9
10
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainApplication {

public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}

配置

1
2
3
4
# 文件需要是 application.properties 文件

# 设置端口号
server.port=8888

starter 场景启动器

springBoot官方的starter命名规则:spring-boot-starter-*

  • 只要引入了starter,这个场景下的所有常规需要的依赖都会自动引入

所有的场景启动器最底层的依赖都是spring-boot-starter

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
  • 引入的默认依赖是可以不些版本的,有默认的版本

    • 可以修改默认版本,在pom文件中修改覆盖即可。
1
2
3
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
  • 普通的依赖是需要指定版本的。

SpringBoot自动配置特性

  • 会自动引入对应的依赖,配置好属性。

例如:springMVC会自动配置好web常见的问题:字符编码问题

  • 默认的包结构:默认会将MainApplication所在包以及其下面的所有的子包组件扫描进去,无需包扫描配置
    • 改变扫描:@SpringBootApplication(scanBasePackages="com.lun")
  • 各种配置都有默认值
    • 默认配置都最终映射到了某个类上,xxxProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象。
  • 按需加载自动配置项
    • @Conditional注解
    • 引入了哪个场景,哪个场景的自动配置才会开启。
    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

SpringBoot重要注解

1.@SpringBootApplication

这个注解相当于三个注解

1
2
3
4
5
6
7
8
9
10
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}

2.@Configuration与@Bean

@Configuration这个注解的作用是告诉SpringBoot这个类是一个配置类,当然这个配置类本身也是一个组件。

  • 属性:proxyBeanMethods:代理bean的方法
    •  Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是**单实例的**)(默认)
    •  Lite(proxyBeanMethods = **false**)(每个@Bean方法被调用多少次返回的组件都是**新创建的**)

@Bean用于方法和注解上,给容器添加组件,默认使用方法名作为组件的id,方法的返回类型就是组件类型,返回值就是组件在容器中的实例。

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

案例:mybatis-plus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class MyBatisPlusConfig {
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false
* 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setDbType(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}

@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}

3.@Import导入组件

1
@Import({User.class, DBHelper.class})

给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名。

4.@Conditional条件装配

满足Conditional指定的条件,则进行组件注入。

1
2
3
4
5
@Configuration
// 当没有叫tom的bean时,这个配置类才会注入
@ConditionalOnMissingBean(name = "tom")
public class MyConfig(){
}

5.@ImportResource引入Spring配置文件

对于一些旧的xml的bean的文件,可以使用这个注解导入。

1
2
3
@ImportResource("classpath:beans.xml")
public class MyConfig {
}

6.@ConfigurationProperties配置绑定

对于application.properties中的配置将其封装到JavaBean中:

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

方式1:

1
2
3
4
5
6
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
// ...
}

方式2:

1
2
3
4
// 1.开启Car的配置绑定功能
@EnableConfigurationProperties(Car.class)
public class MyConfig {
}
1
2
3
4
// 2.读取前缀mycar的属性,将car自动注册到容器中
@ConfigurationProperties(prefix = "mycar")
public class Car {
}

SpringBoot自动配置

核心:@SpringBootApplication

其等价于

1
2
3
4
5
6
7
8
9
10
11
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
  • @ComponentScan 定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中

    • 自定扫描路径下边带有@Controller,@Service,@Repository,@Component注解加入spring容器
    • 通过includeFilters加入扫描路径下没有以上注解的类加入spring容器
    • 通过excludeFilters过滤出不用加入spring容器的类
  • @SpringBootConfiguration 其内部就是@Configuration

  • @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 – 自动配置包

    1
    2
    3
    4
    5
    6
    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
    }
    • 这里可以看到使用Import导入了一个组件:Registrar.这个组件内部给容器导入了一系列组件。
    • 这里的注解在MainApplication上,就是将指定包下的组件导入MainApplication所在包下?

@Import({AutoConfigurationImportSelector.class})

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
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

// 核心
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// configurations 核心
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}

会从META-INF/spring.factories位置来加载一个文件。

默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

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

总结:

SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)

生效的配置类就会给容器中装配很多组件

只要容器中有这些组件,相当于这些功能就有了

定制化配置

用户直接自己@Bean替换底层的组件

用户去看这个组件是获取的配置文件什么值就去修改。

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


SpringBoot全局异常处理

@ControllerAdvice :注解定义全局异常处理类

@ExceptionHandler:声明异常处理方法

使用

1
2
3
4
5
6
7
8
9
10
11
12
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

/**
* 请求参数异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
......
}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!