Spring Boot组件化与参数校验
Spring Boot版本选择
-
2.3.x版本
-
2.6.x版本
Spring Boot核心思想
约定大于配置,简化繁琐的配置
Spring Boot自动配置原理
-
@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot需要运行这个类的main方法来启动SpringBoot应用;
-
SpringBootApplication
java@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:Spring Boot的配置类; 标注在某个类上,表示这是一个Spring Boot的配置类;
-
@Configuration:配置类上来标注这个注解; 配置类 ----- 配置文件;配置类也是容器中的一个组件;本质上是@Component
-
@EnableAutoConfiguration:开启自动配置功能; 以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置,会帮我们自动去加载 自动配置类
-
@ComponentScan : 扫描包 相当于在spring.xml 配置中context:component-scan 但是并没有指定basepackage,如果没有指定spring底层会自动扫描当前配置类所有在的包。TypeExcludeFilter:springboot对外提供的扩展类, 可以供我们去按照我们的方式进行排除,去找到所有自定义的TypeExcludeFilter的bean调用match方法,满足一个则排除。AutoConfigurationExcludeFilter:排除当前类路径下所有@Configuration修饰的类并且是自动配置的类(spring.factories中EnableAutoConfiguration配置了就是自动配置类)
-
@EnableAutoConfiguration
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// 略
}
-
@AutoConfigurationPackage 将当前配置类所在包保存在BasePackages的Bean中。供Spring内部使用。
-
@Import(AutoConfigurationImportSelector.class) 关键点! 可以看到,在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能,而AutoConfigurationImportSelector实现了DeferredImportSelector,Spring延迟到项目beanDefinition已经全被扫描完,才去执行selectImports,内部在解析@Import注解时会调用getAutoConfigurationEntry方法。 下面是2.3.5.RELEASE实现源码:getAutoConfigurationEntry方法进行扫描具有META-INF/spring.factories文件的jar包。
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从META-INF/spring.factories中获得候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 排重
configurations = removeDuplicates(configurations);
//根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
// 根据:@EnableAutoConfiguration.exclude
// @EnableAutoConfiguration.excludeName
// spring.autoconfigure.exclude 进行排除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查如果exclusions排除的类不在自动配置类configurations里,抛出异常
checkExcludedClasses(configurations, exclusions);
// exclusions 也排除
configurations.removeAll(exclusions);
// 通过读取spring.factories 中AutoConfigurationImportFilter的配置类OnBeanCondition\OnClassCondition\OnWebApplicationCondition实例化进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 通过读取spring.factories 中的AutoConfigurationImportListener类实例化(可以支持)处理AutoConfigurationImportEvent的事件
// 分别把候选的配置名单,和排除的配置名单传进去做扩展
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
@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存在指定项 |
我们可以通过设置配置文件中:启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
自定义starter
一、简介 SpringBoot 最强大的功能就是把我们常用的场景抽取成了一个个starter(场景启动器),我们通过引入springboot 为我提供的这些场景启动器,我们再进行少量的配置就能使用相应的功能。即使是这样,springboot也不能囊括我们所有的使用场景,往往我们需要自定义starter,来简化我们对springboot的使用。
模式
我们参照 spring-boot-starter 我们发现其中没有代码:
我们在看它的pom中的依赖中有个 springboot-starter
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
我们再看看 spring-boot-starter 有个 spring-boot-autoconfigure
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
- 启动器(starter)是一个空的jar文件,仅仅提供辅助性依赖管理,这些依赖可能用于自动装配或其他类库。
- 需要专门写一个类似spring-boot-autoconfigure的配置模块
- 用的时候只需要引入启动器starter,就可以使用自动配置了
命名规范
官方命名空间
- 前缀:spring-boot-starter-
- 模式:spring-boot-starter-模块名
- 举例:spring-boot-starter-web、spring-boot-starter-jdbc
自定义命名空间
- 后缀:-spring-boot-starter
- 模式:模块-spring-boot-starter
- 举例:mybatis-spring-boot-starter
参数校验
- 引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--hibernate validator依赖-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
- 添加配置
java
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
- 使用
@Length(max = 64, message = "职务名称超长") 不校验null的字段
@Email 不去检验null和空字符串
@Pattern 可以允许当前字段为null,针对非null做校验
@AssertTrue 不去检验null值
(1)Controller上的参数校验,主要针对于@RequestBody的POJO
(2)Bean的方法参数校验
(3)针对bean的属性做参数校验
@Valid和@Validated区别
区别 | @Valid | @Validated |
---|---|---|
提供者 | JSR-303 规范 | Spring,意味着只能用于POJO之外的地方,比如controller类上、方法、参数上 |
是否支持分组 | 不支持 | 支持 |
标注位置 | METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE | TYPE, METHOD, PARAMETER |
嵌套校验 | 支持 | 不支持 |
- 异常处理
需要全局处理MethodArgumentNotValidException、ConstraintViolationException、BindException异常
java
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Response<?> exceptionHandler(HttpServletRequest httpServletRequest, MethodArgumentNotValidException e) {
log.error("字段校验错误!", e);
String msg = Optional.ofNullable(e.getBindingResult().getFieldError())
.map(DefaultMessageSourceResolvable::getDefaultMessage).orElse(NETWORK_ERROR_MSG);
return new Response<>(CommonErrorCode.FIELD_VALIDATE_FAIL.getCode(), msg);
}
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Response<?> exceptionHandler(HttpServletRequest httpServletRequest, ConstraintViolationException e) {
log.error("字段校验错误!", e);
ConstraintViolation<?> constraintViolation = null;
Iterator<ConstraintViolation<?>> iterator = e.getConstraintViolations().iterator();
if (iterator.hasNext()) {
constraintViolation = iterator.next();
}
String msg = Optional.ofNullable(constraintViolation).map(ConstraintViolation::getMessage)
.orElse(NETWORK_ERROR_MSG);
return new Response<>(CommonErrorCode.FIELD_VALIDATE_FAIL.getCode(), msg);
}
@ExceptionHandler(value = BindException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Response<?> exceptionHandler(HttpServletRequest httpServletRequest, BindException e) {
log.error("字段校验错误!", e);
String msg = Optional.ofNullable(e.getBindingResult().getFieldError())
.map(DefaultMessageSourceResolvable::getDefaultMessage).orElse(NETWORK_ERROR_MSG);
return new Response<>(CommonErrorCode.FIELD_VALIDATE_FAIL.getCode(), msg);
}