【Spring】Spring统一功能处理

2023-12-16 14:45:35

拦截器

场景: 我们要对一个网站实现强制登陆的功能,后端根据Session来判断用户是否登录,但是如果我们要这样实现,就需要对每一个接口都增加这样的逻辑处理 此时就比较麻烦

? 需要修改每个接?的处理逻辑
? 需要修改每个接?的返回结果
? 接?定义修改, 前端代码也需要跟着修改

有没有更简单的办法, 统?拦截所有的请求, 并进?Session校验呢, 这?我们学习?种新的解决办法: 拦截器

拦截器

什么是拦截器

拦截器是Spring框架提供的核?功能之?, 主要?来拦截??的请求, 在指定?法前后, 根据业务需要执?预先设定的代码

也就是说, 允许开发?员提前预定义?些逻辑, 在??的请求响应前后执?. 也可以在??请求前阻?
其执?.
在拦截器当中,开发?员可以在应?程序中做?些通?性的操作, ?如通过拦截器来拦截前端发来的
请求, 判断Session中是否有登录??的信息. 如果有就可以放?, 如果没有就进?拦截.

拦截器的基本使用

定义拦截器

实现HandleInterceptor接口 重写方法

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       log.info("LoginInterceptor 目标方法执行前");
       return true;
   }

   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
       log.info("LoginInterceptor 目标方法执行后");
   }

   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
       log.info("LoginInterceptor 视图执行后");
   }
}

preHandle方法:目标方法执行前执行,返回true 继续执行后续操作,返回false 中断后续操作
postHandle方法:目标方法执行后执行
afterCompletion()?法:视图渲染完毕后执?,最后执?

注册配置拦截器

实现WebMvcConfigurer接口 重写addInterceptors方法

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //?定义的拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册?定义拦截器对象
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径
    }
}

拦截器详解

拦截器的拦截路径配置

拦截路径是指我们定义的这个拦截器, 对哪些请求?效.我们在注册配置拦截器的时候, 通过 addPathPatterns()?法指定要拦截哪些请求. 也可以通过excludePathPatterns() 指定不拦截哪些请求

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //自定义拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //设置拦截器的请求路径
        // /**表示拦截所有
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/**/*.js")
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.png")
                .excludePathPatterns("/**/*.html");
        //addPath设置拦截那些请求
        //excludePath设置不拦截哪些请求
    }
}

在拦截器中除了可以设置 /** 拦截所有资源外,还有?些常?拦截路径设置:

拦截路径含义举例
/*一级路径能匹配/user,/book,/login,不能匹配 /user/login
/**任意级路径能匹配/user,/user/login,/user/reg
/book/*/book下的?级路径能匹配/book/addBook,不能匹配/book/addBook/1,/book
/book/**/book下的任意级路径能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login

添加拦截器后, 执?Controller的?法之前, 请求会先被拦截器拦截住. 执? preHandle() ?法,
这个?法需要返回?个布尔类型的值.
如果返回true, 就表?放?本次操作, 继续访问controller中的?法.
如果返回false,则不会放?(controller中的?法也不会执?).

controller当中的?法执?完毕后,再回过来执? postHandle() 这个?法以及afterCompletion() ?法,执?完毕之后,最终给浏览器响应数据.

拦截器实现原理

当Tomcat启动之后, 有?个核?的类DispatcherServlet, 它来控制程序的执?顺序.

所有请求都会先进到DispatcherServlet,执?doDispatch 调度?法. 如果有拦截器,会先执?拦截器preHandle() ?法的代码, 如果 preHandle() 返回true, 继续访问controller中的?法.controller当中的?法执?完毕后,再回过来执? postHandle() 和 afterCompletion(),返回给DispatcherServlet, 最终给浏览器响应数据.
在这里插入图片描述

初始化

DispatcherServlet的初始化?法 init() 在其?类 HttpServletBean 中实现的.

主要作?是加载 web.xml 中 DispatcherServlet 的 配置, 并调??类的初始化.
在这里插入图片描述

在 HttpServletBean 的 init() 中调?了 initServletBean() , 它是在FrameworkServlet 类中实现的, 主要作?是建? WebApplicationContext 容器(有时也称上下?), 并加载 SpringMVC 配置?件中定义的 Bean到该容器中, 最后将该容器添加到 ServletContext 中. 下?是initServletBean() 的具体代码:

在这里插入图片描述
初始化web容器的过程中, 会通过onRefresh 来初始化SpringMVC的容器
在这里插入图片描述
在initStrategies()中进?9?组件的初始化, 如果没有配置相应的组件,就使?默认定义的组件(在
DispatcherServlet.properties中有配置默认的策略, ?致了解即可

在这里插入图片描述

?法initMultipartResolver、initLocaleResolver、initThemeResolver、initRequestToViewNameTranslator、initFlashMapManager的处理?式?乎都?样(1.2.3.7.8,9),从应??中取出指定的Bean, 如果没有, 就使?默认的.?法initHandlerMappings、initHandlerAdapters、initHandlerExceptionResolvers的处理?式?乎都?样(4,5,6)

  1. 初始化处理器映射器HandlerMappings:处理器映射器作?,1)通过处理器映射器找到对应的处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx?法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取到所有的HandlerMappings,并进?排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器
  2. 初始化处理器适配器HandlerAdapter:作?是通过调?具体的?法来处理具体的请求;如果在ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的 HandlerAdapter,并进?排序;如果在ApplicationContext中没有发现处理器适配器,则不设置异常处理器,则默认SimpleControllerHandlerAdapter作为处理器适配器
  3. 初始化异常处理器解析器HandlerExceptionResolver:如果在ApplicationContext发现有handlerExceptionResolver,则从ApplicationContext中获取到所有的HandlerExceptionResolver,并进?排序;如果在ApplicationContext中没有发现异常处理器解析器,则不设置异常处理器
处理请求
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                //1.获取执行链
                //遍历所有的HandlerMapper 找到与请求对应的Handler
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }


                //2. 获取适配器
                //遍历所有的HandlerAdapter 找到可以处理该Handler的HandlerAdapter
                HandlerAdapter ha = 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;
                    }
                }

                //3. 执行拦截器的preHandler方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                //4. 执行目标方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);

                //5. 执行拦截器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //6.处理视图 处理之后执?拦截器afterCompletion?法
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            //7.执?拦截器afterCompletion?法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

此处最关键的就是3 4 5 点 这里规定了执行目标方法前执行preHandle 执行目标方法之后执行postHandle方法

适配器模式

适配器模式是一种设计模式,用于将一个类的接口转换成客户端所期望的另一个接口。它允许原本不兼容的类能够合作无间。

适配器模式主要包括两个核心角色:目标接口(Target)和适配器(Adapter)。目标接口是客户端所期望的接口,适配器则是将原本不兼容的类转换成目标接口的中间层。

适配器模式可以通过两种方式实现:类适配器和对象适配器。在类适配器中,适配器继承了被适配类,并实现了目标接口。而在对象适配器中,适配器持有一个被适配对象的实例,并实现了目标接口。

使用适配器模式可以有以下几个好处:

  1. 可以让原本不兼容的类能够一起工作,提高代码的复用性。
  2. 可以封装已有的类,对外隐藏底层的实现细节。
  3. 可以在不修改现有代码的情况下引入新的功能。
    总之,适配器模式是一种常用的设计模式,可用于解决不同接口之间不兼容的问题,使得原本无法合作的类能够协同工作。

在这里插入图片描述
HandlerAdapter 在 Spring MVC 中使?了适配器模式

HandlerAdapter 主要?于?持不同类型的处理器(如 Controller、HttpRequestHandler 或者Servlet 等),让它们能够适配统?的请求处理流程。这样,Spring MVC 可以通过?个统?的接?来处理来?各种处理器的请求

场景: 前?学习的slf4j 就使?了适配器模式, slf4j提供了?系列打印?志的api, 底层调?的是log4j 或者logback来打?志, 我们作为调?者, 只需要调?slf4j的api就?了

//Slf4j接口
interface Slf4jApi{
    void log(String message);
}

//log4j接口
class Log4j{
    void log4jLog(String message){
        System.out.println("Log4j打印:" + message);
    }
}

//slf4j和log4j适配器

class Slf4jLog4JAdapter implements Slf4jApi{

    private Log4j log4j;

    public Slf4jLog4JAdapter(Log4j log4j) {
        this.log4j = log4j;
    }

    @Override
    public void log(String message) {
        log4j.log4jLog(message);
    }
}
public class Slf4jDemo {
    public static void main(String[] args) {
        Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
        slf4jApi.log("使用slf4j打印日志");
    }
}

在这里插入图片描述
适配器模式应?场景
?般来说,适配器模式可以看作?种"补偿模式",?来补救设计上的缺陷. 应?这种模式算是"?奈之举", 如果在设计初期,我们就能协调规避接?不兼容的问题, 就不需要使?适配器模式了

所以适配器模式更多的应?场景主要是对正在运?的代码进?改造, 并且希望可以复?原有代码实现新的功能. ?如版本升级等

统一数据返回格式

强制登录案例中, 我们共做了两部分?作

  1. 通过Session来判断??是否登录
  2. 对后端返回数据进?封装, 告知前端处理的结果

后端统一返回结果

package com.bite.book.model;

import com.bite.book.enums.ResultCode;
import lombok.Data;

@Data
public class Result<T> {
    /**
     * 业务状态码
     */
    private ResultCode code;  //0-成功  -1 失败  -2 未登录
    /**
     * 错误信息
     */
    private String errMsg;
    /**
     * 数据
     */
    private T data;

    public static <T> Result<T> success(T data){
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }
    public static <T> Result<T> fail(String errMsg){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(null);
        return result;
    }
    public static <T> Result<T> fail(String errMsg,Object data){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }
    public static <T> Result<T> unlogin(){
        Result result = new Result();
        result.setCode(ResultCode.UNLOGIN);
        result.setErrMsg("用户未登录");
        result.setData(null);
        return result;
    }

}

后端返回接口

@RequestMapping("/getBookListByPage")
    public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
        log.info("查询翻页信息, pageRequest:{}",pageRequest);
        //校验成功
        if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
            return Result.fail("参数校验失败");
        }
        PageResult<BookInfo> bookInfoPageResult = null;
        try {
            bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
            //此处对返回的数据进行再次封装
            return Result.success(bookInfoPageResult);
        }catch (Exception e){
            log.error("查询翻页信息错误,e:{}",e);
            return Result.fail(e.getMessage());
        }
    }

拦截器帮我们实现了第?个功能, 接下来看SpringBoot对第?个功能如何?持

统一数据返回格式快速入门

统一的数据返回格式使用@ControllerAdviceResponseBodyAdvice 的方式实现

@ControllerAdvice表示控制器通知类

添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接?, 并在类上添加 @ControllerAdvice 注解

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    //转json
    private ObjectMapper objectMapper;

    //判断是否要执行beforeBodyWrite方法
    //ture为执行
    //false不执行
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        
        return Result.success(body);
    }
}

supports方法: 判断是否要执行beforeBodyWrite方法, true为执行 false不执行, 通过该方法可以选择那些类或者哪些方法的response要进行处理 其他的不处理
beforeBodyWrite方法: 对response方法进行具体操作处理

统一异常处理

统?异常处理使?的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表?控制器通知类, @ExceptionHandler 是异常处理器,两个结合表?当出现异常的时候执?某个通知,也就是执?某个?法事件

package com.bite.book.config;

import com.bite.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@ResponseBody
@Slf4j
public class ErrorHandler {

    @ExceptionHandler
    public Object handler(Exception e){
        log.info("发生异常 e:{}",e.getMessage());
        return Result.fail(e.getMessage());
    }

    @ExceptionHandler
    public Object handler(NullPointerException e) {
        return Result.fail("发?NullPointerException:"+e.getMessage());
    }

    @ExceptionHandler
    public Object handler(ArithmeticException e) {
        return Result.fail("发?ArithmeticException:"+e.getMessage());
    }
}

当有多个异常通知时,匹配顺序为当前类及其?类向上依次匹配

文章来源:https://blog.csdn.net/m0_72670269/article/details/134899584
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。