背景
大家可能会问,spring MVC支持验证注解,如常用的hibernate-validator,为什么要自己实现一套呢?
最近做一个APP的服务端接口,项目中有自己的业务返回码.spring MVC支持的注解验证器无法设置验证不通过的时候的返回码,各种不方便,所以思前想后还是自己实现了一套.废话不多说,开始正文.
状态码枚举
状态码枚举中有两个属性: 状态码 和 对应的默认消息
public enum ResponseCodeEnum {
_001("001", "用户未登录");
/**
* @Fields code : 状态码
*/
private String code;
/**
* @Fields defaultMessage : 默认消息
*/
private String defaultMessage;
private ResponseCodeEnum (String code, String defaultMessage) {
this.code = code;
this.defaultMessage = defaultMessage;
}
@JsonValue // com.fasterxml.jackson.annotation.JsonValue, 项目中用了 jackson 做为
// springMVC的JSON转换器,该注解表式这个方法的返回值生成到JSON中,其他忽略
public String getCode() {
return code;
}
public String getDefaultMessage() {
return defaultMessage;
}
}
自定义业务异常
业务数据(客户端提交的)验证不过等各种业务处理中的不通过,统一使用该异常,该异常被捕获后会生成统一格式的消息返回到客户端
public class CustomValidatorException extends RuntimeException {
private static final long serialVersionUID = 5968495544349929856L;
private ResponseCodeEnum statusCode;
private String errorMsg;
public CustomValidatorException (ResponseCodeEnum statusCode, String errorMsg) {
this.statusCode = statusCode;
this.errorMsg = errorMsg;
}
public ResponseCodeEnum getStatusCode() {
return statusCode;
}
public String getErrorMsg() {
return errorMsg;
}
}
验证器接口
该接口作为验证器注解必须实现的接口,负责真正的验证
public interface IAnnotationsValidator {
public void doValidator(Object object, Annotation annotation) throws CustomValidatorException ;
}
验证器注解
验证器相关注解定义,首先得有几个基础注解
基础注解
EnableValidator 负责开启验证,使用了该注解的参数Bean才会被验证
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface EnableValidator {
}
CustomValidator 该注解作用于注解(该注解只能被其他注解使用,不能被非注解的类使用),使用了该注解的注解才被做为验证器注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
@Documented
public @interface CustomValidator {
}
验证器注解
这里先只写2个注解吧,其他的可以由其他开发人员开发
Required 必传参数注解,只有这个注解验证参数是否有值,其他注解有值才验证,没值直接通过
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited // 子类可以继承父类的注解
@CustomValidator
public @interface Required {
/**
* @Title: responseCode
* @Description: 验证失败(不通过)的code
* @return
*/
ResponseCodeEnum responseCode();
/**
* @Title: validatFailMessage
* @Description: 验证失败(不通过)的文字消息,可为空,默认使用ResponseCodeEnum对应的消息
* @return
*/
String validatFailMessage() default "";
/**
* @Fields validatorSpringBeanName : 此注解对应的验证器的springBean名称,该名称在定义注解的时候写死
*/
final String validatorSpringBeanName = "requiredValidator";
}
Required 注解中的几个属性是所有验证器注解都必须有的,大家可能注意到了validatorSpringBeanName , 没错,切面就是根据这个在spring容器中拿验证器实现的
NotEmpty 用于验证字符串,List,集合,数组,Map等不能为空,这里的空不包括null,是null以外的空
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited // 子类可以继承父类的注解
@CustomValidator
public @interface NotEmpty {
/**
* @Title: responseCode
* @Description: 验证失败(不通过)的code
* @return
*/
ResponseCodeEnum responseCode();
/**
* @Title: validatFailMessage
* @Description: 验证失败(不通过)的文字消息,可为空,默认使用ResponseStatusCodeEnum对应的消息
* @return
*/
String validatFailMessage() default "";
/**
* @Fields validatorSpringBeanName : 此注解对应的验证器的springBean名称,该名称在定义注解的时候写死
*/
final String validatorSpringBeanName = "notEmptyValidator";
}
除了上面两个验证器中的3个必须有的属性,还可以定义其他的属性,比如验证字符串长度的验证器,可以加一个长度的属性. 这些属性可以在验证器实现中获取
验证器(注解)实现
RequiredImpl: Required 的实现
@Component("requiredValidator")
public class RequiredImpl implements IAnnotationsValidator {
@Override
public void doValidator(Object object, Annotation annotation) throws CustomValidatorException {
Required notEmpty = (Required) annotation;
ResponseStatusCodeEnum statusCode = notEmpty.responseStatusCode();
String message = notEmpty.validatFailMessage();
// TODO 获取验证器注解中的其他属性
// TODO 验证,如果验证不通过,抛出 CustomValidatorException
}
}
NotEmptyImpl: NotEmpty 的实现,具体参考 Required 的实现
AOP
自定义验证器的核心实现,没有它,上面的东西全是白费
public class ValidatorAdvise {
private static Logger logger = LoggerFactory.getLogger(ValidatorAdvise .class);
public Object validator(ProceedingJoinPoint pjp) {
// 获取被拦截的方法的参数
Object[] args = pjp.getArgs();
// 遍历该方法的所有参数
if (args != null && args.length > 0) {
for (Object arg : args) {
Class<?> argClassz = arg.getClass();
if (argClassz.getAnnotation(EnableValidator.class) != null) { // 只有当该参数有EnableValidator注解,也就是开启了验证才处理
List<Field> fieldList = getAllFields(null, argClassz); // 获取所有字段
// 遍历所有字段,并找出有注解的
for (Field field : fieldList) {
// 检查每个字段的注解,有注解的才处理
Annotation[] fieldAnns = field.getAnnotations();
if (fieldAnns != null && fieldAnns.length > 0) {
// 遍历该字段的注解,找到验证器的注解
for (Annotation fieldAnn : fieldAnns) {
try {
// 检查该注解是否有@CustomValidator,有就说明是验证器
if (fieldAnn.annotationType().getAnnotation(CustomValidator.class) != null) {
// 通过反射拿验证器的springBeanName字段,不为null才处理
Field validatorSpringBeanNameFiled = fieldAnn.annotationType().getDeclaredField("validatorSpringBeanName");
if (validatorSpringBeanNameFiled != null) {
// 通过spring拿到验证器进行验证,先拿验证器的springBeanName
Object validatorSpringBeanName = validatorSpringBeanNameFiled.get(fieldAnn);
if(StringUtil.isNotNull(validatorSpringBeanName)) {
// 名字有值,从spring容器中拿对应的验证器
IAnnotationsValidator annotationsValidator = SystemApplicationContext.SPRING_CONTEXT.getBean((String)validatorSpringBeanName, IAnnotationsValidator.class);
if(annotationsValidator != null) {
// 验证器不为空,调用验证器
field.setAccessible(true);
try {
annotationsValidator.doValidator(field.get(arg), fieldAnn);
} catch (CustomValidatorException ex) {
String errMsg = null;
if(StringUtil.isNull(ex.getErrorMsg())) {
errMsg = ex.getStatusCode().getDefaultMessage();
} else {
errMsg = ex.getErrorMsg();
}
return makeResponse(ex.getStatusCode(), errMsg);
} catch (Exception ex) {
logger.error("验证器【{}】里抛出了 CustomValidatorException 以外的异常,请验证器开发人员注意!!!", ex, fieldAnn.annotationType());
return makeResponse(ResponseCodeEnum._500, "服务器内部错误:====" + ex.getMessage());
}
}
}
}
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
logger.error("验证器处理切面出了点问题", e);
}
}
}
}
}
}
}
Object ret = null;
try {
ret = pjp.proceed();
} catch (Throwable e) {
throw new RuntimeException("AOP Point Cut ValidatorAdvise Throw Exception :", e);
}
return ret;
}
/**
* @Title: getAllFields
* @Description: 递归获取该类的所有属性包括父类的爷爷类的...祖宗类的
* @param fieldList
* @param classz
* @return
*/
private List<Field> getAllFields(List<Field> fieldList, Class<?> classz) {
if(classz == null) {
return fieldList;
}
if(fieldList == null) {
fieldList = Arrays.asList(classz.getDeclaredFields()); // 获得该类的所有字段,但不包括父类的
} else {
Collections.addAll(fieldList, classz.getDeclaredFields()); // 获得该类的所有字段,但不包括父类的
}
return getAllFields(fieldList, classz.getSuperclass());
}
/**
* @Title: makeResponse
* @Description: 生成统一 Response
* @param statusCode
* @param statusMessage
* @return
*/
private AppApiResponse<?> makeResponse(ResponseCodeEnum statusCode, String statusMessage) {
AppApiResponse<Object> response = new AppApiResponse<>(new Object());
AppApiResponseHeader respHeader = new AppApiResponseHeader();
response.setHeader(respHeader);
respHeader.setStatusCode(statusCode);
respHeader.setStatusMessage(statusMessage);
return response;
}
}
捕获异常,生成统一格式响应
利用springMVC的@ControllerAdvice捕获所有来自 Controller 的异常
@ControllerAdvice(basePackages = "org.test.appApi.actions")
public class ErrorHandlingControllerAdvice {
private static Logger logger = LoggerFactory.getLogger(ErrorHandlingControllerAdvice.class);
/**
* @Title: handleValidationError
* @Description: 处理表单验证,业务异常
* @param ex
* @return
*/
@ExceptionHandler(CustomValidatorException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public AppApiResponse<?> handleValidationError(CustomValidatorException ex) {
String errMsg = null;
if(StringUtil.isNull(ex.getErrorMsg())) {
errMsg = ex.getStatusCode().getDefaultMessage();
} else {
errMsg = ex.getErrorMsg();
}
return makeResponse(ex.getStatusCode(), errMsg);
}
/**
* @Title: handleValidationError
* @Description: 处理其他异常
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public AppApiResponse<?> handleValidationError(Exception ex) {
logger.error("服务器内部错误:====", ex);
return makeResponse(ResponseCodeEnum._500, "服务器内部错误:====" + ex.getMessage());
}
private AppApiResponse<?> makeResponse(ResponseCodeEnum statusCode, String statusMessage) {
AppApiResponse<Object> response = new AppApiResponse<>(new Object());
AppApiResponseHeader respHeader = new AppApiResponseHeader();
response.setHeader(respHeader);
respHeader.setStatusCode(statusCode);
respHeader.setStatusMessage(statusMessage);
return response;
}
}
配制自定义验证器切面
springMVC的配制文件中增加
<bean id="validatorAdvise" class="org.test.appApi.actions.validator.advises.ValidatorAdvise" />
<aop:config>
<aop:aspect id="validatorAop" ref="validatorAdvise">
<aop:pointcut id="validator" expression="execution(* org.test.appApi.actions..*Action.*(..))
and !execution(* org.test.appApi.actions..*Action.initBinder(..))
and !execution(* org.test.appApi.actions..*Action.set*(..))
and !execution(* org.test.appApi.actions..*Action.get*(..))" />
<aop:around pointcut-ref="validator" method="validator" />
</aop:aspect>
</aop:config>
使用注解
使用注解很简单,springMVC的控制器中的方法可以定义任意类型的参数,把各种参数放到一个java bean中,并在该bean使用类注解 @EnableValidator开启验证,并在需要验证的类属性上使用对应的验证器注解就行,验证器注解可以多个混合使用
写在结束
到这里,自定义验证器的开发就完成了.也许大家有更好的办法,欢迎讨论.也许spring MVC可以有办法实现我想做的但我不知道,也欢迎大家指出.
相关推荐
总结,Spring MVC结合AOP和自定义注解可以方便地实现日志记录功能,无需侵入业务代码。这种方法具有良好的可扩展性和灵活性,可以轻松适应不同场景的日志需求。同时,通过调整切面的定义,我们可以控制日志记录的...
本项目“Spring+SpringMvc+MybatisPlus+Aop(自定义注解)动态切换数据源”正是针对这一需求提供的一种解决方案。下面将详细介绍这个项目中的关键技术点和实现原理。 首先,Spring框架是Java企业级应用开发的核心...
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种强大的方式来实现横切关注点,如日志、事务管理、性能监控等,而无需侵入业务代码。下面将详细介绍Spring AOP的注解方式和XML配置方式。 ### ...
在Spring MVC框架中,AOP通常用于实现日志记录、事务管理、性能监控等功能。本篇文章将深入探讨如何在Spring MVC中配置和使用基于注解的AOP。 一、Spring AOP基础知识 1. **切面(Aspect)**:切面是关注点的模块...
在Spring MVC框架中,AOP(面向切面编程)是一种强大的工具,用于实现跨切面的关注点,如日志管理。本教程将详细介绍如何利用注解来配置和使用AOP来拦截Controller层的方法,以便记录执行过程中的相关信息,实现日志...
通过以上步骤,我们创建了一个基于Spring AOP的权限验证框架,实现了对用户权限的灵活控制。这种方式不仅简化了代码,提高了代码复用性,还使得权限验证策略可以根据需求轻松调整。在实际项目中,还可以结合Spring ...
本项目“Spring MVC Mybatis Plus 实现AOP 切面日志系统”旨在提供一个基础的日志记录框架,能够自动追踪和记录应用程序的关键操作,同时支持数据持久化到数据库中,方便后期分析和问题排查。下面将详细介绍这些技术...
Spring AOP 自定义注解方式实现日志管理的实例讲解 在本文中,我们将探讨如何使用 Spring AOP 实现日志管理,并使用自定义注解方式来记录日志信息。这种方式可以灵活地实现日志管理,提高系统的可维护性和可扩展性...
标题中的“在自定义Spring AOP中使用EL获取拦截方法的变量值”指的是在Spring的面向切面编程(AOP)中,通过Expression Language(EL,表达式语言)来访问被拦截方法的局部变量值。这通常涉及到Spring的代理机制、...
在本项目中,我们将探讨如何通过配置文件实现Spring AOP,包括前置通知、后置通知以及拦截器的运用。 首先,我们需要理解Spring AOP的核心概念。切面(Aspect)是关注点的模块化,这些关注点定义了跨越多个对象的...
本项目"Spring MVC AOP通过注解方式拦截Controller等实现日志管理demo版本2"是基于注解的AOP实践,旨在帮助开发者了解如何利用AOP来记录应用程序中的关键操作日志。以下是关于这个主题的详细解释: 1. **Spring AOP...
在`SpringAOP`目录中,可能包含了定义切面、通知(advice)、切入点(pointcut)等内容。AOP的实现通常通过定义切面类,其中包含通知方法,并通过切入点表达式确定这些通知在何时何地执行。这使得代码更加模块化,...
在Spring中,AOP通过代理实现,可以是JDK动态代理或CGLIB代理。切面由两部分组成:切点(Pointcut)和通知(Advice)。切点定义了代码执行的特定位置,而通知则是在这些切点处执行的代码。Spring提供了注解驱动的AOP...
Spring MVC通过注解如@Controller、@RequestMapping等,可以让开发者在方法级别定义请求映射,简化了配置。 Spring框架的核心是依赖注入(Dependency Injection,DI),它允许组件之间通过接口进行协作,而不是硬...
- **注解AOP**:在`springaop-demo01`中,使用了注解来声明切面。比如`@Aspect`定义一个切面类,`@Before`、`@After`、`@Around`、`@Pointcut`等注解用于定义通知(Advice)和切入点(Pointcut)。 - **配置AOP**...
本主题将深入探讨如何在Spring Boot工程中通过自定义response注解、利用Java反射机制、设置自定义拦截器以及实现WebMvcConfigurer接口来实现这一目标。 首先,我们来看自定义response注解。在Spring Boot中,可以...
在这个主题“自定义注解MVC”中,我们将深入探讨如何在自定义框架中利用注解来实现控制翻转,增强代码的可读性和可维护性。 1. **自定义注解** - 注解是一种在代码中添加元信息的方式,它允许开发者在不改变程序...
本文将详细介绍如何使用AspectJ注解在Spring MVC中实现AOP拦截Controller方法,并提供一个具体的例子。 首先,我们需要了解Spring AOP的基础概念。AOP允许我们定义“切面”,这些切面包含了业务逻辑中横切关注点的...
Spring 框架是 Java 开发中的一个核心框架,它主要由 AOP(面向切面编程)、IOC(控制反转)和 MVC(模型-视图-控制器)三大组件构成。这三大组件是 Spring 提供的强大功能的核心,使得开发更加高效、灵活。 **1. ...
在本视频教程“Spring MVC + Spring + Hibernate 全注解整合开发视频教程 04”中,我们将深入探讨Java企业级开发中的三大核心技术——Spring、Spring MVC和Hibernate的集成与应用,尤其是通过注解实现的简化配置。...