SpringBoot中的请求拦截

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: SpringBoot中的请求拦截

前言

大家好,一直以来我都本着 用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫


适合人群

  • 学完Java基础
  • 想通过Java快速构建web应用程序
  • 想学习或了解SpringBoot

大佬可以绕过 ~


背景

如果你是一路看过来的,很高兴你能够耐心看完。之前带大家学了Springboot基础部分,对基本的使用有了初步的认识, 接下来的几期内容将会带大家进阶使用,会先讲解基础中间件的使用和一些场景的应用,或许这些技术你听说过,没看过也没关系,我会带大家一步一步的入门,耐心看完你一定会有收获~


情景回顾

上期带大家学习了日志的集成, 本期将带大家学习SpringBoot中如何进行请求拦截,同样的,我们集成到Springboot中。最近github可能会被墙,所以我把源码放到了国内gitee上,本节我们依然使用上期的代码


项目源码(持续更新⭐️)


为什么要进行请求拦截

首先大家可以想一下这个问题,我们为什么要去拦截?当你想清楚了这个问题后,你就知道用它要做啥了。


比如我想知道前端传过来的参数是啥,或者我返回给它啥结果,那么你可以去拦截请求和请求响应结果, 这就是所谓记录请求日志。再比如,我想要对所以请求进行验证,比如签名验证,通过某个签名参数去验证请求是否通过。其实请求拦截不止在服务端,其实前端也有这样的拦截,比如前端拦截请求发生前,对参数统一加签名,统一加请求头,对响应结果做拦截处理,未登录跳登录页等等,都是统一处理。


这样的好处是什么呢?这样对代码的维护而言比较友好,减少了很多繁琐的代码,易扩展


拦截请求日志

我们以记录请求日志为例,带大家熟悉一下如何去做拦截。首先我们知道,请求过来的时候,首先最先进入的就是我们的控制器层,所以我们很清楚拦截的地方在Controller,所以我们只要在它进入方法之前做处理就好了。


我们回顾一下,怎么样可以拦截一个方法呢?我们可以通过反射的机制去拦截,但是我们有更好的方式,我们可以利用Spring提供的aop去拦截。好,知道实现的方式了,我们现在就去实现它,如果你还不知道aop反射可以阅读我的这篇文章,你一定会有所收获~


这是一篇长文,可能需要花点时间~


实现 WebLog

修改pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!--Hutool Java工具包-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.5.7</version>
</dependency>
复制代码


lombok这个小工具之前没给大家介绍,它用起来很方便,帮我们省略了写getter和setter方法, 举个例子

@Data
public class WebLog {
    /**
     * 请求参数
     */
    private Object params;
    /**
     * URI
     */
    private String uri;
    /**
     * URL
     */
    private String url;
    /**
     * 请求类型
     */
    private String method;
    /**
     * 操作时间
     */
    private Long startTime;
    /**
     * 消耗时间
     */
    private Integer spendTime;
    /**
     * 根路径
     */
    private String basePath;
    /**
     * 请求返回的结果
     */
    private Object response;
    /**
     * IP地址
     */
    private String ip;
}
复制代码

可以试试实例化后,它会自动的出现了get和set方法


获取请求对象

首先获取当前请求对象。通过如下方法获取:

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
复制代码


这样我们就得到了Request对象,有没有发现,这个跟我们之前在讲Controller的时候提到的请求对象是一样的


那怎么获取请求参数呢?我们知道参数都写在控制器的方法参数上,所以通过反射机制可以拿到:

MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
复制代码


这是获取方法对象,在aop中,我们可以通过切点获取参数

joinPoint.getArgs()
复制代码


然后对它进行遍历即可,响应结果可以通过执行结果获取

Object result = joinPoint.proceed()
复制代码


完整案例

这里给大家展示完整案例,不然看起来有点懵逼

@Aspect
@Component
@Order(1)
public class WebLogFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebLogFilter.class);
    @Pointcut("execution(public * com.example.app.controller.*.*(..))")
    public void webLog() {
    }
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }
    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息
        WebLog webLog = new WebLog();
        Object result = joinPoint.proceed();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        long endTime = System.currentTimeMillis();
        String urlStr = request.getRequestURL().toString();
        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        webLog.setIp(getIp(request));
        webLog.setMethod(request.getMethod());
        webLog.setParams(getParameter(method, joinPoint.getArgs()));
        webLog.setResponse(result);
        webLog.setSpendTime((int) (endTime - startTime));
        webLog.setStartTime(startTime);
        webLog.setUri(request.getRequestURI());
        webLog.setUrl(request.getRequestURL().toString());
        LOGGER.info("{}", JSONUtil.parse(webLog));
        return result;
    }
    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
    public static String getIp(HttpServletRequest req){
        String ip = req.getHeader("Origin");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = req.getHeader("x-forwarded-for");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = req.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = req.getHeader("X-Real-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = req.getRemoteAddr();
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = "127.0.0.1";
        }
        String[] ips = ip.split(",");
        LOGGER.info("抓取的ip为:{}",ips[0]);
        return ips[0].trim();
    }
}
复制代码

这样我们就把请求的信息发放到WebLog对象里了,后期如果你想存到数据库,就可以直接塞了,可以做成报表,你也可以记录到log文件里,在开发的时候,控制台也方便调试,是不是很nice,甚至到了系统业务慢慢增多的时候,你可以把它收集到专门的日志系统里。


结束语

本期到这里就结束了,总结一下,本节主要讲了如何去通过aop拦截请求,做了一个请求日志的小功能,大家可以自行试一下。也可以举一反三,不一定拦截请求,你也可以拦截Service做一些事情,或是自定义的注解,反正能做的事情很多,工具是死的,人是活的,所以多去思考,换做是你,有什么好的方式可以提高开发效率和系统健壮


下期预告

之前我们讲mybatis的时候,其实还有一个比较重要的知识点没讲,就是我们的事务处理, 下期将会带大家学习一下,没听过没关系, 我会一步步从入门讲起, 涉及的理论可能较多。

下期见, 关注我,不迷路~

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
5月前
|
Java 网络架构 Spring
springboot中restful风格请求的使用
本文介绍了在Spring Boot中如何使用RESTful风格的请求,包括创建HTML表单页面、在application.yaml配置文件中开启REST表单支持、编写Controller层及对应映射处理,并进行服务启动和访问测试。HTML表单默认只支持GET和POST请求,因此对于DELETE和PUT请求,需要使用隐藏域`_method`来支持。
springboot中restful风格请求的使用
|
23天前
|
Java 应用服务中间件 Spring
SpringBoot 响应请求是串行还是并行?
Spring Boot 在默认情况下通过 Servlet 容器的线程池实现并行处理 HTTP 请求。通过适当的线程池配置,可以进一步优化并发性能。此外,Spring Boot 提供了异步处理机制(如使用 `@Async` 注解)和反应式编程模型(Spring WebFlux),使得应用能够处理更高的并发负载。在具体项目中,可以根据需求选择合适的处理模型,以充分利用 Spring Boot 的并发处理能力。
55 21
|
4月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
138 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
3月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
76 2
|
3月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
53 0
|
4月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
4月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
323 2
|
4月前
|
前端开发 Java
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
文章介绍了如何使用SpringBoot创建简单的后端服务器来处理HTTP请求,包括建立连接、编写Controller处理请求,并返回响应给前端或网址。
78 0
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
|
5月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
67 6
|
4月前
|
SQL JSON 缓存
你了解 SpringBoot 在一次 http 请求中耗费了多少内存吗?
在工作中常需进行全链路压测并优化JVM参数。通过实验可精确计算特定并发下所需的堆内存,并结合JVM新生代大小估算GC频率,进而优化系统。实验基于SpringBoot应用,利用JMeter模拟并发请求,分析GC日志得出:单次HTTP请求平均消耗约34KB堆内存。复杂环境下,如公司线上环境,单次RPC请求内存消耗可达0.5MB至1MB,揭示了高并发场景下的内存管理挑战。

热门文章

最新文章