Springboot中如何结合@Valid和AOP实现参数校验与日志记录?

更新于
2026-08-16 15:46:05
17阅读来源:SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1940个文字,预计阅读时间需要8分钟。

Springboot中如何结合@Valid和AOP实现参数校验与日志记录?

项目背景+最近在项目上对接前端的时候遇到了几个问题:

1.经常要问前端要参数

2.要根据请求参数写大量if...else,代码散落在Controller中,影响代码质量

3.为了解决第一个问题,到处记笔记

项目背景

最近在项目上对接前端的的时候遇到了几个问题

1.经常要问前端要请求参数

2.要根据请求参数写大量if...else,代码散步在 Controller 中,影响代码质量

3.为了解决问题1,到处记日志,导致到处改代码

解决方案

Springboot中如何结合@Valid和AOP实现参数校验与日志记录?

为了解决这类问题,我使用了@Valid 做参数校验,并使用AOP记录前端请求日志

1.Bean实体类增加注解

对要校验的实体类增加注解,如果实体类中有List结构,就在List上加@Valid

@Valid注解

注解 备注 @Null 只能为null @NotNull 必须不为null @Max(value) 必须为一个不大于 value 的数字 @Min(value) 必须为一个不小于 value 的数字 @AssertFalse 必须为false @AssertTrue 必须为true @DecimalMax(value) 必须为一个小于等于 value 的数字 @DecimalMin(value) 必须为一个大于等于 value 的数字 @Digits(integer,fraction) 必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction @Past 必须是 日期 ,且小于当前日期 @Future 必须是 日期,且为将来的日期 @Size(max,min) 字符长度必须在min到max之间 @Pattern(regex=,flag=) 必须符合指定的正则表达式 @NotEmpty 必须不为null且不为空(字符串长度不为0、集合大小不为0) @NotBlank 必须不为空(不为null、去除首位空格后长度不为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 @Email 必须为Email,也可以通过正则表达式和flag指定自定义的email格式

UserInfo

package com.zero.check.query; import lombok.Data; import org.hibernate.validator.constraints.EAN; import org.springframework.stereotype.Component; import javax.validation.Valid; import javax.validation.constraints.*; import java.util.List; /** * @Description: * @author: wei.wang * @since: 2019/11/21 15:05 * @history: 1.2019/11/21 created by wei.wang */ @Component @Data public class UserInfo { @NotBlank(message = "主键不能为空") @Pattern(regexp = "^[1-9]\\d*$",message = "主键范围不正确") private String id; @Valid @NotEmpty(message = "用户列表不能为空") private List<User> userList; @NotNull(message = "权限不能为空") @Min(value = 1, message = "权限范围为[1-99]") @Max(value = 99, message = "权限范围为[1-99]") private Long roleId; }

User

package com.zero.check.query; import lombok.Data; import org.springframework.stereotype.Component; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; /** * @Description: * @author: wei.wang * @since: 2019/11/21 16:03 * @history: 1.2019/11/21 created by wei.wang */ @Component @Data public class User { @NotBlank(message = "用户工号不能为空") private String userId; @NotBlank(message = "用户名称不能为空") private String userName; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }

2.Controller层

在需要校验的pojo前边添加@Validated,在需要校验的pojo后边添加BindingResult br接收校验出错信息,需要注意的是, BindingResult result一定要跟在 @Validated 注解对象的后面(必须是实体类),而且当有多个@Validated注解时,每个注解对象后面都需要添加一个 BindingResult,而实际使用时由于在WebLogAspect切点读取了请求数据,会导致在Controller层请求参数中读不到数据,这里需要修改其他内容,详见Git

DataCheckController package com.zero.check.controller; import com.zero.check.query.User; import com.zero.check.query.UserInfo; import com.zero.check.utils.Response; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; /** * @Description: * @author: wei.wang * @since: 2019/11/21 14:57 * @history: 1.2019/11/21 created by wei.wang */ @RestController @RequestMapping(value = "/check") public class DataCheckController { @PostMapping(value = "/userValidPost") public Response queryUserPost(@Valid @RequestBody UserInfo userInfo, BindingResult result) { return Response.ok().setData("Hello " + userInfo.getId()); } @GetMapping(value = "/userValidGet") public Response queryUserGet(@Valid User user, BindingResult result) { return Response.ok().setData("Hello " + user.getUserName()); } }

3.AOP

定义切点@Pointcut("execution(com.zero.check.controller.. (..))"),定义后可监控com.zero.check.controller包和子包里任意方法的执行

如果输入参数不能通过校验,就直接抛出异常,由于定义了UserInfoHandler拦截器,可以拦截处理校验错误,这样就可以省略大量的非空判断,让Controller层专注业务代码,并且将日志集中在WebLogAspect中处理,不会因为记录日志导致要到处改代码

if (bindingResult.hasErrors()) { FieldError error = bindingResult.getFieldError(); throw new UserInfoException(Response.error(error.getDefaultMessage()).setData(error)); } UserInfoHandler package com.zero.check.handler; import com.zero.check.exception.UserInfoException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @Description: * @author: wei.wang * @since: 2019/11/21 15:04 * @history: 1.2019/11/21 created by wei.wang */ @RestControllerAdvice public class UserInfoHandler { /** * 校验错误拦截处理 * * @param e 错误信息集合 * @return 错误信息 */ @ExceptionHandler(UserInfoException.class) public Object handle(UserInfoException e) { return e.getR(); } }

WebLogAspect

package com.zero.check.aspect; import com.alibaba.fastjson.JSON; import com.zero.check.exception.UserInfoException; import com.zero.check.utils.Response; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import javax.servlet.www.google.com" }, { "userId": "S", "userName": "www.SoSo.com" }, { "userId": "SoSo", "userName": "www.SoSo.com" } ], "roleId":"11" }

返回结果

{ "code": "ok", "data": "Hello 12", "requestid": "706cd81db49d4c9795e5457cebb1ba8c" }

请求参数

{ "id":"1A2", "userList": [ { "userId": "Google", "userName": "www.google.com" }, { "userId": "S", "userName": "www.SoSo.com" }, { "userId": "SoSo", "userName": "www.SoSo.com" } ], "roleId":"11" }

返回结果

{ "code": "error", "message": "主键范围不正确", "data": { "codes": [ "Pattern.userInfo.id", "Pattern.id", "Pattern.java.lang.String", "Pattern" ], "arguments": [ { "codes": [ "userInfo.id", "id" ], "arguments": null, "defaultMessage": "id", "code": "id" }, [], { "defaultMessage": "^[1-9]\\d*$", "arguments": null, "codes": [ "^[1-9]\\d*$" ] } ], "defaultMessage": "主键范围不正确", "objectName": "userInfo", "field": "id", "rejectedValue": "1A2", "bindingFailure": false, "code": "Pattern" }, "requestid": "076c899495b448b59f1b133efd130061" }

控制台输出

可以看到第一次请求时WebLogAspect成功打印了请求数据和返回结果,而第二次因为没有通过校验,没有进入WebLogAspect,所以没有打印数据

2019-11-21 22:50:43.283 INFO 94432 --- [nio-9004-exec-2] com.zero.check.aspect.WebLogAspect : REQUEST: { "id":"1", "userList": [ { "userId": "Google", "userName": "www.google.com" }, { "userId": "S", "userName": "www.SoSo.com" }, { "userId": "SoSo", "userName": "www.SoSo.com" } ], "roleId":"11" } 2019-11-21 22:50:43.345 INFO 94432 --- [nio-9004-exec-2] com.zero.check.aspect.WebLogAspect : RESPONSE: {"code":"ok","data":"Hello 1","requestid":"286174a075c144eeb0de0b8dbd7c1851"}

GET接口

localhost:9004/check/userValidGet?userId=a&userName=zero

返回结果

{ "code": "ok", "data": "Hello zero", "requestid": "9b5ea9bf1db64014b0b4d445d8baf9dc" } localhost:9004/check/userValidGet?userId=a&userName=

返回结果

{ "code": "error", "message": "用户名称不能为空", "data": { "codes": [ "NotBlank.user.userName", "NotBlank.userName", "NotBlank.java.lang.String", "NotBlank" ], "arguments": [ { "codes": [ "user.userName", "userName" ], "arguments": null, "defaultMessage": "userName", "code": "userName" } ], "defaultMessage": "用户名称不能为空", "objectName": "user", "field": "userName", "rejectedValue": "", "bindingFailure": false, "code": "NotBlank" }, "requestid": "5677d93c084d418e88cf5bb8547c5a2e" }

控制台输出

可以看到第一次请求时WebLogAspect成功打印了请求和返回结果,而第二次因为没有通过校验,没有进入WebLogAspect,所以没有打印数据

2019-11-21 23:18:50.755 INFO 94432 --- [nio-9004-exec-9] com.zero.check.aspect.WebLogAspect : REQUEST: {userName=zero, userId=a} 2019-11-21 23:18:50.756 INFO 94432 --- [nio-9004-exec-9] com.zero.check.aspect.WebLogAspect : RESPONSE: {"code":"ok","data":"Hello zero","requestid":"422edc9cd59d45bea275e579a67ccd0c"}

5.代码Git地址

git@github.com:A-mantis/SpringBootDataCheck.git

总结

以上所述是小编给大家介绍的Springboot使用@Valid 和AOP做参数校验及日志输出问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对易盾网络网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

本文共计1940个文字,预计阅读时间需要8分钟。

Springboot中如何结合@Valid和AOP实现参数校验与日志记录?

项目背景+最近在项目上对接前端的时候遇到了几个问题:

1.经常要问前端要参数

2.要根据请求参数写大量if...else,代码散落在Controller中,影响代码质量

3.为了解决第一个问题,到处记笔记

项目背景

最近在项目上对接前端的的时候遇到了几个问题

1.经常要问前端要请求参数

2.要根据请求参数写大量if...else,代码散步在 Controller 中,影响代码质量

3.为了解决问题1,到处记日志,导致到处改代码

解决方案

Springboot中如何结合@Valid和AOP实现参数校验与日志记录?

为了解决这类问题,我使用了@Valid 做参数校验,并使用AOP记录前端请求日志

1.Bean实体类增加注解

对要校验的实体类增加注解,如果实体类中有List结构,就在List上加@Valid

@Valid注解

注解 备注 @Null 只能为null @NotNull 必须不为null @Max(value) 必须为一个不大于 value 的数字 @Min(value) 必须为一个不小于 value 的数字 @AssertFalse 必须为false @AssertTrue 必须为true @DecimalMax(value) 必须为一个小于等于 value 的数字 @DecimalMin(value) 必须为一个大于等于 value 的数字 @Digits(integer,fraction) 必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction @Past 必须是 日期 ,且小于当前日期 @Future 必须是 日期,且为将来的日期 @Size(max,min) 字符长度必须在min到max之间 @Pattern(regex=,flag=) 必须符合指定的正则表达式 @NotEmpty 必须不为null且不为空(字符串长度不为0、集合大小不为0) @NotBlank 必须不为空(不为null、去除首位空格后长度不为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 @Email 必须为Email,也可以通过正则表达式和flag指定自定义的email格式

UserInfo

package com.zero.check.query; import lombok.Data; import org.hibernate.validator.constraints.EAN; import org.springframework.stereotype.Component; import javax.validation.Valid; import javax.validation.constraints.*; import java.util.List; /** * @Description: * @author: wei.wang * @since: 2019/11/21 15:05 * @history: 1.2019/11/21 created by wei.wang */ @Component @Data public class UserInfo { @NotBlank(message = "主键不能为空") @Pattern(regexp = "^[1-9]\\d*$",message = "主键范围不正确") private String id; @Valid @NotEmpty(message = "用户列表不能为空") private List<User> userList; @NotNull(message = "权限不能为空") @Min(value = 1, message = "权限范围为[1-99]") @Max(value = 99, message = "权限范围为[1-99]") private Long roleId; }

User

package com.zero.check.query; import lombok.Data; import org.springframework.stereotype.Component; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; /** * @Description: * @author: wei.wang * @since: 2019/11/21 16:03 * @history: 1.2019/11/21 created by wei.wang */ @Component @Data public class User { @NotBlank(message = "用户工号不能为空") private String userId; @NotBlank(message = "用户名称不能为空") private String userName; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }

2.Controller层

在需要校验的pojo前边添加@Validated,在需要校验的pojo后边添加BindingResult br接收校验出错信息,需要注意的是, BindingResult result一定要跟在 @Validated 注解对象的后面(必须是实体类),而且当有多个@Validated注解时,每个注解对象后面都需要添加一个 BindingResult,而实际使用时由于在WebLogAspect切点读取了请求数据,会导致在Controller层请求参数中读不到数据,这里需要修改其他内容,详见Git

DataCheckController package com.zero.check.controller; import com.zero.check.query.User; import com.zero.check.query.UserInfo; import com.zero.check.utils.Response; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; /** * @Description: * @author: wei.wang * @since: 2019/11/21 14:57 * @history: 1.2019/11/21 created by wei.wang */ @RestController @RequestMapping(value = "/check") public class DataCheckController { @PostMapping(value = "/userValidPost") public Response queryUserPost(@Valid @RequestBody UserInfo userInfo, BindingResult result) { return Response.ok().setData("Hello " + userInfo.getId()); } @GetMapping(value = "/userValidGet") public Response queryUserGet(@Valid User user, BindingResult result) { return Response.ok().setData("Hello " + user.getUserName()); } }

3.AOP

定义切点@Pointcut("execution(com.zero.check.controller.. (..))"),定义后可监控com.zero.check.controller包和子包里任意方法的执行

如果输入参数不能通过校验,就直接抛出异常,由于定义了UserInfoHandler拦截器,可以拦截处理校验错误,这样就可以省略大量的非空判断,让Controller层专注业务代码,并且将日志集中在WebLogAspect中处理,不会因为记录日志导致要到处改代码

if (bindingResult.hasErrors()) { FieldError error = bindingResult.getFieldError(); throw new UserInfoException(Response.error(error.getDefaultMessage()).setData(error)); } UserInfoHandler package com.zero.check.handler; import com.zero.check.exception.UserInfoException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @Description: * @author: wei.wang * @since: 2019/11/21 15:04 * @history: 1.2019/11/21 created by wei.wang */ @RestControllerAdvice public class UserInfoHandler { /** * 校验错误拦截处理 * * @param e 错误信息集合 * @return 错误信息 */ @ExceptionHandler(UserInfoException.class) public Object handle(UserInfoException e) { return e.getR(); } }

WebLogAspect

package com.zero.check.aspect; import com.alibaba.fastjson.JSON; import com.zero.check.exception.UserInfoException; import com.zero.check.utils.Response; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import javax.servlet.www.google.com" }, { "userId": "S", "userName": "www.SoSo.com" }, { "userId": "SoSo", "userName": "www.SoSo.com" } ], "roleId":"11" }

返回结果

{ "code": "ok", "data": "Hello 12", "requestid": "706cd81db49d4c9795e5457cebb1ba8c" }

请求参数

{ "id":"1A2", "userList": [ { "userId": "Google", "userName": "www.google.com" }, { "userId": "S", "userName": "www.SoSo.com" }, { "userId": "SoSo", "userName": "www.SoSo.com" } ], "roleId":"11" }

返回结果

{ "code": "error", "message": "主键范围不正确", "data": { "codes": [ "Pattern.userInfo.id", "Pattern.id", "Pattern.java.lang.String", "Pattern" ], "arguments": [ { "codes": [ "userInfo.id", "id" ], "arguments": null, "defaultMessage": "id", "code": "id" }, [], { "defaultMessage": "^[1-9]\\d*$", "arguments": null, "codes": [ "^[1-9]\\d*$" ] } ], "defaultMessage": "主键范围不正确", "objectName": "userInfo", "field": "id", "rejectedValue": "1A2", "bindingFailure": false, "code": "Pattern" }, "requestid": "076c899495b448b59f1b133efd130061" }

控制台输出

可以看到第一次请求时WebLogAspect成功打印了请求数据和返回结果,而第二次因为没有通过校验,没有进入WebLogAspect,所以没有打印数据

2019-11-21 22:50:43.283 INFO 94432 --- [nio-9004-exec-2] com.zero.check.aspect.WebLogAspect : REQUEST: { "id":"1", "userList": [ { "userId": "Google", "userName": "www.google.com" }, { "userId": "S", "userName": "www.SoSo.com" }, { "userId": "SoSo", "userName": "www.SoSo.com" } ], "roleId":"11" } 2019-11-21 22:50:43.345 INFO 94432 --- [nio-9004-exec-2] com.zero.check.aspect.WebLogAspect : RESPONSE: {"code":"ok","data":"Hello 1","requestid":"286174a075c144eeb0de0b8dbd7c1851"}

GET接口

localhost:9004/check/userValidGet?userId=a&userName=zero

返回结果

{ "code": "ok", "data": "Hello zero", "requestid": "9b5ea9bf1db64014b0b4d445d8baf9dc" } localhost:9004/check/userValidGet?userId=a&userName=

返回结果

{ "code": "error", "message": "用户名称不能为空", "data": { "codes": [ "NotBlank.user.userName", "NotBlank.userName", "NotBlank.java.lang.String", "NotBlank" ], "arguments": [ { "codes": [ "user.userName", "userName" ], "arguments": null, "defaultMessage": "userName", "code": "userName" } ], "defaultMessage": "用户名称不能为空", "objectName": "user", "field": "userName", "rejectedValue": "", "bindingFailure": false, "code": "NotBlank" }, "requestid": "5677d93c084d418e88cf5bb8547c5a2e" }

控制台输出

可以看到第一次请求时WebLogAspect成功打印了请求和返回结果,而第二次因为没有通过校验,没有进入WebLogAspect,所以没有打印数据

2019-11-21 23:18:50.755 INFO 94432 --- [nio-9004-exec-9] com.zero.check.aspect.WebLogAspect : REQUEST: {userName=zero, userId=a} 2019-11-21 23:18:50.756 INFO 94432 --- [nio-9004-exec-9] com.zero.check.aspect.WebLogAspect : RESPONSE: {"code":"ok","data":"Hello zero","requestid":"422edc9cd59d45bea275e579a67ccd0c"}

5.代码Git地址

git@github.com:A-mantis/SpringBootDataCheck.git

总结

以上所述是小编给大家介绍的Springboot使用@Valid 和AOP做参数校验及日志输出问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对易盾网络网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!