Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验,使用消息资源文件对消息国际化
导包和配置
导入 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,多个用逗号隔开
encoding: UTF-8 # 必须指定解析编码,否则中文乱码
在 springboot 启动类里面配置
@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"); // 重复定义
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();
}
/**
* 方法级别的单个参数验证开启
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
我们对于校验参数通过不了抛出的异常进行处理,是通过统一异常捕捉。
@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());
}
}
Msg 结果返回集
public class Msg {
private boolean success = true; //是否成功
private Object data; //数据
private String message; //信息
private long code; //错误代码
public Object getData() {
return this.data;
}
public String getMessage() {
return this.message;
}
public long getCode() {
return this.code;
}
public Msg() {
}
public Msg(int status) {
this.code = status;
}
public Msg(String msg, Object data) {
this.message = msg;
this.data = data;
}
public Msg(boolean success, String msg, Object data) {
this.success = success;
this.message = msg;
this.data = data;
}
public Msg(int status, String msg, Object data) {
this.code = status;
this.message = msg;
this.data = data;
}
public Msg(boolean success, int status, String msg, Object data) {
this.success = success;
this.code = status;
this.message = msg;
this.data = data;
}
public boolean isSuccess() {
return this.success;
}
public static Msg.BodyBuilder status(boolean success, int code) {
return new Msg.DefaultBuilder(success, code);
}
public static Msg.BodyBuilder status(boolean success) {
return new Msg.DefaultBuilder(success);
}
/* 快捷输出 start */
public static Msg.BodyBuilder ok() {
return status(true);
}
public static Msg.BodyBuilder ok(int code) {
return status(true, code);
}
public static Msg ok(Object data) {
Msg.BodyBuilder builder = ok();
return builder.body(data);
}
public static Msg ok(String msg) {
Msg.BodyBuilder builder = ok();
return builder.msg(msg).build();
}
public static Msg ok(String msg, Object data) {
Msg.BodyBuilder builder = ok();
return builder.msg(msg).body(data);
}
public static Msg ok(int code, String msg, Object data) {
Msg.BodyBuilder builder = ok(code);
return builder.msg(msg).body(data);
}
public static Msg.BodyBuilder fail() {
return status(false);
}
public static Msg.BodyBuilder fail(int code) {
return status(false, code);
}
public static Msg fail(Object data) {
Msg.BodyBuilder builder = fail();
return builder.body(data);
}
public static Msg fail(String msg) {
Msg.BodyBuilder builder = fail();
return builder.msg(msg).build();
}
public static Msg fail(String msg, Object data) {
Msg.BodyBuilder builder = fail();
return builder.msg(msg).body(data);
}
public static Msg fail(int code, String msg, Object data) {
Msg.BodyBuilder builder = fail(code);
return builder.msg(msg).body(data);
}
public static Msg error(Object data) {
Msg.BodyBuilder builder = fail();
return builder.body(data);
}
public static Msg error(String msg) {
Msg.BodyBuilder builder = fail();
return builder.msg(msg).build();
}
public static Msg error(String msg, Object data) {
Msg.BodyBuilder builder = fail();
return builder.msg(msg).body(data);
}
public static Msg error(int code, String msg, Object data) {
Msg.BodyBuilder builder = fail(code);
return builder.msg(msg).body(data);
}
/* 快捷输出 end */
private static class DefaultBuilder implements Msg.BodyBuilder {
private boolean success;
private int code;
private String message;
public DefaultBuilder(boolean success) {
this.success = success;
}
public DefaultBuilder(boolean success, int code) {
this.success = success;
this.code = code;
}
public DefaultBuilder(boolean success, String message) {
this.success = success;
this.message = message;
}
@Override
public Msg body(Object data) {
Msg msg = new Msg();
msg.success = this.success;
msg.message = this.message;
msg.code = this.code;
if (data instanceof Number) {
return new Msg(this.success, this.message, data);
}
msg.data = data;
if (msg.data == null) {
msg.data = new Object();
}
return msg;
}
@Override
public Msg.BodyBuilder msg(String message) {
this.message = message;
return this;
}
@Override
public Msg build() {
return new Msg(this.success, this.code, this.message, "");
}
}
public interface BodyBuilder {
Msg body(Object var1);
Msg.BodyBuilder msg(String message);
Msg build();
}
resources/base.propertie
creatorId=创建者 id 不能为小于 {value}。
modifierId=修改者 id 不能为小于 {value}。
resources/todo.properties
todo.privateId.min=私有 id 不能为小于 {value}。
在 bean 字段上使用注解,其中 group 中的 C 和 S 接口是指 Controller 和 Service 的叫法简称,里面分别有 Insert 接口、Update 接口等等,都是自定义约定的东西。
public interface C {
interface Insert {}
interface Query {}
interface Update {}
interface UpdateStatus {}
}
public interface S {
interface Insert {}
interface Query {}
interface Update {}
interface UpdateStatus {}
}
/**
* 私有 id,是代表项目任务/非项目任务/风险/问题/评审待办问题等多张表的外键
*/
@Min(value = 1, message = "{todo.privateId.min}", groups = {C.Insert.class, C.Update.class, S.Insert.class, S.Update.class})
private long privateId;
/**
* 创建者id
*/
@Min(value = 1, message = "{creatorId}", groups = {S.Insert.class})
private long creatorId;
Controller 控制层验证
@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 注解上的验证组,其他验证组不会进行验证,这样可以区别开来进行单独验证。
而像没有实体,只有一个基础数据类型的,可以进行验证,但是需要满足三个条件:
- 在启动类配置方法级别验证启用类
- 在 Controller 类上注解
@Validated
- 在方法参数里使用验证注解如
@Min
,@NotNull
等等
自行验证。
Service 服务层 AOP 验证
ValidUtil 工具类
需要被 springboot 扫描并注册为单例
@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);
}
/**
* 校验参数,并返回第一个错误提示
* @param t 验证的对象
* @param groups 验证的组别
* @param <T> 对象擦除前原类型
* @return 第一个错误提示
*/
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);
}
}
/**
* 校验参数,并返回第一个错误提示
* @param targetClass 验证的对象的 class 类型
* @param fieldName 需要验证的名字
* @param obj 需要属性值
* @param groups 验证的组别
* @param <T> 对象擦除前原类型
* @return 第一个错误提示
*/
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 拦截方法执行参数,对参数获取注解。再利用工具类来验证参数,如果验证不通过,直接抛出自定义错误,自定义错误已经全局统一处理了。
@Aspect
@Component
public class ValidatorAOP {
@Autowired
private ValidUtil validUtil;
/**
* 定义拦截规则:拦截 com.servic 包下面的所有类中,有 @Service 注解的方法。
*/
@Pointcut("execution(* com.service..*(..)) and @annotation(org.springframework.stereotype.Service)")
public void controllerMethodPointcut() {
}
/**
* 拦截器具体实现
*/
@Around("controllerMethodPointcut()") // 指定拦截器规则;也可以直接把 “execution(* com.xjj.........)” 写进这里
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;
}
}
验证注解 @Min @NotNull 使用方法
不能写在实现类上,只能在接口中使用注解
与 Controller 使用方式基本一样
@Validated
public interface TodoService {
/**
* 查询 单个待办
* @param id 序号
* @return 单个待办
*/
Msg getVo(@Min(value = 1, message = "待办 id 不能小于 1。") long id);
/**
* 添加数据
* @param todo 对象
*/
Msg add(@Validated({S.Insert.class}) Todo todo);
}
分享几个自定义验证注解
字符串判空验证
package javax.validation.constraints;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 字符串判空验证,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 是否为其中一个值,可以根据验证组自定义判断
resources/todo.properties
todo.todoType.insert=新增时,待办类型只能是 非项目任务、项目任务、问题 之中一。
todo.todoType.update=修改时,待办类型只能是风险、评审待办问题 之中一。
bean
/**
* 待办类型0非项目任务1项目任务2问题3风险4评审待办问题
*/
@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 "请输入正确的类型";
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 支持的同一注解多次特性。
根据上面的同样也可以用在枚举类上
resources/todo.properties
todo.todoStatus.insert=新增时,状态只能是未开始。
todo.todoStatus.update=修改时,状态只能是进行中或已完成。
bean
/**
* 待办状态0未开始1进行中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 "请输入正确的状态";
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();
}
}
Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验,使用消息资源文件对消息国际化的更多相关文章
- springMvc基本注解:@Component、@Repository(持久层) 、@Service(业务逻辑) 、@Controller(控制层)
1.@Controller(控制层) :就是action层 2.@Service(业务逻辑) :业务逻辑层,负责处理各种控制层的操作 3.@Repository(持久层) :称为“持久化”层,负责对数 ...
- 接收的参数为日期类型、controller控制层进行数据保存、进行重定向跳转
目录 1.接收的参数为日期类型 2.controller控制层进行数据保存 3.controller层如何进行重定向跳转(因为默认是请求转发) 4.静态资源的映射 1.接收的参数为日期类型 WEB-I ...
- Spring Boot的Controller控制层和页面
一.项目实例 1.项目结构 2.项目代码 1).ActionController.Java: package com.example.controller; import java.util.Date ...
- Servlet—作controller控制层
servlet控制器的改造步骤: 1.编写servlet类,和访问路径 2.修改jsp请求路径 servlet参数配置---获取初始化参数 servlet参数配置---全局参数
- SpringBoot系列四:SpringBoot开发(改变环境属性、读取资源文件、Bean 配置、模版渲染、profile 配置)
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念 SpringBoot 开发深入 2.具体内容 在之前已经基本上了解了整个 SpringBoot 运行机制,但是也需要清 ...
- SpringBoot开发(改变环境属性、读取资源文件、Bean 配置、模版渲染、profile 配置)
1.概念 SpringBoot 开发深入 2.具体内容 在之前已经基本上了解了整个 SpringBoot 运行机制,但是也需要清楚的认识到以下的问题,在实际的项目开发之中,尤其是 Java 的 MVC ...
- SDN原理 控制层 Controller控制器
本文参照SDN原理视频而成:SDN原理 Controller 概念 从上面这个图片,我们能够知道,Controller 是一个非常重要的东西:承上启下,左右拓展. 从整个SDN的架构来看,控制器 处在 ...
- SpringBoot自定义控制层参数解析
一.背景 在Spring的Controller中,我们通过@RequestParam或@RequestBody就可以将请求中的参数映射到控制层具体的参数中,那么这个是怎么实现的呢?如果我现在控制层中的 ...
- springboot之json传参(后台控制层如何接收和解析参数)
一般web端都是用form标签的形式进行表单提交到后台,后台控制层再用相应的实体对象去接收前端传来的json参数. 但是有时候前端界面很复杂,要传入后端的参数是各种标签里面的value值,这些值又是来 ...
随机推荐
- Visual paradigm软件介绍
Visual paradigm软件介绍 说起Visual Paradigm你可能并不陌生,因为此前有一款功能强大的UML软件叫Visual Paradigm for UML,在这款软件在v11.1的时 ...
- centos 6.9安装zabbix 3.0
Linux下常用的系统监控软件有Nagios.Cacti.Zabbix.Monit等,这些开源的软件,可以帮助我们更好的管理机器,在第一时间内发现,并警告系统维护人员. 今天开始研究下Zabbix,使 ...
- 粗略整理的java面试题
1.垃圾回收 是回收的空闲堆空间 只有在cpu空闲并且堆空间不足的情况下才回收 2.threadlocal 就是为线程的变量都提供了一个副本,每个线程运行都只是在更新这个副本. Threadloc ...
- C#中SQL语句参数写法
OracleConnection oc=new OracleConnection("data source=osserver;User Id=****;password=**"); ...
- java 导出blob图片到excel
实现功能,导出当前页面显示员工的图片,核心代码已给出,仅供参考, 如需转载请注明出处http://www.cnblogs.com/wangjianguang/p/7852060.html 随便再扯2句 ...
- Windows定时器学习
定时器是一个在特定时间或者规则间隔被激发的内核对象.结合定时器的异步程序调用可以允许回调函数在任何定时器被激发的时候执行. 通过调用CreateWaitableTimer()可以创建一个定时器,此函数 ...
- 2017年php面试题汇总
1.http状态码 200 这个没有什么好说的,是代表请求被正常的处理成功了 302 代表临时重定向 400 400表示请求报文中存在语法错误.需要修改后再次发送 403 表明请求访问的资源被拒绝了. ...
- 前端如何处理emoji表情
这段时间在做移动端的开发, 有一个功能就是发表评论,其实这个功能本身是比较简单的, 但是在提测是的时候QA给哦提了一个bug,说输入手机自带的emoji表情发送失败了.我就奇怪了,emoji表情也是文 ...
- matlab R2016a 中添加新的工具箱的方法
matlab添加新的工具箱分三步: 1. 下载新的工具箱,并解压. 2. 将解压后的工具箱文件夹移到安装的matlab中的toolbox文件夹中 3. 添加新文件夹及其子文件夹到路径中. 接下来以添加 ...
- Python和Excel交互
Python和Excel交互 使用的python包为XlsxWriter 下载的链接 https://pypi.python.org/pypi/XlsxWriter 初级的例子: def write_ ...