SpringBoot拦截器与统一功能处理实战

前言
spring aop是一个基于面向切面编程的框架,用于将横切性关注点(如日志记录、事务管理)与业务逻辑分离,通过代理对象将这些关注点织入到目标对象的方法执行前后、抛出异常或返回结果时等特定位置执行,从而提高程序的可复用性、可维护性和灵活性。
但使用原生spring aop实现统一的拦截是非常繁琐、困难的。而在本节,我们将使用一种简单的方式进行统一功能处理,这也是aop的一次实战,具体如下:
统一用户登录权限验证
统一数据格式返回
统一异常处理
0 为什么需要统一功能处理?
统一功能处理是为了提高代码的可维护性、可重用性和可扩展性而进行的一种设计思想。在应用程序中,可能存在一些通用的功能需求,例如身份验证、日志记录、异常处理等。
这些功能需要在多个地方进行调用和处理,如果每个地方都单独实现这些功能,会导致代码冗余、难以维护和重复劳动。通过统一功能处理的方式,可以将这些通用功能抽取出来,以统一的方式进行处理。这样做有以下几个好处:
「代码复用」 :将通用功能抽取成独立的模块或组件,可以在多个地方共享使用,减少重复编写代码的工作量。
「可维护性」 :将通用功能集中处理,可以方便地对其进行修改、优化或扩展,而不需要在多个地方进行修改。
「代码整洁性」 :通过统一功能处理,可以使代码更加清晰、简洁,减少了冗余的代码。
「可扩展性」 :当需要添加新的功能时,只需要在统一功能处理的地方进行修改或扩展,而不需要在多个地方进行修改,降低了代码的耦合度。
1 统一用户登录权限验证
1.1 使用原生 spring aop 实现统一拦截的难点
以使用原生 spring aop 来实现⽤户统⼀登录验证为例,主要是使用前置通知和环绕通知实现的,具体实现如下
import org.aspectj.lang.proceedingjoinpoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.component;/** * @author 兴趣使然黄小黄 * @version 1.0 * @date 2023/7/18 16:37 */@aspect // 表明此类为一个切面@component // 随着框架的启动而启动public class useraspect {    // 定义切点, 这里使用 aspect 表达式语法    @pointcut(execution(* com.hxh.demo.controller.usercontroller.*(..)))    public void pointcut(){ }    // 前置通知    @before(pointcut())    public void beforeadvice() {        system.out.println(执行了前置通知~);    }    // 环绕通知    @around(pointcut())    public object aroundadvice(proceedingjoinpoint joinpoint) {        system.out.println(进入环绕通知~);        object obj = null;        // 执行目标方法        try {            obj = joinpoint.proceed();        } catch (throwable e) {            e.printstacktrace();        }        system.out.println(退出环绕通知~);        return obj;    }}  
从上述的代码示例可以看出,使用原生的 spring aop 实现统一拦截的难点主要有以下几个方面:
定义拦截规则非常困难。如注册⽅法和登录⽅法是不拦截的,这样的话排除⽅法的规则很难定义,甚⾄没办法定义。
在切面类中拿到 httpsession 比较难。
为了解决 spring aop 的这些问题,spring 提供了拦截器~
1.2 使用 spring 拦截器实现统一用户登录验证
spring拦截器是spring框架提供的一个功能强大的组件,用于在请求到达控制器之前或之后进行拦截和处理。拦截器可以用于实现各种功能,如身份验证、日志记录、性能监测等。
要使用spring拦截器,需要创建一个实现了handlerinterceptor接口的拦截器类。该接口定义了三个方法:prehandle、posthandle和aftercompletion。
prehandle方法在请求到达控制器之前执行,可以用于进行身份验证、参数校验等;
posthandle方法在控制器处理完请求后执行,可以对模型和视图进行操作;
aftercompletion方法在视图渲染完成后执行,用于清理资源或记录日志。
拦截器的实现可以分为以下两个步骤:
创建自定义拦截器,实现 handlerinterceptor 接口的 prehandle(执行具体方法之前的预处理)方法。
将自定义拦截器加入 webmvcconfigurer 的 addinterceptors 方法中,并且设置拦截规则。
具体实现如下:
step1. 创建自定义拦截器,自定义拦截器是一个普通类,代码如下:
import org.springframework.web.servlet.handlerinterceptor;import javax.servlet.http.httpservletrequest;import javax.servlet.http.httpservletresponse;import javax.servlet.http.httpsession;/** * @author 兴趣使然黄小黄 * @version 1.0 * @date 2023/7/19 16:31 * 统一用户登录权限验证 —— 登录拦截器 */public class logininterceptor implements handlerinterceptor {    @override    public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {        // 用户登录业务判断        httpsession session = request.getsession(false);        if (session != null && session.getattribute(userinfo) != null) {            return true; // 验证成功, 继续controller的流程        }        // 可以跳转登录界面或者返回 401/403 没有权限码        response.sendredirect(/login.html); // 跳转到登录页面        return false; // 验证失败    }}  
step2. 配置拦截器并设置拦截规则,代码如下:
import org.springframework.context.annotation.configuration;import org.springframework.web.servlet.config.annotation.interceptorregistry;import org.springframework.web.servlet.config.annotation.webmvcconfigurer;/** * @author 兴趣使然黄小黄 * @version 1.0 * @date 2023/7/19 16:51 */@configurationpublic class appconfig implements webmvcconfigurer {    @override    public void addinterceptors(interceptorregistry registry) {        registry.addinterceptor(new logininterceptor())                .addpathpatterns(/**) // 拦截所有请求                .excludepathpatterns(/user/login) // 不拦截的 url 地址                .excludepathpatterns(/user/reg)                .excludepathpatterns(/**/*.html); // 不拦截所有页面    }}  
1.3 拦截器的实现原理及源码分析
当有了拦截器后,会在调用 controller 之前进行相应的业务处理,执行的流程如下图所示:
「拦截器实现原理的源码分析」
从上述案例实现结果的控制台的日志信息可以看出,所有的 controller 执⾏都会通过⼀个调度器 dispatcherservlet 来实现。
而所有的方法都会执行 dispatcherservlet 中的 dodispatch 调度方法,dodispatch 源码如下:
protected void dodispatch(httpservletrequest request, httpservletresponse response) throws exception {    httpservletrequest processedrequest = request;    handlerexecutionchain mappedhandler = null;    boolean multipartrequestparsed = false;    webasyncmanager asyncmanager = webasyncutils.getasyncmanager(request);    try {        try {            modelandview mv = null;            object dispatchexception = null;            try {                processedrequest = this.checkmultipart(request);                multipartrequestparsed = processedrequest != request;                mappedhandler = this.gethandler(processedrequest);                if (mappedhandler == null) {                    this.nohandlerfound(processedrequest, response);                    return;                }                handleradapter ha = this.gethandleradapter(mappedhandler.gethandler());                string method = request.getmethod();                boolean isget = httpmethod.get.matches(method);                if (isget || httpmethod.head.matches(method)) {                    long lastmodified = ha.getlastmodified(request, mappedhandler.gethandler());                    if ((new servletwebrequest(request, response)).checknotmodified(lastmodified) && isget) {                        return;                    }                }                                // 调用预处理                if (!mappedhandler.applyprehandle(processedrequest, response)) {                    return;                }                // 执行 controller 中的业务                mv = ha.handle(processedrequest, response, mappedhandler.gethandler());                if (asyncmanager.isconcurrenthandlingstarted()) {                    return;                }                this.applydefaultviewname(processedrequest, mv);                mappedhandler.applyposthandle(processedrequest, response, mv);            } catch (exception var20) {                dispatchexception = var20;            } catch (throwable var21) {                dispatchexception = new nestedservletexception(handler dispatch failed, var21);            }            this.processdispatchresult(processedrequest, response, mappedhandler, mv, (exception)dispatchexception);        } catch (exception var22) {            this.triggeraftercompletion(processedrequest, response, mappedhandler, var22);        } catch (throwable var23) {            this.triggeraftercompletion(processedrequest, response, mappedhandler, new nestedservletexception(handler processing failed, var23));        }    } finally {        if (asyncmanager.isconcurrenthandlingstarted()) {            if (mappedhandler != null) {                mappedhandler.applyafterconcurrenthandlingstarted(processedrequest, response);            }        } else if (multipartrequestparsed) {            this.cleanupmultipart(processedrequest);        }    }}  
从上述源码可以看出,在执行 controller 之前,先会调用 预处理方法 applyprehandle,该方法源码如下:
boolean applyprehandle(httpservletrequest request, httpservletresponse response) throws exception {    for(int i = 0; i  true);    }}  
另一种方式是在application配置文件中配置:
server.servlet.context-path=/hxh  
2 统一异常处理
统一异常处理是指 在应用程序中定义一个公共的异常处理机制,用来处理所有的异常情况。 这样可以避免在应用程序中分散地处理异常,降低代码的复杂度和重复度,提高代码的可维护性和可扩展性。
需要考虑以下几点:
异常处理的层次结构:定义异常处理的层次结构,确定哪些异常需要统一处理,哪些异常需要交给上层处理。
异常处理的方式:确定如何处理异常,比如打印日志、返回错误码等。
异常处理的细节:处理异常时需要注意的一些细节,比如是否需要事务回滚、是否需要释放资源等
本文讲述的统一异常处理使用的是 @controlleradvice + @exceptionhandler 来实现的:
@controlleradvice 表示控制器通知类。
@exceptionhandler 异常处理器。
以上两个注解组合使用,表示当出现异常的时候执行某个通知,即执行某个方法事件,具体实现代码如下:
import org.springframework.web.bind.annotation.controlleradvice;import org.springframework.web.bind.annotation.exceptionhandler;import org.springframework.web.bind.annotation.responsebody;import java.util.hashmap;/** * @author 兴趣使然黄小黄 * @version 1.0 * @date 2023/7/19 18:27 * 统一异常处理 */@controlleradvice // 声明是一个异常处理器public class myexhandler {    // 拦截所有的空指针异常, 进行统一的数据返回    @exceptionhandler(nullpointerexception.class) // 统一处理空指针异常    @responsebody // 返回数据    public hashmap nullexception(nullpointerexception e) {        hashmap result = new hashmap();        result.put(code, -1); // 与前端定义好的异常状态码        result.put(msg, 空指针异常:  + e.getmessage()); // 错误码的描述信息        result.put(data, null); // 返回的数据        return result;    }}  
上述代码中,实现了对所有空指针异常的拦截并进行统一的数据返回。
在实际中,常常设置一个保底,比如发生的非空指针异常,也会有保底措施进行处理,类似于 try-catch 块中使用 exception 进行捕获,代码示例如下:
@exceptionhandler(exception.class) @responsebody public hashmap exception(exception e) {    hashmap result = new hashmap();    result.put(code, -1); // 与前端定义好的异常状态码    result.put(msg, 异常:  + e.getmessage()); // 错误码的描述信息    result.put(data, null); // 返回的数据    return result;}  
3 统一数据返回格式
为了保持 api 的一致性和易用性,通常需要使用统一的数据返回格式。 一般而言,一个标准的数据返回格式应该包括以下几个元素:
状态码:用于标志请求成功失败的状态信息;
消息:用来描述请求状态的具体信息;
数据:包含请求的数据信息;
时间戳:可以记录请求的时间信息,便于调试和监控。
实现统一的数据返回格式可以使用 @controlleradvice + responsebodyadvice 的方式实现,具体步骤如下:
创建一个类,并添加 @controlleradvice 注解;
实现 responsebodyadvice 接口,并重写 supports 和 beforebodywrite 方法。
示例代码如下:
import org.springframework.core.methodparameter;import org.springframework.http.mediatype;import org.springframework.http.server.serverhttprequest;import org.springframework.http.server.serverhttpresponse;import org.springframework.web.bind.annotation.controlleradvice;import org.springframework.web.servlet.mvc.method.annotation.responsebodyadvice;import java.util.hashmap;/** * @author 兴趣使然黄小黄 * @version 1.0 * @date 2023/7/19 18:59 * 统一数据返回格式 */@controlleradvicepublic class responseadvice implements responsebodyadvice {    /**     * 此方法返回 true 则执行下面的 beforebodywrite 方法, 反之则不执行     */    @override    public boolean supports(methodparameter returntype, class convertertype) {        return true;    }    /**     * 方法返回之前调用此方法     */    @override    public object beforebodywrite(object body, methodparameter returntype, mediatype selectedcontenttype, class selectedconvertertype, serverhttprequest request, serverhttpresponse response) {        hashmap result = new hashmap();        result.put(code, 200);        result.put(msg, );        result.put(data, body);        return null;    }}  
但是,如果返回的 body 原始数据类型是 string ,则会出现类型转化异常,即 classcastexception。
因此,如果原始返回数据类型为 string ,则需要使用 jackson 进行单独处理,实现代码如下:
import com.fasterxml.jackson.core.jsonprocessingexception;import com.fasterxml.jackson.databind.objectmapper;import org.springframework.beans.factory.annotation.autowired;import org.springframework.core.methodparameter;import org.springframework.http.mediatype;import org.springframework.http.server.serverhttprequest;import org.springframework.http.server.serverhttpresponse;import org.springframework.web.bind.annotation.controlleradvice;import org.springframework.web.servlet.mvc.method.annotation.responsebodyadvice;import java.util.hashmap;/** * @author 兴趣使然黄小黄 * @version 1.0 * @date 2023/7/19 18:59 * 统一数据返回格式 */@controlleradvicepublic class responseadvice implements responsebodyadvice {    @autowired    private objectmapper objectmapper;    /**     * 此方法返回 true 则执行下面的 beforebodywrite 方法, 反之则不执行     */    @override    public boolean supports(methodparameter returntype, class convertertype) {        return true;    }    /**     * 方法返回之前调用此方法     */    @override    public object beforebodywrite(object body, methodparameter returntype, mediatype selectedcontenttype, class selectedconvertertype, serverhttprequest request, serverhttpresponse response) {        hashmap result = new hashmap();        result.put(code, 200);        result.put(msg, );        result.put(data, body);        if (body instanceof string) {            // 需要对 string 特殊处理            try {                return objectmapper.writevalueasstring(result);            } catch (jsonprocessingexception e) {                e.printstacktrace();            }        }        return result;    }}  
但是,在实际业务中,上述代码只是作为保底使用,因为状态码始终返回的是200,过于死板,还需要具体问题具体分析。


等离子不锈钢抛光机的特点
Acconeer 创新的 3D传感器技术现通过 Digi-Key 全球发售
美国出口限制清单新增6项新技术,芯片技术首当其冲
LLC如何抑制工频纹波
元宇宙到底是啥?揭秘元宇宙的7个基本要素
SpringBoot拦截器与统一功能处理实战
高通发布第三代骁龙8移动平台,为下一代旗舰智能手机带来生成式AI
海信HZ55E60D AI声控电视评测 玩转智慧生态建设
为什么选用边缘计算技术管理工业机器人?有什么意义?
深圳倍泰环形导轨输送线 弧形导轨输送线
九芯电子:MP3音乐芯片的独特优势有哪些?
差分信号的传输与种类介绍
动力电池领域宁德时代与比亚迪谁将成为最后“王者”
华为Mate10保时捷版售价已确定,网友:把我卖了都买不起!
当人工智能与安防相结合 企业需要寻求更多落地场景
TE推出MAG-MATE插入式连接器
恒讯科技概述:服务器防火墙怎么设置?
模拟电路设计失误两则
芯片设计中必不可少的调试设计
成熟度曲线将追踪AI基本趋势和未来创新,人工智能技术发展的范围、状态、价值和风险