Spring Boot组件化与参数校验

Spring Boot组件化与参数校验

Spring Boot版本选择

  1. 2.3.x版本

  2. 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

参数校验

  1. 引入依赖
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>
  1. 添加配置
java 复制代码
@Bean
public Validator validator(){
    ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
            .configure()

            .failFast(true)
            .buildValidatorFactory();

return validatorFactory.getValidator();
}
  1. 使用

@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
嵌套校验 支持 不支持
  1. 异常处理

需要全局处理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);
}
相关推荐
葫芦和十三10 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp11 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑11 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯12 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan14 小时前
多Agent之间的区别
后端
青石路16 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充16 小时前
1.面向对象设计思想
后端
IT_陈寒17 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro17 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗17 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端