前言

有好久没写java相关的博文了。最近又忙于java项目,有了一个新的需求。

具体需求如下:

以一种通用的方法,不修改原来代码的情况下,符合开闭原则,对某一特定方法进行请求参数校验,比如判空

这样一来就涉及到面向切面编程了,会用到的东西就是 spring aop。

下面说下面对这一场景,如何完成的代码实现。

环境

  1. mac
  2. java 1.8
  3. maven

框架

框架选型: springboot、validation-api

看下 maven 的依赖图,validation-api 用于验证参数注解,进行校验。它的父包来自 hibernate-validator,而hibernate-validator的父包来自 spring-boot-starter-web

springboot aop实现方法的请求参数校验_aop

所以pom.xml引入以下:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

代码实现

文件目录结构:
|--service
|-impl
|-xxxxServiceImpl.java
|-xxxService.java
  1. 前置通知,扫描某service包下某个实现类的所有实现方法,进行参数校验。

xxxServiceImpl.java 源码 :

@Override
public PersonReportResponseParam personReport(PersonReportRequestParam requestParam) {
}

PersonReportRequestParam.java 源码:

@Data
public class PersonReportRequestParam {
/**
* 姓名
*/
@NotNull(message = "姓名不能为空!")
private String name;

/**
* 性别
*/
@NotNull(message = "性别不能为空!")
private String gendar;
}

Apsect前置校验:

@Aspect
@Component
public class ValidationAspect {

@Autowired
private Validator validator;

/**
* 前置通知
* @param joinPoint
*/
@Before("execution( * com.xxx.service.imp.xxxxServiceImpl.*(..))")
public void permissionCheck(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args == null) {
return;
}
for (Object o : args) {
if (o == null) {
continue;
}
Set<ConstraintViolation<Object>> result = validator.validate(o);
if (result.size() > 0) {
ConstraintViolation<Object> v = result.iterator().next();
String message = v.getPropertyPath() + " " + v.getMessage();
throw new ConstraintViolationException(message, result);
}
}
}
}
  1. 直接扫描 RequestMapping 注解进行参数校验。

xxxService.java源码:

在这里插入代码片

Apsect前置校验:

@Aspect
@Component
public class ValidationAspect {

@Autowired
private Validator validator;

@Before(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void start(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args == null) {
return;
}
for (Object o : args) {
if (o == null) {
continue;
}
Set<ConstraintViolation<Object>> result = validator.<Object>validate(o);
if (result.size() > 0) {
ConstraintViolation<Object> v = result.iterator().next();
String message = v.getPropertyPath() + " " + v.getMessage();
throw new ConstraintViolationException(message, result);
}
}
}
}

注意:这里抛出去的异常,会到你通知作用的范围内,比如@Before中的是扫描的是@RequestMapping 注解,则会抛到异常参数的 Controller 层。

相关概念补充说明

aop相关概念

  • Joinpoint(连接点): 类里面可以被增强的方法,这些方法称为连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • Aspect(切面): 是切入点和通知(引介)的结合
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
  • Target(目标对象):代理的目标对象(要增强的类)
  • Weaving(织入):是把增强应用到目标的过程,把advice 应用到 target的过程
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
    其实我们只需要这么记忆即可:

切入点:在类里边可以有很多方法被增强,比如实际操作中,只是增强了个别方法,则定义实际被增强的某个方法为切入点。
通知/增强:增强的逻辑,称为增强,比如扩展日志功能,这个日志功能称为增强。包括:​​​​​​​

切面:把增强应用到具体方法上面的过程称为切面。

通知类型

  • @Before前置通知:在方法之前执行
  • @After后置通知:在方法之后执行
  • @AfterRunning异常通知:方法出现异常执行
  • @AfterThrowing最终通知:在后置之后执行
  • @Around环绕通知:在方法之前和之后执行

表达式配置切入点

execution( * com.xxx.service.imp.xxxxServiceImpl.*(..))

例如上述表达式:

1、 execution():用于匹配方法执行的连接点;

2、第一个*号:表示返回类型, *号表示所有的类型。

3、包名:表示需要拦截的包名,以及具体的类名

4、*(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数

以上,aop使用的一些实战小技巧。果然,实战才能使人记忆深刻。