【Spring Boot】之统一功能处理

本文介绍了如何在Spring框架中实现统一功能处理,包括用户登录验证的自定义拦截器、异常处理的全局控制以及统一的数据格式返回,以提高代码可维护性和降低耦合度。
摘要由CSDN通过智能技术生成

🏀🏀🏀来都来了,不妨点个关注!
🎧🎧🎧博客主页:欢迎各位大佬!
在这里插入图片描述

1. 为什么要进行统一功能的处理

Spring统一功能处理的原因主要有以下几点:

1. 提高代码的可维护性、可重用性和可扩展性。在应用程序中,存在一些通用的功能需求,如身份验证、日志记录、异常处理等。这些功能需要在多个地方进行调用和处理。如果每个地方都单独实现这些功能,会导致代码冗余、难以维护和重复劳动。通过统一功能处理的方式,可以将这些通用功能抽取出来,以统一的方式进行处理,从而简化代码结构,提高代码的可读性和可维护性。
2. 降低系统的代码耦合度。 在项目中,无论是controller层、service层还是dao层都可能会有异常发生。如果每个过程都单独处理异常,会导致系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。因此,将异常处理从各处理过程解耦出来,可以使相关处理过程的功能更加单一,也便于进行异常信息的统一处理和维护。

2. 统一用户登录验证

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步
骤:

  1. 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅
    法。
  2. 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。

2.1 自定义拦截器

package com.example.demo.config;


import com.example.demo.common.AppVar;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 自定义拦截器
 */
@Component
public class UserInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("do UserInterceptor");
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(AppVar.SESSION_KEY) != "") {
            //用户已经登录
             return true;//执行后面的流程
        }
        response.sendRedirect("https://www.baidu.com");
        return false;
    }
}

2.2 将自定义拦截器添加到系统配置

package com.example.demo.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Autowired
     private UserInterceptor userInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        registry.addInterceptor(new UserInterceptor());  //添加我们自定义的拦截器

        registry.addInterceptor(userInterceptor)
                .addPathPatterns("/**")  //拦截所有请求
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/user/login")
                .excludePathPatterns("img/**");//排除所有图片
    }
}

其中:
addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
excludePathPatterns:表示需要排除的 URL。
说明:以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)。

2.4 拦截器实现原理

正常调用顺序:
在这里插入图片描述
加入拦截器后,会在Controeller层之前加一个统一的登录处理:
在这里插入图片描述

2.5 拦截器实现源码分析

在我们执行Spring项目的时候,它首先会执行一个 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;
                    }

                    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);
            }

        }
    }

我们只用关注一小部分即可:
在这里插入图片描述
我们再进入applyPreHandle方法的源码:

 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }

        return true;
    }

在这里插入图片描述

从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中
的 preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了,如下图所示
在这里插入图片描述
此时用户的登录验证就完毕了,这就是拦截器的原理。

3. 统一异常处理

统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表
示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执⾏某个通知,
也就是执⾏某个⽅法事件,具体实现代码如下:

package com.example.demo.config;

import com.example.demo.common.ResultAjax;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

//@ControllerAdvice
@RestControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(NullPointerException.class)
    public ResultAjax doNullPointerException(NullPointerException e) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMessage("空指针异常:" + e.getMessage());
        resultAjax.setData(null);
        return resultAjax;
    }

}

在这里插入图片描述
其中,方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。
这里我们是自定义了一个类ResultAjax来接受返回值。
由于在上述统一异常处理中,我们只处理了空指针的异常处理,在实际中,我们一般会加一个保底的异常处理,即当我们定义的异常无法捕获该异常时,我们有所有异常的父类Exception来捕获,代码如下:

 @ExceptionHandler(Exception.class)
    public ResultAjax doException(Exception e) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMessage("异常:" + e.getMessage());
        resultAjax.setData(null);
        return resultAjax;
    }

4. 统一数据格式返回

ResultAjax定义如下:

package com.example.demo.common;

import lombok.Data;

@Data
public class ResultAjax {
    private int code; //状态码
    private String msg; //状态码的描述信息
    private Object data;  //返回数据
    /**
     * 返回成功对象
     * @param data
     * @return
     */
    public static ResultAjax success(Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg("");
        resultAjax.setData(data);
        return resultAjax;
    }

    public static ResultAjax success(String msg, Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }

    public static ResultAjax fail(int code,String msg){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(null);
        return resultAjax;
    }

    public static ResultAjax fail(int code,String msg,Object data){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }

}

4.1 为什么需要统一的数据返回?

  • 方便前端程序员更好的接收和解析后端返回的数据。
  • 降低前后端程序员沟通的成本,按照某个特定的格式返回就可以了。
    一般我们统一返回的数据格式如下:
  • 状态码:用来标识执行成功或失败的状态信息
  • 消息:用来描述请求的具体消息
  • 数据:包括请求的数据消息

4.2 统一数据格式返回的实现

一般我们通过@ControllerAdvice+ResponseBodyAdvice的方式实现,具体如下:
定义一个类加上@ControllerAdvice注解,并继承ResponseBodyAdvice接口:

package com.example.demo.config;

import com.example.demo.common.ResultAjax;
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;

/**
 * 对返回的数据类型做强制的数据转换
 */

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

 /**
     * true -> 才会调用 beforeBodyWrite 方法,
     * 反之则永远不会调用 beforeBodyWrite 方法
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @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) {
           if (body instanceof ResultAjax) {
               return body;
           }
           return ResultAjax.success(body);
    }
}

当我们继承了这个接口后,我们就需要重写它的上面这两个方法,其中supports返回true才会调用下面的beforeBodyWrite方法。值得注意的是我们需要对String类型的body做单独的处理,因为当我们走下面的步骤时会走两个解析引擎:
报错信息:在这里插入图片描述

在这里插入图片描述

它是在转换成JSON字符串的时候报错的,所以我们可以在转换前做一个判断,如果它是String类型的,我们先提前将它转换成JSON字符串即可。
在这里插入图片描述
在这里插入图片描述
以上就是全部内容了,感谢支持!

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值