Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對(duì)消息國際化
導(dǎo)包和配置
導(dǎo)入 JSR 303 的包、hibernate valid 的包
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.5.Final</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.0.Final</version> </dependency>
springboot 配置
resources/application.yml 消息資源文件國際化處理配置
spring:
messages:
basename: base,todo # 資源文件 base.properties 和 todo.properties,多個(gè)用逗號(hào)隔開
encoding: UTF-8 # 必須指定解析編碼,否則中文亂碼
在 springboot 啟動(dòng)類里面配置
@SpringBootApplication
public class Application extends WebMvcConfigurerAdapter {
@Value("${spring.messages.basename}")
private String basename;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
@Primary
public MessageSource messageSource() {
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
resourceBundleMessageSource.setUseCodeAsDefaultMessage(false);
resourceBundleMessageSource.setDefaultEncoding("UTF-8"); // 重復(fù)定義
resourceBundleMessageSource.setBasenames(basename.split(","));
return resourceBundleMessageSource;
}
@Bean
@Primary
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setProviderClass(HibernateValidator.class);
validatorFactoryBean.setValidationMessageSource(messageSource());
return validatorFactoryBean;
}
@Override
public Validator getValidator() {
return validator();
}
/**
* 方法級(jí)別的單個(gè)參數(shù)驗(yàn)證開啟
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
我們對(duì)于校驗(yàn)參數(shù)通過不了拋出的異常進(jìn)行處理,是通過統(tǒng)一異常捕捉。
@ControllerAdvice
@Component
public class BindValidExceptionHandler {
@ResponseStatus(value = HttpStatus.OK)
@ExceptionHandler(ConstraintViolationException.class)
public @ResponseBody
Msg handleConstraintViolationException(ConstraintViolationException e) {
String messageTemplate = e.getConstraintViolations().iterator().next().getMessageTemplate();
return Msg.error(messageTemplate);
}
@ResponseStatus(value = HttpStatus.OK)
@ExceptionHandler(BindException.class)
public @ResponseBody
Msg handleBindException(BindException e) {
BindingResult bindingResult = e.getBindingResult();
String className = bindingResult.getTarget().getClass().getName();
FieldError next = bindingResult.getFieldErrors().iterator().next();
String fieldName = next.getField();
String defaultMessage = next.getDefaultMessage();
if (Pattern.compile("IllegalArgumentException: No enum").matcher(defaultMessage).find()) {
Matcher matcher = Pattern.compile("for value '(.*?)'").matcher(defaultMessage);
if (matcher.find()) {
defaultMessage = "找不到枚舉類型【" + matcher.group(1) + "】";
}
}
return Msg.error(defaultMessage);
}
@ResponseStatus(value = HttpStatus.OK)
@ExceptionHandler(ValidError.class)
public @ResponseBody
Msg handleValidError(ValidError e) {
return Msg.error(e.getMessage());
}
}
resources/base.propertie
creatorId=創(chuàng)建者 id 不能為小于 {value}。
modifierId=修改者 id 不能為小于 {value}。
resources/todo.properties
todo.privateId.min=私有 id 不能為小于 {value}。
在 bean 字段上使用注解,其中 group 中的 C 和 S 接口是指 Controller 和 Service 的叫法簡稱,里面分別有 Insert 接口、Update 接口等等,都是自定義約定的東西。
/**
* 私有 id,是代表項(xiàng)目任務(wù)/非項(xiàng)目任務(wù)/風(fēng)險(xiǎn)/問題/評(píng)審待辦問題等多張表的外鍵
*/
@Min(value = 1, message = "{todo.privateId.min}", groups = {C.Insert.class, C.Update.class, S.Insert.class, S.Update.class})
private long privateId;
/**
* 創(chuàng)建者id
*/
@Min(value = 1, message = "{creatorId}", groups = {S.Insert.class})
private long creatorId;
Controller 控制層驗(yàn)證
@Validated
@RestController
@RequestMapping("todo")
public class TodoController {
@Autowired
private TodoService todoService;
@GetMapping("getVo")
public Msg getVo(
@Min(value = 1, message = "待辦 id 不能小于 1。")
@RequestParam(required = false, defaultValue = "0")
long id
) {
return this.todoService.getVo(id);
}
@PostMapping("add")
public Msg add(@Validated({C.Insert.class}) Todo todo) {
return this.todoService.add(todo);
}
}
@Validated({C.Insert.class}) 聲明啟用 bean 注解上的驗(yàn)證組,其他驗(yàn)證組不會(huì)進(jìn)行驗(yàn)證,這樣可以區(qū)別開來進(jìn)行單獨(dú)驗(yàn)證。
而像沒有實(shí)體,只有一個(gè)基礎(chǔ)數(shù)據(jù)類型的,可以進(jìn)行驗(yàn)證,但是需要滿足三個(gè)條件:
- 在啟動(dòng)類配置方法級(jí)別驗(yàn)證啟用類
- 在 Controller 類上注解 @Validated
- 在方法參數(shù)里使用驗(yàn)證注解如 @Min,@NotNull 等等
自行驗(yàn)證。
Service 服務(wù)層 AOP 驗(yàn)證
ValidUtil 工具類
需要被 springboot 掃描并注冊(cè)為單例
@Component
public class ValidUtil {
@Autowired
private Validator validator;
public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
return validator.validate(object, groups);
}
public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
return validator.validateValue(beanType, propertyName, value, groups);
}
/**
* 校驗(yàn)參數(shù),并返回第一個(gè)錯(cuò)誤提示
* @param t 驗(yàn)證的對(duì)象
* @param groups 驗(yàn)證的組別
* @param <T> 對(duì)象擦除前原類型
* @return 第一個(gè)錯(cuò)誤提示
*/
public <T> void validAndReturnFirstErrorTips(T t, Class<?>... groups) {
Set<ConstraintViolation<T>> validate = validator.validate(t, groups);
if (validate.size() > 0) {
ConstraintViolation<T> next = validate.iterator().next();
String message = next.getRootBeanClass().getName() + "-" + next.getPropertyPath() + "-" + next.getMessage();
throw new ValidError(message);
}
}
/**
* 校驗(yàn)參數(shù),并返回第一個(gè)錯(cuò)誤提示
* @param targetClass 驗(yàn)證的對(duì)象的 class 類型
* @param fieldName 需要驗(yàn)證的名字
* @param obj 需要屬性值
* @param groups 驗(yàn)證的組別
* @param <T> 對(duì)象擦除前原類型
* @return 第一個(gè)錯(cuò)誤提示
*/
public <T> void validAndReturnFirstErrorTips(Class targetClass, String fieldName, Object obj, Class<?>... groups) {
Set<ConstraintViolation<T>> validate = validator.validateValue(targetClass, fieldName, obj, groups);
if (validate.size() > 0) {
String message = targetClass.getName() + "-" + fieldName + "-" + validate.iterator().next().getMessage();
throw new ValidError(message);
}
}
}
AOP 配置
主要原理是利用 aop 攔截方法執(zhí)行參數(shù),對(duì)參數(shù)獲取注解。再利用工具類來驗(yàn)證參數(shù),如果驗(yàn)證不通過,直接拋出自定義錯(cuò)誤,自定義錯(cuò)誤已經(jīng)全局統(tǒng)一處理了。
@Aspect
@Component
public class ValidatorAOP {
@Autowired
private ValidUtil validUtil;
/**
* 定義攔截規(guī)則:攔截 com.servic 包下面的所有類中,有 @Service 注解的方法。
*/
@Pointcut("execution(* com.service..*(..)) and @annotation(org.springframework.stereotype.Service)")
public void controllerMethodPointcut() {
}
/**
* 攔截器具體實(shí)現(xiàn)
*/
@Around("controllerMethodPointcut()") // 指定攔截器規(guī)則;也可以直接把 “execution(* com.xjj.........)” 寫進(jìn)這里
public Object Interceptor(ProceedingJoinPoint pjp) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
Annotation[][] argAnnotations = method.getParameterAnnotations();
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
for (Annotation annotation : argAnnotations[i]) {
if (Validated.class.isInstance(annotation)) {
Validated validated = (Validated) annotation;
Class<?>[] groups = validated.value();
validUtil.validAndReturnFirstErrorTips(args[i], groups);
}
}
}
try {
return pjp.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return true;
}
}
驗(yàn)證注解 @Min @NotNull 使用方法
不能寫在實(shí)現(xiàn)類上,只能在接口中使用注解
與 Controller 使用方式基本一樣
@Validated
public interface TodoService {
/**
* 查詢 單個(gè)待辦
* @param id 序號(hào)
* @return 單個(gè)待辦
*/
Msg getVo(@Min(value = 1, message = "待辦 id 不能小于 1。") long id);
/**
* 添加數(shù)據(jù)
* @param todo 對(duì)象
*/
Msg add(@Validated({S.Insert.class}) Todo todo);
}
分享幾個(gè)自定義驗(yàn)證注解
字符串判空驗(yàn)證
package javax.validation.constraints;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 字符串判空驗(yàn)證,hibernate 自帶的可能有問題,使用不了,需要重寫,package 是不能變的。
*/
@Documented
@Constraint(
validatedBy = {NotBlank.NotBlankValidator.class}
)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotBlank {
Class<?>[] groups() default {};
String message() default "{notBlank}";
Class<? extends Payload>[] payload() default {};
class NotBlankValidator implements ConstraintValidator<NotBlank, Object> {
public NotBlankValidator() {
}
@Override
public void initialize(NotBlank constraintAnnotation) {
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return value != null && !value.toString().isEmpty();
}
}
}
類型判斷,判斷 type 是否為其中一個(gè)值,可以根據(jù)驗(yàn)證組自定義判斷
resources/todo.properties
todo.todoType.insert=新增時(shí),待辦類型只能是 非項(xiàng)目任務(wù)、項(xiàng)目任務(wù)、問題 之中一。
todo.todoType.update=修改時(shí),待辦類型只能是風(fēng)險(xiǎn)、評(píng)審待辦問題 之中一。
bean
/**
* 待辦類型0非項(xiàng)目任務(wù)1項(xiàng)目任務(wù)2問題3風(fēng)險(xiǎn)4評(píng)審待辦問題
*/
@TodoTypeValid(value = {"0", "1", "2"}, message = "{todo.todoType.insert}", groups = {C.Insert.class, S.Insert.class})
@TodoTypeValid(value = {"3", "4"}, message = "{todo.todoType.update}", groups = {C.Update.class, S.Update.class})
private String todoType;
自定義注解
@Documented
@Constraint(validatedBy = {TodoTypeValid.TodoTypeValidFactory.class})
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(TodoTypeValid.List.class)
public @interface TodoTypeValid {
String message() default "請(qǐng)輸入正確的類型";
String[] value() default {};
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class TodoTypeValidFactory implements ConstraintValidator<TodoTypeValid, String> {
private String[] annotationValue;
@Override
public void initialize(TodoTypeValid todoStatusValid) {
this.annotationValue = todoStatusValid.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (Arrays.asList(annotationValue).contains(value))
return true;
return false;
}
}
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
TodoTypeValid[] value();
}
}
@Repeatable(TodoTypeValid.List.class) 是 JDK8 支持的同一注解多次特性。
根據(jù)上面的同樣也可以用在枚舉類上
resources/todo.properties
todo.todoStatus.insert=新增時(shí),狀態(tài)只能是未開始。
todo.todoStatus.update=修改時(shí),狀態(tài)只能是進(jìn)行中或已完成。
bean
/**
* 待辦狀態(tài)0未開始1進(jìn)行中2已完成
*/
@TodoStatusValid(enums = {TodoStatus.NOT_STARTED}, message = "{todo.todoStatus.insert}", groups = {C.Insert.class, S.Insert.class})
@TodoStatusValid(enums = {TodoStatus.PROCESSING, TodoStatus.COMPLETED}, message = "{todo.todoStatus.update}", groups = {C.Update.class, S.Update.class})
private TodoStatus todoStatus;
自定義注解
@Documented
@Constraint(validatedBy = {TodoStatusValid.TodoStatusValidFactory.class})
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(TodoStatusValid.List.class)
public @interface TodoStatusValid {
String message() default "請(qǐng)輸入正確的狀態(tài)";
TodoStatus[] enums() default {};
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class TodoStatusValidFactory implements ConstraintValidator<TodoStatusValid, TodoStatus> {
private TodoStatus[] enums;
@Override
public void initialize(TodoStatusValid todoStatusValid) {
this.enums = todoStatusValid.enums();
}
@Override
public boolean isValid(TodoStatus value, ConstraintValidatorContext context) {
TodoStatus[] values = TodoStatus.values();
if (enums != null && enums.length != 0) {
values = enums;
}
if (Arrays.asList(values).contains(value))
return true;
return false;
}
}
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
TodoStatusValid[] value();
}
}
總結(jié)
以上所述是小編給大家介紹的Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對(duì)消息國際化,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
詳細(xì)聊聊SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的方法
在大型分布式項(xiàng)目中,經(jīng)常會(huì)出現(xiàn)多數(shù)據(jù)源的情況,下面這篇文章主要給大家介紹了關(guān)于SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
IDEA配置Maven并版本統(tǒng)一管理的實(shí)現(xiàn)
本文主要介紹了IDEA配置Maven并版本統(tǒng)一管理的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Springboot單體架構(gòu)http請(qǐng)求轉(zhuǎn)換https請(qǐng)求來支持微信小程序調(diào)用接口
這篇文章主要介紹了Springboot單體架構(gòu)http請(qǐng)求轉(zhuǎn)換https請(qǐng)求來支持微信小程序調(diào)用接口,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
java8 實(shí)現(xiàn)提取集合對(duì)象的每個(gè)屬性
這篇文章主要介紹了java8 實(shí)現(xiàn)提取集合對(duì)象的每個(gè)屬性方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Springboot自動(dòng)配置與@Configuration配置類詳解
這篇文章主要介紹了SpringBoot中的@Configuration與自動(dòng)配置,在進(jìn)行項(xiàng)目編寫前,我們還需要知道一個(gè)東西,就是SpringBoot對(duì)我們的SpringMVC還做了哪些配置,包括如何擴(kuò)展,如何定制,只有把這些都搞清楚了,我們?cè)谥笫褂貌艜?huì)更加得心應(yīng)手2022-07-07
springboot 使用QQ郵箱發(fā)送郵件的操作方法
這篇文章主要介紹了springboot使用QQ郵箱發(fā)送郵件功能,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10
Java實(shí)現(xiàn)批量修改文件名和重命名的方法
這篇文章主要介紹了Java實(shí)現(xiàn)批量修改文件名和重命名的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
springmvc實(shí)現(xiàn)自定義類型轉(zhuǎn)換器示例
本篇文章主要介紹了springmvc實(shí)現(xiàn)自定義類型轉(zhuǎn)換器示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
spring mvc中的@PathVariable動(dòng)態(tài)參數(shù)詳解
這篇文章主要介紹了spring mvc中的@PathVariable動(dòng)態(tài)參數(shù)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

