红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. SpringMVC 学习笔记
  3. 正文

SpringMVC 学习笔记4:拦截器

2023年8月31日 1010点热度 0人点赞 0条评论

1.简介

拦截器是 SpringMVC 的技术,用于拦截控制层处理的 HTTP 请求,起作用有两种:

  • 在请求前和请求后执行特定代码,对请求进行功能增强。

  • 阻止请求的执行(用于鉴权等)。

1630676280170

过滤器(Filter)是 Tomcat 的技术,拦截器是 SpringMVC 的技术:

1630676903190

关于拦截器的详细说明可以观看这个视频。

2.快速入门

这里使用的示例项目是 mvc-demo,对应的测试数据是mybatis.sql。

2.1.定义拦截器

创建拦截器需要实现HandlerInterceptor接口:

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
​
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }
​
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

这个接口有三个方法:

  • preHandle,在控制层方法调用前执行。

  • postHandle,在控制层方法调用后执行。

  • afterCompletion,在 HTTP 请求结束后执行。

这三个方法的调用过程可以表示为:

1630679464294

这三个方法都有默认实现,这里实现这个方法,并进行一些简单打印:

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
​
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

拦截器是用于控制层的,可以放在控制层的包下(比如xxx.controller.interceptor),这样也方便 SpringMVC 配置类的组件扫描。

为了能让 Spring 管理这个拦截器,用@Component标记。

2.2.注册拦截器

需要在 Spring 配置中注册这个拦截器:

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private MyInterceptor myInterceptor;
    // ...
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
        registry.addInterceptor(myInterceptor).addPathPatterns("/books","/books/*");
    }
}

addPathPatterns方法说明了拦截器作用的请求路径。在上面这个示例中,对于所有的/books或/books/*请求都有效,比如GET http://localhost/books/2这个请求,控制台输出:

preHandle...
getBookInfo...
postHandle...
afterCompletion...

getBookInfo...是在接口调用中添加的打印信息。

2.3.阻止请求

如果拦截器的preHandle方法返回false,HTTP 请求将被阻止,控制层方法就不会被调用:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("preHandle...");
    return false;
}

控制台输出:

preHandle...

HTTP 响应报文:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Wed, 30 Aug 2023 11:37:21 GMT

返回的 HTTP 报文体为空。

2.4.WebMvcConfigurer

最后,SpringMVC 的相关注册方法除了用WebMvcConfigurationSupport实现外,还可以选择让配置类实现WebMvcConfigurer接口:

@Configuration
@ComponentScan({"cn.icexmoon.mvcdemo.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private MyInterceptor myInterceptor;
​
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        WebMvcConfigurer.super.addResourceHandlers(registry);
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(myInterceptor).addPathPatterns("/books", "/books/*");
    }
}

现在SpringMvcConfig只需要扫描cn.icexmoon.mvcdemo.controller包即可。

3.拦截器参数

3.1.HttpServletRequest

利用这个参数,我们可以访问 HTTP 请求报文中的内容,比如打印请求报文头信息:

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        HttpUtil.printHeaders(request);
        return true;
    }
    // ...
}

这里使用了一个工具函数,用于打印头信息:

public class HttpUtil {
    /**
     * 打印 HTTP 请求报文头
     * @param request
     */
    public static void printHeaders(HttpServletRequest request){
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String name = headerNames.nextElement();
            Enumeration<String> headerValues = request.getHeaders(name);
            List<String> headerValuesList = new ArrayList<>();
            while (headerValues.hasMoreElements()){
                headerValuesList.add(headerValues.nextElement());
            }
            String values = headerValuesList.stream().collect(Collectors.joining(","));
            System.out.println(String.format("%s:%s", name, values));
        }
    }
}

打印结果:

user-agent:PostmanRuntime-ApipostRuntime/1.1.0
cache-control:no-cache
content-type:application/json
accept:*/*
accept-encoding:gzip, deflate, br
connection:keep-alive
host:localhost

3.2.HttpServletResponse

类似的,可以在拦截器中利用HttpServletResponse类型的参数获取响应报文信息,比如打印响应报文头:

@Component
public class MyInterceptor implements HandlerInterceptor {
    // ...
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
        HttpUtil.printResponseHeaders(response);
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

工具类HttpUtil的相关方法这里不再赘述,可以参考文末的完整示例。

3.3.HandlerMethod

控制层中的handler参数实际上是控制层方法的句柄,其真实类型是HandlerMethod,包含一个反射类型Method的属性,因此我们可以用它来获取拦截到的控制层方法信息:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("preHandle...");
    HttpUtil.printRequestHeaders(request);
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    HttpUtil.printHandlerMethod(handlerMethod);
    return true;
}

3.4.Exception

afterCompletion方法中有个Exception类型的参数,它代表控制层方法调用产生的异常。像前篇文章说的那样,一般我们会使用异常拦截器来处理异常,所以这里的异常对象一般不会使用。

5.拦截器链

对同一个请求,可以配置多个拦截器进行处理。

比如这里定义三个拦截器:

@Component
public class MyInterceptor1 implements HandlerInterceptor {
    // ...
}

@Component
public class MyInterceptor2 implements HandlerInterceptor {
    // ...
}

@Component
public class MyInterceptor3 implements HandlerInterceptor {
    // ...
}

对三个拦截器进行注册:

public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private MyInterceptor1 myInterceptor1;
    @Autowired
    private MyInterceptor2 myInterceptor2;
    @Autowired
    private MyInterceptor3 myInterceptor3;
	// ...
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(myInterceptor1).addPathPatterns("/books", "/books/*");
        registry.addInterceptor(myInterceptor2).addPathPatterns("/books", "/books/*");
        registry.addInterceptor(myInterceptor3).addPathPatterns("/books", "/books/*");
    }
}

执行请求后控制台输出:

preHandle1...
preHandle2...
preHandle3...
getBookInfo...
postHandle3...
postHandle2...
postHandle1...
afterCompletion3...
afterCompletion2...
afterCompletion1...

preHandle的调用顺序与拦截器的注册顺序是一致的,postHandle和afterCompletion的调用顺序与拦截器的注册顺序是相反的。

特殊的是,如果有拦截器的preHandle方法返回false,其后面的拦截器的preHandle方法不会被执行,所有拦截器的postHandle方法都不会被执行。其之前的afterCompletion方法会执行。

这里举例说明。

如果让MyInterceptor3的preHandle方法返回false:

preHandle1...
preHandle2...
preHandle3...
afterCompletion2...
afterCompletion1...

如果让MyInterceptor2的preHandle方法返回false:

preHandle1...
preHandle2...
afterCompletion1...

如果让MyInterceptor1的preHandle方法返回false:

preHandle1...

这个过程可以用下图表示:

1630680579735The End,谢谢阅读。

本文的完整示例可以从这里获取。

6.参考资料

  • 黑马程序员SSM框架教程

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: mvc spring 拦截器
最后更新:2023年8月31日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号