详解Spring AOP 实现“切面式”valid校验

时间:2021-08-25 14:45:58

why:

为什么要用aop实现校验

answer:

spring mvc 默认自带的校验机制 @valid + bindingresult, 但这种默认实现都得在controller方法的中去接收bindingresult,从而进行校验.

eg:

?
1
2
3
4
5
6
7
if (result.haserrors()) {
 list<objecterror> allerrors = result.getallerrors();
 list<string> errorlists = new arraylist<>();
  for (objecterror objecterror : allerrors) {
    errorlists.add(objecterror.getdefaultmessage());
  }
 }

获取errorlists。这样实现的话,每个需要校验的方法都得重复调用,即使封装也是。

可能上面那么说还不能表明spring 的@valid + bindingresult实现,我先举个“栗子”。

1. 栗子(旧版本)

1.1 接口层(idal)

eg: 简单的post请求,@requestbody接收请求数据,@valid + bindingresult进行校验

  1. httpmethid: post
  2. parameters:@requestbody接收请求数据
  3. valid:@valid +bindingresult
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@responsebody
 @postmapping("body")
 public responsevo bodypost(@requestbody @valid testvo body,bindingresult result){
  //校验到错误
  if (result.haserrors()) {
   list<objecterror> allerrors = result.getallerrors();
   list<string> lists = new arraylist<>();
   for (objecterror objecterror : allerrors) {
     lists.add(objecterror.getdefaultmessage());
   }
   return new responsevo(httpstatus.bad_request.value(), "parameter empty", lists);
 }
   return new responsevo(httpstatus.ok.value(), "bodypost", null);
}

1.2 实体(vo)校验内容

@valid + bindingresult的校验注解一大堆,网上一摸就有的!

?
1
2
3
4
5
6
7
8
9
10
public class testvo {
  @getter
  @setter
  @min(value = 0,message = "请求参数isstring不能小于0")
  private integer isint;
  @getter
  @setter
  @notblank(message = "请求参数isstring不能为空")
  private string isstring;
}

1.3 结果测试

详解Spring AOP 实现“切面式”valid校验

2. aop校验(升级版)

可以看到若是多个像bodypost一样都需要对body进行校验的话,那么有一坨代码就必须不断复现,即使改为父类可复用方法,也得去调用。所以左思右想还是觉得不优雅。所以有了aop进行切面校验。

2.1 接口层(idal)

是的!你没看错,上面那一坨代码没了,也不需要调用父类的的共用方法。就单单一个注解就完事了:@paramvalid

?
1
2
3
4
5
6
@paramvalid
@responsebody
@postmapping("body")
public responsevo bodypost(@requestbody @valid testvo body,bindingresult result){
  return new responsevo("bodypost", null);
}

2.2 自定义注解(annotation)

这个注解也是简简单单的用于方法的注解。

?
1
2
3
@target(elementtype.method)
@retention(retentionpolicy.runtime)
public @interface paramvalid {}

2.3 重点!切面实现(aspect)

切面详解:

@before: 使用注解方式@annotation(xx),凡是使用到所需切的注解(@paramvalid),都会调用该方法

joinpoint: 通过joinpoint获取方法的参数,以此获取bindingresult所校验到的内容

迁移校验封装: 将原先那一坨校验迁移到aspect中:validrequestparams

响应校验结果:

  1. 通过requestcontextholder获取response
  2. 获取响应outputstream
  3. 将bindingresult封装响应
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@aspect
@component
public class paramvalidaspect {
 
  private static final logger log = loggerfactory.getlogger(paramvalidaspect.class);
 
  @before("@annotation(paramvalid)")
  public void paramvalid(joinpoint point, paramvalid paramvalid) {
    object[] paramobj = point.getargs();
    if (paramobj.length > 0) {
      if (paramobj[1] instanceof bindingresult) {
        bindingresult result = (bindingresult) paramobj[1];
        responsevo errormap = this.validrequestparams(result);
        if (errormap != null) {
          servletrequestattributes res = (servletrequestattributes) requestcontextholder.getrequestattributes();
          httpservletresponse response = res.getresponse();
          response.setcharacterencoding("utf-8");
          response.setcontenttype(mediatype.application_json_utf8_value);
          response.setstatus(httpstatus.bad_request.value());
 
          outputstream output = null;
          try {
            output = response.getoutputstream();
            errormap.setcode(null);
            string error = new gson().tojson(errormap);
            log.info("aop 检测到参数不规范" + error);
            output.write(error.getbytes("utf-8"));
          } catch (ioexception e) {
            log.error(e.getmessage());
          } finally {
            try {
              if (output != null) {
                output.close();
              }
            } catch (ioexception e) {
              log.error(e.getmessage());
            }
          }
        }
      }
    }
  }
 
  /**
   * 校验
   */
  private responsevo validrequestparams(bindingresult result) {
    if (result.haserrors()) {
      list<objecterror> allerrors = result.getallerrors();
      list<string> lists = new arraylist<>();
      for (objecterror objecterror : allerrors) {
        lists.add(objecterror.getdefaultmessage());
      }
      return new responsevo(httpstatus.bad_request.value(), "parameter empty", lists);
    }
    return null;
  }
}

2.4 测试结果

 详解Spring AOP 实现“切面式”valid校验

看了上面两种结果,就可以对比出使用spring aop 配合@valid + bindingresult进行校验的优点:

  1. 去除代码冗余
  2. aop异步处理
  3. 优化代码实现

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://juejin.im/post/5a5e1159518825732b19d8ce