SpringMVC aop实现方法级别的手动数据校验

254 阅读3分钟

引言

今天开会,说前端与后端数据交互使用restful形式,把请求数据放到Http请求的头部中。这样后台的数据校验就跟之前用json有一些不同。大家开发中应该也遇到过类似的问题,如果是json形式的请求参数可以直接封装到java bean中,用Valid注解跟bindResult去处理数据校验结果。 但如果只有一两个基本型参数,如String name, int age,封装到一个bean中反而觉得有点麻烦,那就需要用到手动数据校验。

请求参数在http请求头部中

比如前端是这样传来一个name跟age,

http://xxx.com/companies?name=xxx&age=

在controller中接收的参数如下,参数不是一个实体,那只能手动实现方法级别的数据校验。后面的步骤虽然费点功夫,但是很灵活。

@RequestMapping("doRegist")
@Valid
public String registHandler(@NotBlank @NotNull String name, @Min(value=1, message="age too young") String age)

手动校验的步骤

1. pom.xml中添加配置

<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>

<dependency>
   <groupId>org.glassfish.web</groupId>
   <artifactId>javax.el</artifactId>
   <version>2.2.4</version>
</dependency>

<dependency>
   <groupId>org.hibernate.validator</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>6.0.17.Final</version>
</dependency>

2. Srping中的aop配置

<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />

3.AOP切入类

下面的例子就是上述controller中的name,跟age参数进行的手动数据校验。
该类通过添加@Aspect注册到AOP容器中。

  • 通过JoinPoint类获得被切入的对象,跟方法,以及方法参数。
  • JoinPoint#getThis()方法,可以获得上述controller的class对象
  • 通过controller的class对象的反射,获得需要数据校验方法的参数
@Component
@Aspect
public class ValidationAspect {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	
	// 定义切入点。这里是对包下所有的注解了@Valid的controller进行切入
	@Pointcut("execution(* com.cuts.siled.controller..*.*(..))&& @annotation(javax.validation.Valid))")
	public void cutPoint() {}
	
	// controller执行前,切入
	@Before(value = "cutPoint()")
	public void startValidCheck(JoinPoint jp) throws NoSuchMethodException, SecurityException {
		logger.debug("@Before: {}" , jp.getSignature());
		
		Object[] values = new Object[jp.getArgs().length];
		// 获得controller的class对象
		Class<? extends Object> cls = jp.getThis().getClass();
		
		// 通过反射,获得controller的参数,参数值
		Class[] args = new Class[jp.getArgs().length];
		for (int i = 0; i < jp.getArgs().length; i++) {
			args[i] = jp.getArgs()[i].getClass();
			values[i] = jp.getArgs()[i];
		}
		
		ValidUtil util = new ValidUtil();
		HashMap<String , String> map = new HashMap<String, String>();
		
		// 使用工具类进行校验,并把结果放在map中。具体怎么处理map中的结果,根据业务
		boolean normal = util.validateMethod(jp.getThis(), cls.getMethod(jp.getSignature().getName(), args), values, map);
		
		if (!normal) {
			for (String key : map.keySet()) {
				logger.error("key:{},value:{}", key, map.get(key));
			}
			throw new ValidationException("validation error");
		}
		
	}
}

4.使用工具类手动校验

手动校验可以参考Hibernate Validator官方文档。这里是一个简单的例子,手动校验,并把结果存在HashMap中。

<参考>
docs.jboss.org/hibernate/v…

public class ValidUtil {

	private Validator validator;
	private ExecutableValidator executableValidator;
	
	public ValidUtil() {
		validator = Validation.buildDefaultValidatorFactory().getValidator();
		executableValidator = validator.forExecutables();
	}
	
	public boolean validateMethod(Object requestVO, Method method,Object[] parameterValues,HashMap<String, String> result) {
		Set<ConstraintViolation<Object>> violations = executableValidator.validateParameters(requestVO, method, parameterValues);
		
		if (violations.isEmpty()) {
			return true;
		}
		
		setResult(result, violations);
		return false;

	}
}

5.执行结果

[04 14:42:56,088 ERROR] [http-nio-8080-exec-1] validate.ValidationAspect - key:registHandler.arg0,value:nick must be 4-10 chars or numbers.
[04 14:42:56,088 ERROR] [http-nio-8080-exec-1] validate.ValidationAspect - key:registHandler.arg1,value:age too young