记录一下SpringBoot的拦截器(Interceptor)使用
拦截器(Interceptor)是AOP面向切面编程的思想来实现的,对于只写代码的来说,具体如何实现不需要多关心,只需要关心如何去使用,会用在那些地方。
当http请求进入Springboot应用程序后,会去调用 Controller 层,在进入Controller处理应用业务之前,这个请求是需要通过经过拦截器(Interceptor)的,可能是一个也可能是多个,根据需求来,在Controller处理完业务逻辑之后,也要经过拦截器(Interceptor),最终将结果返回给请求着,即使不返回,也会经过的。
在拦截器(Interceptor)中,最常的应用有几个方面
1.校验token(最常用):根据请求token拦截校验是否允许访问特定资源 比如用户列表,开放的请求地址url需要额外的拦截器配置 比如登录
2.记录日志: 记录请求信息的日志数据
3.数据统计: 统计请求某一资源的次数,统计请求进入到响应的处理时间等
4.其他一些实际需求的功能
目前使用的是SpringBoot2.0.5版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
自定义 Interceptor一般是实现 org.springframework.web.servlet.HandlerInterceptor接口
源码是这样的
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
/**
* Workflow interface that allows for customized handler execution chains.
* Applications can register any number of existing or custom interceptors
* for certain groups of handlers, to add common preprocessing behavior
* without needing to modify each handler implementation.
*
* <p>A HandlerInterceptor gets called before the appropriate HandlerAdapter
* triggers the execution of the handler itself. This mechanism can be used
* for a large field of preprocessing aspects, e.g. for authorization checks,
* or common handler behavior like locale or theme changes. Its main purpose
* is to allow for factoring out repetitive handler code.
*
* <p>In an asynchronous processing scenario, the handler may be executed in a
* separate thread while the main thread exits without rendering or invoking the
* {@code postHandle} and {@code afterCompletion} callbacks. When concurrent
* handler execution completes, the request is dispatched back in order to
* proceed with rendering the model and all methods of this contract are invoked
* again. For further options and details see
* {@code org.springframework.web.servlet.AsyncHandlerInterceptor}
*
* <p>Typically an interceptor chain is defined per HandlerMapping bean,
* sharing its granularity. To be able to apply a certain interceptor chain
* to a group of handlers, one needs to map the desired handlers via one
* HandlerMapping bean. The interceptors themselves are defined as beans
* in the application context, referenced by the mapping bean definition
* via its "interceptors" property (in XML: a <list> of <ref>).
*
* <p>HandlerInterceptor is basically similar to a Servlet Filter, but in
* contrast to the latter it just allows custom pre-processing with the option
* of prohibiting the execution of the handler itself, and custom post-processing.
* Filters are more powerful, for example they allow for exchanging the request
* and response objects that are handed down the chain. Note that a filter
* gets configured in web.xml, a HandlerInterceptor in the application context.
*
* <p>As a basic guideline, fine-grained handler-related preprocessing tasks are
* candidates for HandlerInterceptor implementations, especially factored-out
* common handler code and authorization checks. On the other hand, a Filter
* is well-suited for request content and view content handling, like multipart
* forms and GZIP compression. This typically shows when one needs to map the
* filter to certain content types (e.g. images), or to all requests.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see HandlerExecutionChain#getInterceptors
* @see org.springframework.web.servlet.handler.HandlerInterceptorAdapter
* @see org.springframework.web.servlet.handler.AbstractHandlerMapping#setInterceptors
* @see org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor
* @see org.springframework.web.servlet.i18n.LocaleChangeInterceptor
* @see org.springframework.web.servlet.theme.ThemeChangeInterceptor
* @see javax.servlet.Filter
*/
public interface HandlerInterceptor {
/**
* Intercept the execution of a handler. Called after HandlerMapping determined
* an appropriate handler object, but before HandlerAdapter invokes the handler.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can decide to abort the execution chain,
* typically sending a HTTP error or writing a custom response.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation returns {@code true}.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
* @throws Exception in case of errors
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* Intercept the execution of a handler. Called after HandlerAdapter actually
* invoked the handler, but before the DispatcherServlet renders the view.
* Can expose additional model objects to the view via the given ModelAndView.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can post-process an execution,
* getting applied in inverse order of the execution chain.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param response current HTTP response
* @param handler handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param modelAndView the {@code ModelAndView} that the handler returned
* (can also be {@code null})
* @throws Exception in case of errors
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* Callback after completion of request processing, that is, after rendering
* the view. Will be called on any outcome of handler execution, thus allows
* for proper resource cleanup.
* <p>Note: Will only be called if this interceptor's {@code preHandle}
* method has successfully completed and returned {@code true}!
* <p>As with the {@code postHandle} method, the method will be invoked on each
* interceptor in the chain in reverse order, so the first interceptor will be
* the last to be invoked.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param response current HTTP response
* @param handler handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法
这个方法是在http请求进来之后处理之前被调用的 返回是 Boolean 类型,返回 false 时,表示直接掐断请求,直接结束了,那么也就不会进入Controller;返回 true 时,会继续进入下一个拦截器同样的方法(preHandler)直到所有的preHandler方法都通过才进入Controller。
postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 方法
http请求处理好业务后,从Controller控制器返回后调用,视图解析器之前,也就是在渲染数据返回数据之前被调用。
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法
当对应的拦截器preHandle返回值是 true 时才会在整个请求结束之后执行,视图解析器之后,也可以理解为postHandler后
自定义CustomHandlerInterceptor.java
package boot.example.interceptor.config;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomHandlerInterceptor implements HandlerInterceptor{
/**
* Controller逻辑执行之前进行拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle:");
String uri = request.getRequestURI();
System.out.println("拦截的uri:"+uri);
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
System.out.println("拦截 Controller:"+ handlerMethod.getBean().getClass().getName());
System.out.println("拦截方法:"+handlerMethod.getMethod().getName());
}
//拦截token,没有token的不允许继续往下执行
String token = request.getHeader("token");
if(token == null){
return false;
}
long startTime = System.currentTimeMillis();
System.out.println("start-time: " + startTime);
request.setAttribute("startTime", startTime);
return true;
}
/**
* Controller逻辑执行完毕但是视图解析器还为进行解析之前进行拦截
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle:");
long endTime = System.currentTimeMillis();
long startTime = (Long) request.getAttribute("startTime");
System.out.println("post-end-time: " + endTime);
System.out.println("post-差值ms:" + (endTime - startTime));
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* Controller逻辑和视图解析器执行完毕进行拦截
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion:");
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("after-end-time: " + endTime);
System.out.println("after-差值ms:" + (endTime - startTime));
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置拦截器
bean方式(不推荐)
package boot.example.interceptor.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 使用bean的方式
*
*/
@Configuration
public class InterceptorConfig {
@Bean
public CustomHandlerInterceptor customHandlerInterceptor(){
return new CustomHandlerInterceptor();
}
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customHandlerInterceptor())
.addPathPatterns("/**")
//.excludePathPatterns("/")
.excludePathPatterns("/index/**");
}
};
}
}
重写方式来配置(推荐)
package boot.example.interceptor.config;
import org.springframework.context.annotation.Bean;
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 InterceptorConfig implements WebMvcConfigurer {
@Bean
public CustomHandlerInterceptor customHandlerInterceptor(){
return new CustomHandlerInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customHandlerInterceptor())
.addPathPatterns("/**")
//.excludePathPatterns("/")
.excludePathPatterns("/index/**");
}
}
测试-BootIndexController.java
package boot.example.interceptor.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class BootIndexController {
@RequestMapping("/")
@ResponseBody
public String index() {
return "index";
}
@RequestMapping("/index/hello")
@ResponseBody
public String indexHello() {
return "hello world";
}
@RequestMapping("/inter/hello")
@ResponseBody
public String interHello(){
return "inter world";
}
@RequestMapping("/inter/sleep")
@ResponseBody
public String sleepHello() throws InterruptedException {
// 假装处理2s
Thread.sleep(2000);
return "inter world";
}
}
表示拦截所有的url请求
.addPathPatterns(“/**”)
表示放开拦截的url请求
.excludePathPatterns(“/”)
.excludePathPatterns(“/index/**”)
请求无拦截的情况,直接返回数据,不会经过拦截器
请求有拦截的情况,没有token是访问不到资源的
查看控制台
随意写个token
可以看到拦截通过了 执行了对应的三个方法 在这里有测试的故意延迟
一个应用可能有多个拦截器,有的时候拦截器需要执行的先后顺序,默认的情况下是按照注册的先后顺序
拦截器ONE
CustomOrderOneHandlerInterceptor.java
package boot.example.interceptor.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomOrderOneHandlerInterceptor implements HandlerInterceptor{
/**
* Controller逻辑执行之前进行拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("CustomOrderOneHandlerInterceptor: preHandle");
return true;
}
/**
* Controller逻辑执行完毕但是视图解析器还为进行解析之前进行拦截
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("CustomOrderOneHandlerInterceptor: postHandle");
}
/**
* Controller逻辑和视图解析器执行完毕进行拦截
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("CustomOrderOneHandlerInterceptor: afterCompletion");
}
}
拦截器TWO
CustomOrderTwoHandlerInterceptor.java
package boot.example.interceptor.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomOrderTwoHandlerInterceptor implements HandlerInterceptor{
/**
* Controller逻辑执行之前进行拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("CustomOrderTwoHandlerInterceptor: preHandle");
return true;
}
/**
* Controller逻辑执行完毕但是视图解析器还为进行解析之前进行拦截
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("CustomOrderTwoHandlerInterceptor: postHandle");
}
/**
* Controller逻辑和视图解析器执行完毕进行拦截
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("CustomOrderTwoHandlerInterceptor: afterCompletion");
}
}
拦截器配置
OderInterceptorConfig.java
package boot.example.interceptor.config;
import org.springframework.context.annotation.Bean;
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 OderInterceptorConfig implements WebMvcConfigurer {
@Bean
public CustomOrderOneHandlerInterceptor customOrderOneHandlerInterceptor(){
return new CustomOrderOneHandlerInterceptor();
}
@Bean
public CustomOrderTwoHandlerInterceptor customOrderTwoHandlerInterceptor(){
return new CustomOrderTwoHandlerInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customOrderOneHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index/**");
registry.addInterceptor(customOrderTwoHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index/**");
}
}
可以看到customOrderOneHandlerInterceptor()在customOrderTwoHandlerInterceptor()之前,启动程序打开访问打开控制台查看打印日志确认
交换顺序可以看到TWO在ONE之前执行
使用order自定义顺序 可以看到值小的比值大的先执行
使用拦截器重定向
当访问
http://localhost:8080
重定向
http://localhost:8080/home
CustomRedirectOneHandlerInterceptor.java
package boot.example.interceptor.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomRedirectOneHandlerInterceptor implements HandlerInterceptor{
/**
* Controller逻辑执行之前进行拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("CustomRedirectOneHandlerInterceptor: preHandle");
System.out.println("CustomRedirectOneHandlerInterceptor Request url: " + request.getRequestURL());
response.sendRedirect(request.getContextPath()+ "/home");
return false;
}
/**
* Controller逻辑执行完毕但是视图解析器还为进行解析之前进行拦截
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("CustomRedirectOneHandlerInterceptor: postHandle");
}
/**
* Controller逻辑和视图解析器执行完毕进行拦截
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("CustomRedirectOneHandlerInterceptor: afterCompletion");
}
}
CustomRedirectTwoHandlerInterceptor.java
package boot.example.interceptor.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomRedirectTwoHandlerInterceptor implements HandlerInterceptor{
/**
* Controller逻辑执行之前进行拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("CustomRedirectTwoHandlerInterceptor: preHandle");
System.out.println("CustomRedirectTwoHandlerInterceptor Request url: " + request.getRequestURL());
return true;
}
/**
* Controller逻辑执行完毕但是视图解析器还为进行解析之前进行拦截
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("CustomRedirectTwoHandlerInterceptor: postHandle");
}
/**
* Controller逻辑和视图解析器执行完毕进行拦截
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("CustomRedirectTwoHandlerInterceptor: afterCompletion");
}
}
BootIndexController.java
package boot.example.interceptor.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class BootIndexController {
@RequestMapping("/")
@ResponseBody
public String index() {
return "/";
}
@RequestMapping("/home")
@ResponseBody
public String indexHello() {
return "/home";
}
}
用postman访问
浏览器输入http://localhost:8080会跳转的