SpringBoot通过AOP与注解实现入参校验详情

目录
  • 前言:
  • 注解标记
  • 通过AOP对方法进行增强
  • 测试Get请求
  • 测试POST请求
  • 解决方法代码
  • 再次测试POST请求

前言:

问题源头:

在日常的开发中,在Service层经常会用到对某一些必填参数进行是否存在的校验。比如我在写一个项目管理系统:

这种必填参数少一些还好,如果多一些的话光是if语句就要写一堆。像我这种有代码洁癖的人看着这一堆无用代码更是难受。

如何解决:

在Spring里面有一个非常好用的东西可以对方法进行增强,那就是AOP。AOP可以对方法进行增强,比如:我要校验参数是否存在,可以在执行这个方法之前对请求里面的参数进行校验判断是否存在,如果不存在就直接的抛出异常。

因为不是所有的方法都需要进行必填参数的校验,所以我还需要一个标识用来标记需要校验参数的方法,这个标记只能标记在方法上。这一部分的功能可以使用Java中的注解来实现。然后配合AOP来实现必填参数的校验。

代码实现:

注解标记

这个是标记注解的代码:

package com.gcs.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckRequireParam {
    String[] requireParam() default "";

}

@Target({ElementType.METHOD}):作用是该注解只能用到方法上

@Retention(RetentionPolicy.RUNTIME):注解不仅被保留到 class 文件中,JVM 加载 class 文件之后,会仍然存在

这个里面还有一个requireParam参数,用来存放必填参数的Key

通过AOP对方法进行增强

需要依赖的Jar:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>版本号</version>
</dependency>
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>版本号</version>
 </dependency>

因为这里是要在执行一个方法之前对传入的参数进行校验,所以这里使用到了AOP的环绕通知

AOP里面的通知方式:

  • Before:前置通知
  • After:后置通知
  • Around:环绕通知

这里我选用的是环绕通知,环绕通知是这几个通知中最强大的一个功能。我选择环绕通知的一个原因是,环绕通知可以通过代码来控制被代理方法是否执行。

现在需要创建一个切面类,并且该类需要被@Aspect@Component标记:

  • @Aspect:表明当前类是一个切面类
  • @Component:将其放到IOC里面管理
@Component
@Aspect
public class CheckRequireParamAop {
    //.....do something
}

这个类里面加了一个方法有来设置切点,通过@Pointcut注解

@Pointcut:这个参数是一个表达式,其作用是用来指定哪些方法需要被"增强"

@Pointcut("@annotation(com.gcs.demo.annotation.CheckRequireParam)")
public void insertPoint(){
}

接下来就是要写一个增强的方法,因为我是选用的环绕通知,所以该方法需要被@Around标记

@Around("insertPoint()")
public Object checkParam(ProceedingJoinPoint proceedingJoinPoint){
	//.....do something
}

然后就要具体的来聊一下这个checkParam方法里面要做什么事情了。

首先,这个的功能是校验参数,那么首先要做的是将请求的参数获取到。这里获取参数的方式就要区分成GETPOST请求。GET请求还好可以通过HttpServletRequest对象里面的getParameterMap方法可以直接获取到,然而POST通过这个方法就不可以了。

public Map<String,String> getRequestParams(HttpServletRequest request) throws IOException {
    Map<String,String> resultParam = null;
    if(request.getMethod().equalsIgnoreCase("POST")){
        StringBuffer data = new StringBuffer();
        String line = null;
        BufferedReader reader = request.getReader();
        while (null != (line = reader.readLine()))
            data.append(line);
        if(data.length() != 0) {
             resultParam = JSONObject.parseObject(data.toString(), new TypeReference<Map<String,String>>(){});
        }
    }else if(request.getMethod().equalsIgnoreCase("GET")){
        resultParam = request.getParameterMap().entrySet().stream().collect(Collectors.toMap(i -> i.getKey(), e -> Arrays.stream(e.getValue()).collect(Collectors.joining(","))));
    }
    return resultParam != null ? resultParam : new HashMap();
}

这里通过if分成了两块:

POST

  • POST无法通过getParameter获取到参数,请求体只能通过getInputStream或者是getReader来获取到。通过流的方式获取到后,通过FastJson里面的方法将其转成Map返回就好了

GET

  • GET方法就简单了,直接通过getParameterMap方法返回一个Map即可,这里也对直接获取到的Map做了下处理,通过这个方法获取到的Map它的泛形是<String,String[]>,我将这个数组里面的元素通过逗号给拼接了起来形成一个字符串,这样的话的判断是否是空的时候就比较容易了。

获取到参数后就可以对参数进行校验是否存在了:

@Around("insertPoint()")
public Object checkParam(ProceedingJoinPoint proceedingJoinPoint){
    //获取到HttpServletRequest对象
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature();
    //获取到CheckRequireParam注解
    CheckRequireParam annotation = signature.getMethod().getAnnotation(CheckRequireParam.class);
    //获取到CheckRequireParam注解中的requireParam属性
    String[] checkParams = annotation.requireParam();
    try {
        //通过封装的方法获取到请求的参数
        Map<String,String> parameterMap = getRequestParams(request);
        //当规定了必传参数,获取到的参数里面是空的,这里就直接抛出异常
        if(checkParams.length > 0 && (parameterMap == null || parameterMap.size() == 0)){
            throw new ParamNotRequire("当前获取到的参数为空");
        }
        //通过循环判断requireParam中的属性名是否在请求参数的中是否存在
        Arrays.stream(checkParams).forEach(item ->{
            if(!parameterMap.containsKey(item)){
                throw new ParamNotRequire("参数[" + item + "]不存在");
            }
            if(!StringUtils.hasLength(parameterMap.get(item))){
                throw new ParamNotRequire("参数[" + item + "]不能为空");
            }
        });
        //这个proceed方法一定要进行调用,否则走不到代理的方法
        Object proceed = proceedingJoinPoint.proceed();
        return proceed;
    } catch (Throwable throwable) {
        //如果参数不存在会抛出ParamNotRequire异常会被这里捕获到,在这里重新将其抛出,让全局异常处理器进行处理
        if(throwable instanceof ParamNotRequire){
            throw (ParamNotRequire)throwable;
        }
        throwable.printStackTrace();
    }
    return null;
}

上面的代码总结下大概有以下几步:

  • 0x01:因为所有的参数都是在HttpServletRequest对象中获取到的,所要先获取到HttpServletRequest对象
  • 0x02:其次,还要和CheckRequireParam注解里面requireParam属性写的参数名进行对比,所以这里要获取到这个注解的requireParam属性
  • 0x03:通过代码中提供的getRequestParams方法来获取到请求的参数
  • 0x04:将requireParam属性中的值与参数Map里面的值进行对比,如果requireParam中有一个值不存在于parameterMap就会抛出异常
  • 0x05:如果参数判断通过,必须要调用proceed方法,否则会调用不到被代理的方法

代码写到这里,你创建一个Controller,然后写一个Get方法,程序应该是正常运行的,并且可以判断出哪一个参数没有传值。

测试Get请求

创建Controller是很简单的,这里我只贴出测试要用的代码:

@GetMapping("/test")
@CheckRequireParam(requireParam = {"username","age"})
public String testRequireParam(UserInfo info){
    return info.getUsername();
}

把参数按照CheckRequireParam注解的规定传入是可以正常返回没有抛出异常:

将age参数删除掉,就抛出了参数不存在的异常:

Get请求测试完美,撒花!!!!!

测试POST请求

写一个测试的方法:

@PostMapping("/postTest")
@CheckRequireParam(requireParam = {"password"})
public UserInfo postTest(@RequestBody UserInfo userInfo){
    return userInfo;
}

访问后并没有给出对应的错误信息,不过看后台是出现了非法状态异常:

这个问题的原因是,在使用@RequestBody的时候,它会通过流的方式将数据读出来(getReader或getInputStream),而这种方式读取数据只能读取一次,不能读取第二次。

这里我解决这一问题的方法是先将RequestBody保存为一个byte数组,然后继承HttpServletRequestWrapper类覆盖getReader()和getInputStream()方法,使流从保存的byte数组读取。

解决方法代码

继承HttpServletRequestWrapper类重写getInputStream和getReader方法,每次读的时候读取保存在requestBody中的数据

public class CustomRequestWrapper extends HttpServletRequestWrapper {
    private byte[] requestBody;
    private HttpServletRequest request;
    public RequestWrapper(HttpServletRequest request) {
        super(request);
        this.request = request;
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        if(this.requestBody == null){
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(),bos);
            this.requestBody = bos.toByteArray();
        }
        ByteArrayInputStream bis = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {

            }
            @Override
            public int read() throws IOException {
                return bis.read();
            }
        };
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}

增加一个过滤器,把Filter中的ServletRequest替换为ServletRequestWrapper

@Component
@WebFilter(filterName = "channelFilter",urlPatterns = {"/*"})
public class CustomFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if(request instanceof HttpServletRequest){
            requestWrapper = new CustomRequestWrapper((HttpServletRequest) request);
        }
        if(requestWrapper == null){
            filterChain.doFilter(request,servletResponse);
        }else{
            filterChain.doFilter(requestWrapper,servletResponse);
        }
    }
}

再次测试POST请求

按照CheckRequireParam规则传入参数:

不传入参数获者传入一个空的参数:

到此这篇关于SpringBoot通过AOP与注解实现入参校验详情的文章就介绍到这了,更多相关SpringBoot入参校验内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot利用AOP完成日志统计的详细步骤

    目录 1.创建日志表 2.创建实体类 3.创建枚举类 4.创建自定义注解 5.获取ip的util 6.线程池util 7.HttpServletRequest实现类 8.添加过滤器 9.添加AOP核心类 10.接口测试 步骤写的很详细,可以直接复制拿来用的,其中用到了过滤器.自定义注解以及AOP切面,来完成日志记录统计,感兴趣的收藏起来,以后遇到了可以直接用. 可能步骤会比较多,但是整体跟着思路下来,应该没什么大问题的. 项目用到了过滤器,可能有的人会不理解,之所以用过滤器是因为想要在日志记录p

  • 在springboot中使用AOP进行全局日志记录

    目录 前言 1. spring AOP 是什么? 2.spring AOP 能做什么? 3.spring AOP 我能用 AOP 解决什么问题? 一.引入依赖,增加自定义注解 1.引入 maven 依赖 2.增加自定义注解 OperationLog 二.为自定义注解编写切面实现 三.使用自定义日志注解 前言 此前项目上需要对用户的操作进行日志记录,以便后续追踪问题,所以就学习了使用 spring AOP 来进行日志记录. 1. spring AOP 是什么? spring 的两大核心就是 IOC

  • SpringBoot中通过AOP整合日志文件的实现

    目录 1.导入相关的依赖 2.log4j2 日志文件 3.dao层的接口以及实现类 4.Service层业务实现类 5.Controller层接口控制类 6.编写业务类增强类,加入一个日志文件记录 7.运行测试,查看结果 1.导入相关的依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter&

  • SpringBoot中创建的AOP不生效的原因及解决

    目录 SpringBoot 创建AOP不生效的原因 SpringBoot aop无效的情况 项目结构 SpringBoot 创建AOP不生效的原因 最近在学习SpringBoot,今天学习了Aop的注册方式,原理很简单,配置也很简单,但是我注册了切面之后切面一直不生效,是为什么呢?查了好久的资料终于发现了原因,可以看下图我的切面注册类并没有问题 然后在网上偶然看到可能是主程序扫描的原因,才发现了原因,可以看到我的显示方式本来是flat的,那样的话就很难找出原因了 修改为hirerchical就可

  • SpringBoot使用AOP统一日志管理的方法详解

    目录 前言 实现 1.引入依赖 2.定义logback配置 3.编写切面类 4.测试 前言 请问今天您便秘了吗?程序员坐久了真的会便秘哦,如果偶然点进了这篇小干货,就麻烦您喝杯水然后去趟厕所一边用左手托起对准嘘嘘,一边用右手滑动手机看完本篇吧. 实现 本篇AOP统一日志管理写法来源于国外知名开源框架JHipster的AOP日志管理方式 1.引入依赖 <!-- spring aop --> <dependency> <groupId>org.springframework

  • SpringBoot使用AOP实现统计全局接口访问次数详解

    目录 AOP是什么 AOP的作用和优势 常见的动态代理技术 AOP相关概念 实现 AOP是什么 AOP(Aspect Oriented Programming),也就是面向切面编程,是通过预编译方式和运行期间动态代理实现程序功能的传统已维护的一种技术. AOP的作用和优势 作用:在程序运行期间,在不修改源代码的情况下对某些方法进行功能增强 优势:减少重复代码,提高开发效率,并且便于维护 常见的动态代理技术 jdk代理:基于接口的动态代理技术 cglib代理:基于父类的动态代理技术 AOP相关概念

  • springboot使用AOP+反射实现Excel数据的读取

    如果我们遇到把excel表格中的数据导入到数据库,首先我们要做的是:将excel中的数据先读取出来.因此,今天就给大家分享一个读取Excel表格数据的代码示例: 为了演示方便,首先我们创建一个Spring Boot项目:具体创建过程这里不再详细介绍: 示例代码主要使用了Apache下的poi的jar包及API:因此,我们需要在pom.xml文件中导入以下依赖:         <dependency>             <groupId>org.apache.poi</

  • Springboot+AOP实现时间参数格式转换

    目录 前言 场景 效果 实战 自定义注解一 自定义注解二 拦截器 工具类 使用 接口 调用 前言 场景 前端传过来的时间参数,我们后端自定义时间格式转化使用,想转成什么就转成什么. 不同业务场景,跟前端对接,一种控件基本时间参数是固定格式的,为了避免前端去转换时间参数的格式,跟前端约定好,让他们固定传递一种格式,后端自己看需求转换格式使用即可. 效果 ① 从 yyyy-MM-dd HH:mm:ss 转换成 yyyy-MM-dd 使用: ② 从 yyyyMMddHHmmss 转换成 yyyy-MM

  • springboot aop配合反射统一签名验证实践

    目录 aop配合反射统一签名验证 接口统一签名校验 第一种aop 方式实现 第二种拦截器 aop配合反射统一签名验证 直接上代码,作为记录. CheckSignAspect.java @Aspect //定义一个切面 @Configuration @Log4j2 public class CheckSignAspect { // 定义切点Pointcut @Pointcut("execution(* com.lsj.xxl.controller.*.*CheckSign(..))")

  • SpringBoot通过AOP与注解实现入参校验详情

    目录 前言: 注解标记 通过AOP对方法进行增强 测试Get请求 测试POST请求 解决方法代码 再次测试POST请求 前言: 问题源头: 在日常的开发中,在Service层经常会用到对某一些必填参数进行是否存在的校验.比如我在写一个项目管理系统: 这种必填参数少一些还好,如果多一些的话光是if语句就要写一堆.像我这种有代码洁癖的人看着这一堆无用代码更是难受. 如何解决: 在Spring里面有一个非常好用的东西可以对方法进行增强,那就是AOP.AOP可以对方法进行增强,比如:我要校验参数是否存在

  • 使用Spring Boot AOP处理方法的入参和返回值

    前言 IOC和AOP是Spring 中最重要的两个模块.这里练习一下如何使用Spring Boot AOP处理方法的入参和返回值. Spring AOP的简单介绍: AOP(Aspect-Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理.⽇志管理.权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于提高系统的可拓展性和可维护性.

  • 基于SpringBoot项目遇到的坑--Date入参问题

    目录 SpringBoot Date入参问题 1.传输中的Date类型时间不准确 2.后台返回的json数据 springboot接口入参的一些问题 入参绑定 入参错误全局异常处理 SpringBoot Date入参问题 springboot项目遇到的坑-----使用@ResponseBody @RequestBody,对象Date 类型入参,返回json格式化 1.传输中的Date类型时间不准确 时区会有8个小时偏差 原因分析 而SpringBoot默认的是Jackson框架转换,而Jacks

  • 使用注解@Validated和BindingResult对入参进行非空校验方式

    目录 注解@Validated和BindingResult对入参非空校验 @Validated 和 BindingResult 使用遇到的坑 注解@Validated和BindingResult对入参非空校验 在项目当中少不了入参校验,服务器和浏览器互不信任,不能因为前端加入参判断了后台就不处理了,这样是不对的. 比如前台传过来一个对象作为入参参数,这个对象中有些属性允许为空,有些属性不允许为空.那么你还在使用if()else{}进行非空判断吗?不妨尝试下使用注解,可以使用@Validated和

  • SpringBoot2 参数管理实践之入参出参与校验的方式

    目录 一.参数管理 二.接收参数 三.响应参数 四.参数校验 1.借鉴参考 2.常用校验方式 五.源代码地址 一.参数管理 在编程系统中,为了能写出良好的代码,会根据是各种设计模式.原则.约束等去规范代码,从而提高代码的可读性.复用性.可修改,实际上个人觉得,如果写出的代码很好,即别人修改也无法破坏原作者的思路和封装,这应该是非常高水准. 但是在日常开发中,碍于很多客观因素,很少有时间去不断思考和优化代码,所以只能从实际情况的角度去思考如何构建系统代码,保证以后自己还能读懂自己的代码,在自己的几

  • 使用@RequestBody配合@Valid校验入参参数

    目录 @RequestBody配合@Valid校验入参参数 自定义一个Controller 自定义实体类 自定义全局异常处理器 附录 @Valid校验@RequestBody的参数 希望通过注解校验post请求的body 在request实体类添加注解进行校验 可以返回注解配置的错误信息 @RequestBody配合@Valid校验入参参数 自定义一个Controller import com.example.demo.pojo.User; import org.springframework.

  • SpringBoot打印POST请求原始入参body体方式

    目录 SpringBoot打印POST请求原始入参body体 1.首先定义过滤器配置 2.实现1中的过滤器 Post接收不到body里的参数(对象参数) 检查注解 检查实体 检查Content-Type SpringBoot打印POST请求原始入参body体 1.首先定义过滤器配置 package com.choice.o2o.device.common.config; import com.choice.o2o.device.common.filter.LogFilter; import or

  • springboot 接收List 入参的几种方法

    目录 第一种方式:使用@ModelAttribute 注解 + 对象接收 第二种方式: 使用 @RequestParam 注解接收 第三种方式:利用数组接收 第四种方式: 第五种方式: @RequestBody 加 对象 接收 第六种方式: 接收list<T>对象 第七种方式:  利用String 接收然后参数,然后在后台强转 第一种方式:使用@ModelAttribute 注解 + 对象接收 1. get 请求  入参为 projectIds=1,2,3 2. @RequestMapping

  • spring-boot使用AOP统一处理日志

    AOP我想大家都很清楚,有时候我们需要处理一些请求日志,或者对某些方法进行一些监控,如果出现例外情况应该进行怎么样的处理,现在,我们从spring-boot中引入AOP. [开发环境:jdk版本号为1.8,spring boot的版本号为1.4.1]{style="background-color:#FF0000"} 首先,我们先引入jar包, POM文件添加如下内容: <!--引用AOP--> <dependency> <groupId>org.s

  • Spring Boot接收单个String入参的解决方法

    前言 接受参数是我们在日常开发中经常会遇到的一个需求,下面这篇文章主要给大家介绍了关于Spring Boot接收单个String入参之解决方案的相关内容,下面话不多说了,来一起看看详细的介绍吧 场景: 在做接口时,有的时候,接口入参只需要一个参数,如果将一个参数封装成一个对象很麻烦,故有了以下方式: 思路: spring自带的参数解析器貌似是不具备这个能力的,所有自定义 方式方法: 1.定义一个注解 @Target(ElementType.PARAMETER) @Retention(Retent

随机推荐