一、SpringMVC定义interceptor方式

在SpringMVC 中定义一个Interceptor是比较非常简单,主要有两种方式:
第一种:实现HandlerInterceptor 接口,或者是继承实现了HandlerInterceptor 接口的类,例如HandlerInterceptorAdapter; 
第二种:实现Spring的WebRequestInterceptor接口,或者是继承实现了WebRequestInterceptor的类。

1.1、HandlerInterceptorAdapter

1.1.1、 HandlerInterceptor接口

SpringMVC的拦截器HandlerInterceptor对应提供了三个preHandle,postHandle,afterCompletion方法:

  1.  boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handle)方法:该方法将在请求处理之前进行调用,只有该方法返回true,才会继续执行后续的Interceptor和Controller,当返回值为true 时就会继续调用下一个Interceptor的preHandle 方法,如果已经是最后一个Interceptor的时候就会是调用当前请求的Controller方法;
  2. void postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)方法:该方法将在请求处理之后,DispatcherServlet进行视图返回渲染之前进行调用,可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。
  3. void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)方法:该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。用于进行资源清理。

1.1.2、HandlerInterceptorAdapter抽象类

  HandlerInterceptorAdapter它实现了AsyncHandlerInterceptor接口,为每个方法提供了空实现。这样下来HandlerInterceptorAdapter比HandlerInterceptor多了一个实现方法afterConcurrentHandlingStarted(),它来自HandlerInterceptorAdapter的直接实现类AsyncHandlerInterceptor,AsyncHandlerInterceptor接口直接继承了HandlerInterceptor,并新添了afterConcurrentHandlingStarted()方法用于处理异步请求。

afterConcurrentHandlingStarted()执行时机:???

1.2、WebRequestInterceptor

1.2.1、 WebRequestInterceptor接口

WebRequestInterceptor接口同HandlerInterceptor接口一样定义了三个方法,preHandle 、postHandle 以及afterCompletion。两个接口的方法名都相同,调用次序也相同。即preHandle是在请求处理之前调用;postHandle实在请求处理之后,视图渲染之前调用;afterCompletion是在视图渲染之后调用。接下来我们看看他们的不同之处。

1.方法参数不同。WebRequest是Spring定义的接口,它是对HttpServletRequest的封装。对WebRequest 进行的操作都将同步到HttpServletRequest 中。WebRequest 的set/getAttribute(name, value, scope)比HttpServletRequest 的set/getAttribute多了一个scope参数。它有三个取值:

  • SCOPE_REQUEST:它的值是0,表示request请求作用范围。
  • SCOPE_SESSION :它的值是1,表示session请求作用范围。
  • SCOPE_GLOBAL_SESSION :它的值是2 ,表示全局会话作用范围,即ServletContext上下文作用范围。

2.preHandle 方法。WebRequestInterceptor的该方法返回值为void,不是boolean。所以该方法不能用于请求阻断,一般用于资源准备。

3.postHandle 方法。preHandle 中准备的数据都可以通过参数WebRequest访问。ModelMap 是Controller 处理之后返回的Model 对象,可以通过改变它的属性来改变Model 对象模型,达到改变视图渲染效果的目的。

4.afterCompletion方法。Exception 参数表示的是当前请求的异常对象,如果Controller 抛出的异常已经被处理过,则Exception对象为null 。

1.2.1、 WebRequestInterceptorAdapter抽象类

在 Spring 框架之中,还提供了一个和WebRequestInterceptor接口长的很像的抽象类,那就是:WebRequestInterceptorAdapter,其实现了AsyncHandlerInterceptor接口,并在内部调用了WebRequestInterceptor接口。
afterConcurrentHandlingStarted()执行时机:???

1.3、HandlerInterceptorAdapter和WebRequestInterceptor相同点:

两个接口都可用于Contrller层请求拦截,接口中定义的方法作用也是一样的。

1.4、HandlerInterceptorAdapter和WebRequestInterceptor不同点:

  1. WebRequestInterceptor的入参WebRequest是包装了HttpServletRequest 和HttpServletResponse的,通过WebRequest获取Request中的信息更简便。
  2. WebRequestInterceptor的preHandle是没有返回值的,说明该方法中的逻辑并不影响后续的方法执行,所以这个接口实现就是为了获取Request中的信息,或者预设一些参数供后续流程使用。
  3. HandlerInterceptor的功能更强大也更基础,可以在preHandle方法中就直接拒绝请求进入controller方法。

二、自定义拦截器配置方法

  1. 在sping的xml配置中可以用<mvc:interceptors>和<mvc:interceptor>来配置拦截器类(实现HandlerInterceptorAdapter)
  2. 在javaConfig中配置通过WebMvcConfiguration的实现类配置拦截器类(实现HandlerInterceptorAdapter)

2.1、javaconfig中配置SpringMVC示例

1、新建一个springboot项目auth-demo2

2、权限校验相关的注解

package com.dxz.authdemo2.web.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
/** 检查项枚举 */
PermissionEnum[] permissionTypes() default {}; /** 检查项关系 */
RelationEnum relation() default RelationEnum.OR;
} package com.dxz.authdemo2.web.auth; import java.io.PrintWriter;
import java.lang.annotation.Annotation; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; /**
* 权限检查拦截器
*/
@Component
public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {
/** 权限检查服务 */
@Autowired
private PermissionCheckProcessor permissionCheckProcessor;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//Class<?> clazz = handler.getClass();
Class<?> clazz = ((HandlerMethod)handler).getBeanType();
System.out.println("PermissionCheckInterceptor.preHandle()" + clazz);
for(Annotation a : clazz.getAnnotations()){
System.out.println(a);
}
if (clazz.isAnnotationPresent(Permission.class)) {
Permission permission = (Permission) clazz.getAnnotation(Permission.class);
return permissionCheckProcessor.process(permission, request, response);
}
return true;
} public boolean preHandle2(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SecurityInterceptor:"+request.getContextPath()+","+request.getRequestURI()+","+request.getMethod());
HttpSession session = request.getSession();
if (session.getAttribute("uid") == null) {
System.out.println("AuthorizationException:未登录!"+request.getMethod());
if("POST".equalsIgnoreCase(request.getMethod())){
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
out.write("未登录!");
out.flush();
out.close();
}else{
response.sendRedirect(request.getContextPath()+"/login");
}
return false;
} else {
return true;
}
}
} package com.dxz.authdemo2.web.auth; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component;
@Component
public class PermissionCheckProcessor {
public boolean process(Permission permission, HttpServletRequest request, HttpServletResponse response) {
PermissionEnum[] permissionTypes = permission.permissionTypes();
try {
String uid = request.getParameter("uid");
if ("duanxz".equals(uid)) {
System.out.println("认证成功");
return true;
} else {
System.out.println("认证失败");
return false;
}
} catch (Exception e) {
return false;
}
}
} package com.dxz.authdemo2.web.auth; public enum PermissionEnum {
DEVELOPER_VALID, DEVELOPER_FREEZE;
} package com.dxz.authdemo2.web.auth; public enum RelationEnum {
OR, AND;
}

3、SpringMVC拦截器配置

package com.dxz.authdemo2.web.auth;

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.WebMvcConfigurerAdapter; @Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter { @Autowired
PermissionCheckInterceptor permissionCheckInterceptor; @Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
// 映射为 user 的控制器下的所有映射
registry.addInterceptor(permissionCheckInterceptor).addPathPatterns("/admin/*").excludePathPatterns("/index", "/");
super.addInterceptors(registry);
}
}

4、测试controller

package com.dxz.authdemo2.web;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView; import com.dxz.authdemo2.web.auth.Permission;
import com.dxz.authdemo2.web.auth.PermissionEnum; @Controller
@RequestMapping("/admin")
@Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID })
public class AppDetailController {
@RequestMapping(value="/appDetail", method = RequestMethod.GET)
public String doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {
//1. 业务操作,此处省略
System.out.println("appDetail.htm 处理中...");
return "appDetail";
}
} package com.dxz.authdemo2.web; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import com.dxz.authdemo2.web.auth.Permission;
import com.dxz.authdemo2.web.auth.PermissionEnum; @Controller
@RequestMapping("index")
public class IndexController {
@RequestMapping(method = RequestMethod.GET)
public void doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {
System.out.println("index");
}
}

cotroller中的jsp文件appDetail.jsp

<html>
<h1>appDetail</h1>
</html>

启动类:

package com.dxz.authdemo2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver; @EnableWebMvc
@EnableAutoConfiguration
@SpringBootApplication
public class AuthDemo2Application { public static void main(String[] args) {
SpringApplication.run(AuthDemo2Application.class, args);
} // 配置JSP视图解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}

结果:

访问:http://localhost:8080/admin/appDetail?uid=duanxz2

访问:http://localhost:8080/admin/appDetail?uid=duanxz

2.2、xml中配置SpringMVC示例

首先在springmvc.xml中加入自己定义的拦截器我的实现逻辑PermissionCheckInterceptor,如下:

三、体验Spring MVC的异步模式(Callable、WebAsyncTask、DeferredResult)

Spring MVC的同步模式

要知道什么是异步模式,就先要知道什么是同步模式。

浏览器发起请求,Web服务器开一个线程处理(请求处理线程),处理完把处理结果返回浏览器。这就是同步模式。绝大多数Web服务器都如此般处理。这里面有几个关键的点:简单示例图如下

此处需要明晰一个概念:比如tomcat,它既是一个web服务器,同时它也是个servlet后端容器(调java后端服务),所以要区分清楚这两个概念。请求处理线程是有限的,宝贵的资源~(注意它和处理线程的区别)

  1. 请求发起者发起一个request,然后会一直等待一个response,这期间它是阻塞的
  2. 请求处理线程会在Call了之后等待Return,自身处于阻塞状态(这个很关键)
  3. 然后都等待return,知道处理线程全部完事后返回了,然后把response反给调用者就算全部结束了
问题在哪里?

Tomcat等应用服务器的连接线程池实际上是有限制的;每一个连接请求都会耗掉线程池的一个连接数;如果某些耗时很长的操作,如对大量数据的查询操作、调用外部系统提供的服务以及一些IO密集型操作等,会占用连接很长时间,这个时候这个连接就无法被释放而被其它请求重用。如果连接占用过多,服务器就很可能无法及时响应每个请求;极端情况下如果将线程池中的所有连接耗尽,服务器将长时间无法向外提供服务!

Spring MVC异步模式Demo Show

Spring MVC3.2之后支持异步请求,能够在controller中返回一个Callable或者DeferredResult。由于Spring MVC的良好封装,异步功能使用起来出奇的简单。

Callable案例:
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; @Controller
@RequestMapping("/async/controller")
public class AsyncHelloController { @ResponseBody
@GetMapping("/hello")
public Callable<String> helloGet() throws Exception {
System.out.println(Thread.currentThread().getName() + " 主线程start"); Callable<String> callable = () -> {
System.out.println(Thread.currentThread().getName() + " 子线程start");
TimeUnit.SECONDS.sleep(5); // 模拟处理业务逻辑,花费了5秒钟
System.out.println(Thread.currentThread().getName() + " 子线程end"); // 这里稍微小细节一下:最终返回的不是Callable对象,而是它里面的内容
return "hello world";
}; System.out.println(Thread.currentThread().getName() + " 主线程end");
return callable;
}
}

输出:

http-apr-8080-exec-3 主线程start
http-apr-8080-exec-3 主线程end
MvcAsync1 子线程start
MvcAsync1 子线程end

先明细两个概念:

  1. 请求处理线程:处理线程 属于 web 服务器线程,负责 处理用户请求,采用 线程池 管理。
  2. 异步线程:异步线程 属于 用户自定义的线程,也可采用 线程池管理。

前端页面等待5秒出现结果,如下:

注意:异步模式对前端来说,是无感知的,这是后端的一种技术。所以这个和我们自己开启一个线程处理,立马返回给前端是有非常大的不同的,需要注意~

由此我们可以看出,主线程早早就结束了(需要注意,此时还并没有把response返回的,此处一定要注意),真正干事的是子线程(交给TaskExecutor去处理的,后续分析过程中可以看到),它的大致的一个处理流程图可以如下:

这里能够很直接的看出:我们很大程度上提高了我们请求处理线程的利用率,从而肯定就提高了我们系统的吞吐量。

异步模式处理步骤概述如下:
  1. 当Controller返回值是Callable的时候
  2. Spring就会将Callable交给TaskExecutor去处理(一个隔离的线程池)
  3. 与此同时将DispatcherServlet里的拦截器、Filter等等都马上退出主线程,但是response仍然保持打开的状态
  4. Callable线程处理完成后,Spring MVC将请求重新派发给容器**(注意这里的重新派发,和后面讲的拦截器密切相关)**
  5. 根据Callabel返回结果,继续处理(比如参数绑定、视图解析等等就和之前一样了)~~~

Spring官方解释如下截图:

WebAsyncTask案例:

官方有这么一句话,截图给你:

如果我们需要超时处理的回调或者错误处理的回调,我们可以使用WebAsyncTask代替Callable

实际使用中,我并不建议直接使用Callable ,而是使用Spring提供的WebAsyncTask 代替,它包装了Callable,功能更强大些

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.WebAsyncTask; @Controller
@RequestMapping("/async/controller")
public class AsyncHelloController { @ResponseBody
@GetMapping("/hello")
public WebAsyncTask<String> helloGet() throws Exception {
System.out.println(Thread.currentThread().getName() + " 主线程start"); Callable<String> callable = () -> {
System.out.println(Thread.currentThread().getName() + " 子线程start");
TimeUnit.SECONDS.sleep(5); // 模拟处理业务逻辑,话费了5秒钟
System.out.println(Thread.currentThread().getName() + " 子线程end"); return "hello world";
}; // 采用WebAsyncTask 返回 这样可以处理超时和错误 同时也可以指定使用的Excutor名称
WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, callable);
// 注意:onCompletion表示完成,不管你是否超时、是否抛出异常,这个函数都会执行的
webAsyncTask.onCompletion(() -> System.out.println("程序[正常执行]完成的回调")); // 这两个返回的内容,最终都会放进response里面去===========
webAsyncTask.onTimeout(() -> "程序[超时]的回调");
// 备注:这个是Spring5新增的
// webAsyncTask.onError(() -> "程序[出现异常]的回调"); System.out.println(Thread.currentThread().getName() + " 主线程end");
return webAsyncTask;
}
}

如上,由于我们设置了超时时间为3000ms,而业务处理是5s,所以会执行onTimeout这个回调函数。因此页面是会显示“程序[超时]的回调”这几个字。其执行的过程同Callback。

下面我们简单看看WebAsyncTask的源码,非常简单,就是个包装:

public class WebAsyncTask<V> implements BeanFactoryAware {

    // 正常执行的函数(通过WebAsyncTask的构造函数可以传进来)
private final Callable<V> callable;
// 处理超时时间(ms),可通过构造函数指定,也可以不指定(不会有超时处理)
private Long timeout;
// 执行任务的执行器。可以构造函数设置进来,手动指定。
private AsyncTaskExecutor executor;
// 若设置了,会根据此名称去IoC容器里找这个Bean (和上面二选一)
// 若传了executorName,请务必调用set方法设置beanFactory
private String executorName;
private BeanFactory beanFactory; // 超时的回调
private Callable<V> timeoutCallback;
// 发生错误的回调
private Callable<V> errorCallback;
// 完成的回调(不管超时还是错误都会执行)
private Runnable completionCallback; ... // 这是获取执行器的逻辑
@Nullable
public AsyncTaskExecutor getExecutor() {
if (this.executor != null) {
return this.executor;
} else if (this.executorName != null) {
Assert.state(this.beanFactory != null, "BeanFactory is required to look up an executor bean by name");
return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class);
} else {
return null;
}
} public void onTimeout(Callable<V> callback) {
this.timeoutCallback = callback;
}
public void onError(Callable<V> callback) {
this.errorCallback = callback;
}
public void onCompletion(Runnable callback) {
this.completionCallback = callback;
} // 最终执行超时回调、错误回调、完成回调都是通过这个拦截器实现的
CallableProcessingInterceptor getInterceptor() {
return new CallableProcessingInterceptor() {
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
return (timeoutCallback != null ? timeoutCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
}
@Override
public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
return (errorCallback != null ? errorCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
}
@Override
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
if (completionCallback != null) {
completionCallback.run();
}
}
};
} }

WebAsyncTask 的异步编程 API。相比于 @Async 注解,WebAsyncTask 提供更加健全的 超时处理 和 异常处理 支持。但是@Async也有更优秀的地方,就是他不仅仅能用于controller中~~~~(任意地方)

DeferredResult案例:

DeferredResult使用方式与Callable类似,但在返回结果上不一样,它返回的时候实际结果可能没有生成,实际的结果可能会在另外的线程里面设置到DeferredResult中去。

这个特性非常非常的重要,对后面实现复杂的功能(比如服务端推技术、订单过期时间处理、长轮询、模拟MQ的功能等等高级应用

官方给的Demo如下:

自己写个非常粗糙的Demo:

import java.util.ArrayList;
import java.util.List; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult; @Controller
@RequestMapping("/async/controller")
public class AsyncHelloController { private List<DeferredResult<String>> deferredResultList = new ArrayList<>(); @ResponseBody
@GetMapping("/hello")
public DeferredResult<String> helloGet() throws Exception {
DeferredResult<String> deferredResult = new DeferredResult<>(); //先存起来,等待触发
deferredResultList.add(deferredResult);
return deferredResult;
} @ResponseBody
@GetMapping("/setHelloToAll")
public void helloSet() throws Exception {
// 让所有hold住的请求给与响应
deferredResultList.forEach(d -> d.setResult("say hello to all"));
}
}

我们第一个请求/hello,会先deferredResult存起来,然后前端页面是一直等待(转圈状态)的。知道我发第二个请求:setHelloToAll,所有的相关页面才会有响应~~

执行过程

官方:

  1. controller 返回一个DeferredResult,我们把它保存到内存里或者List里面(供后续访问)
  2. Spring MVC调用request.startAsync(),开启异步处理
  3. 与此同时将DispatcherServlet里的拦截器、Filter等等都马上退出主线程,但是response仍然保持打开的状态
  4. 应用通过另外一个线程(可能是MQ消息、定时任务等)给DeferredResult set值。然后Spring MVC会把这个请求再次派发给servlet容器
  5. DispatcherServlet再次被调用,然后处理后续的标准流程

简单看看源码:

public class DeferredResult<T> {

    private static final Object RESULT_NONE = new Object()

    // 超时时间(ms) 可以不配置
@Nullable
private final Long timeout;
// 相当于超时的话的,传给回调函数的值
private final Object timeoutResult; // 这三种回调也都是支持的
private Runnable timeoutCallback;
private Consumer<Throwable> errorCallback;
private Runnable completionCallback; // 这个比较强大,就是能把我们结果再交给这个自定义的函数处理了 他是个@FunctionalInterface
private DeferredResultHandler resultHandler; private volatile Object result = RESULT_NONE;
private volatile boolean expired = false; // 判断这个DeferredResult是否已经被set过了(被set过的对象,就可以移除了嘛)
// 如果expired表示已经过期了你还没set,也是返回false的
// Spring4.0之后提供的
public final boolean isSetOrExpired() {
return (this.result != RESULT_NONE || this.expired);
} // 没有isSetOrExpired 强大,建议使用上面那个
public boolean hasResult() {
return (this.result != RESULT_NONE);
} // 还可以获得set进去的结果
@Nullable
public Object getResult() {
Object resultToCheck = this.result;
return (resultToCheck != RESULT_NONE ? resultToCheck : null);
} public void onTimeout(Runnable callback) {
this.timeoutCallback = callback;
}
public void onError(Consumer<Throwable> callback) {
this.errorCallback = callback;
}
public void onCompletion(Runnable callback) {
this.completionCallback = callback;
} // 如果你的result还需要处理,可以这是一个resultHandler,会对你设置进去的结果进行处理
public final void setResultHandler(DeferredResultHandler resultHandler) {
Assert.notNull(resultHandler, "DeferredResultHandler is required");
// Immediate expiration check outside of the result lock
if (this.expired) {
return;
}
Object resultToHandle;
synchronized (this) {
// Got the lock in the meantime: double-check expiration status
if (this.expired) {
return;
}
resultToHandle = this.result;
if (resultToHandle == RESULT_NONE) {
// No result yet: store handler for processing once it comes in
this.resultHandler = resultHandler;
return;
}
}
try {
resultHandler.handleResult(resultToHandle);
} catch (Throwable ex) {
logger.debug("Failed to handle existing result", ex);
}
} // 我们发现,这里调用是private方法setResultInternal,我们设置进来的结果result,会经过它的处理
// 而它的处理逻辑也很简单,如果我们提供了resultHandler,它会把这个值进一步的交给我们的resultHandler处理
// 若我们没有提供此resultHandler,那就保存下这个result即可
public boolean setResult(T result) {
return setResultInternal(result);
} private boolean setResultInternal(Object result) {
// Immediate expiration check outside of the result lock
if (isSetOrExpired()) {
return false;
}
DeferredResultHandler resultHandlerToUse;
synchronized (this) {
// Got the lock in the meantime: double-check expiration status
if (isSetOrExpired()) {
return false;
}
// At this point, we got a new result to process
this.result = result;
resultHandlerToUse = this.resultHandler;
if (resultHandlerToUse == null) {
this.resultHandler = null;
}
}
resultHandlerToUse.handleResult(result);
return true;
} // 发生错误了,也可以设置一个值。这个result会被记下来,当作result
// 注意这个和setResult的唯一区别,这里入参是Object类型,而setResult只能set规定的指定类型
// 定义成Obj是有原因的:因为我们一般会把Exception等异常对象放进来。。。
public boolean setErrorResult(Object result) {
return setResultInternal(result);
} // 拦截器 注意最终finally里面,都可能会调用我们的自己的处理器resultHandler(若存在的话)
// afterCompletion不会调用resultHandler~~~~~~~~~~~~~
final DeferredResultProcessingInterceptor getInterceptor() {
return new DeferredResultProcessingInterceptor() {
@Override
public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
boolean continueProcessing = true;
try {
if (timeoutCallback != null) {
timeoutCallback.run();
}
} finally {
if (timeoutResult != RESULT_NONE) {
continueProcessing = false;
try {
setResultInternal(timeoutResult);
} catch (Throwable ex) {
logger.debug("Failed to handle timeout result", ex);
}
}
}
return continueProcessing;
}
@Override
public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> deferredResult, Throwable t) {
try {
if (errorCallback != null) {
errorCallback.accept(t);
}
} finally {
try {
setResultInternal(t);
} catch (Throwable ex) {
logger.debug("Failed to handle error result", ex);
}
}
return false;
}
@Override
public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
expired = true;
if (completionCallback != null) {
completionCallback.run();
}
}
};
} // 内部函数式接口 DeferredResultHandler
@FunctionalInterface
public interface DeferredResultHandler {
void handleResult(Object result);
} }

DeferredResult的超时处理,采用委托机制,也就是在实例DeferredResult时给予一个超时时长(毫秒),同时在onTimeout中委托(传入)一个新的处理线程(我们可以认为是超时线程);当超时时间到来,DeferredResult启动超时线程,超时线程处理业务,封装返回数据,给DeferredResult赋值(正确返回的或错误返回的)

Spring MVC异步模式中使用Filter和HandlerInterceptor

看到上面的异步访问,不免我们会新生怀疑,若是普通的拦截器HandlerInterceptor,还生效吗?若生效,效果是怎么样的,现在我们直接看一下吧:(备注:我以上面Callable的Demo为示例)

Filter
// 注意,这里必须开启异步支持asyncSupported = true,否则报错:Async support must be enabled on a servlet and for all filters involved in async request processing
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class HelloFilter extends OncePerRequestFilter { @Override
protected void initFilterBean() throws ServletException {
System.out.println("Filter初始化...");
} @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println(Thread.currentThread().getName() + "--->" + request.getRequestURI());
filterChain.doFilter(request, response);
} }

输出:

http-apr-8080-exec-3--->/demowar_war/async/controller/hello
http-apr-8080-exec-3 主线程start
http-apr-8080-exec-3 主线程end
MvcAsync1 子子子线程start
MvcAsync1 子子子线程end

由此可以看出,异步上下文,Filter还是只会被执行一次拦截的,符合我们的预期,所以没什么毛病。

HandlerInterceptor
public class HelloInterceptor implements HandlerInterceptor {

    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(Thread.currentThread().getName() + "---preHandle-->" + request.getRequestURI());
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(Thread.currentThread().getName() + "---postHandle-->" + request.getRequestURI());
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(Thread.currentThread().getName() + "---postHandle-->" + request.getRequestURI());
}
} // 注册拦截器
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer { @Override
public void addInterceptors(InterceptorRegistry registry) {
// /**拦截所有请求
registry.addInterceptor(new HelloInterceptor()).addPathPatterns("/**");
}
}

输出:

http-apr-8080-exec-3--->/demowar_war/async/controller/hello
http-apr-8080-exec-3---preHandle-->/demowar_war/async/controller/hello http-apr-8080-exec-3 主线程start
http-apr-8080-exec-3 主线程end
MvcAsync1 子子子线程start
MvcAsync1 子子子线程end // 注意 子子子线程处理结束后,再一次触发了preHandle=====
// 此处还要一个细节:这里面的线程既不是子线程,也不是上面的线程 而是新开了一个线程~~~
http-apr-8080-exec-5---preHandle-->/demowar_war/async/controller/hello
http-apr-8080-exec-5---postHandle-->/demowar_war/async/controller/hello
http-apr-8080-exec-5---afterCompletion-->/demowar_war/async/controller/hello

从上面可以看出,如果我们就是普通的Spring MVC的拦截器,preHandler会执行两次,这也符合我们上面分析的处理步骤。所以我们在书写preHandler的时候,一定要特别的注意,要让preHandler即使执行多次,也不要受到影响(幂等)

异步拦截器 AsyncHandlerInterceptor、CallableProcessingInterceptor、DeferredResultProcessingInterceptor

Spring MVC给提供了异步拦截器,能让我们更深入的参与进去异步request的生命周期里面去。其中最为常用的为:AsyncHandlerInterceptor

public class AsyncHelloInterceptor implements AsyncHandlerInterceptor {

    // 这是Spring3.2提供的方法,专门拦截异步请求的方式
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(Thread.currentThread().getName() + "---afterConcurrentHandlingStarted-->" + request.getRequestURI());
} @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(Thread.currentThread().getName() + "---preHandle-->" + request.getRequestURI());
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(Thread.currentThread().getName() + "---postHandle-->" + request.getRequestURI());
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(Thread.currentThread().getName() + "---afterCompletion-->" + request.getRequestURI());
}
}

输出:

http-apr-8080-exec-3---preHandle-->/demowar_war/async/controller/hello
http-apr-8080-exec-3 主线程start
http-apr-8080-exec-3 主线程end // 这里发现,它在主线程结束后,子线程开始之前执行的(线程号还是同一个哦~)
http-apr-8080-exec-3---afterConcurrentHandlingStarted-->/demowar_war/async/controller/hello MvcAsync1 子子子线程start
MvcAsync1 子子子线程end
http-apr-8080-exec-6---preHandle-->/demowar_war/async/controller/hello
http-apr-8080-exec-6---postHandle-->/demowar_war/async/controller/hello
http-apr-8080-exec-6---afterCompletion-->/demowar_war/async/controller/hello

AsyncHandlerInterceptor提供了一个afterConcurrentHandlingStarted()方法, 这个方法会在Controller方法异步执行时开始执行, 而Interceptor的postHandle方法则是需要等到Controller的异步执行完才能执行

(比如我们用DeferredResult的话,afterConcurrentHandlingStarted是在return的之后执行,而postHandle()是执行.setResult()之后执行)

需要说明的是:如果我们不是异步请求,afterConcurrentHandlingStarted是不会执行的。所以我们可以把它当做加强版的HandlerInterceptor来用。平时我们若要使用拦截器,建议使用它。(Spring5,JDK8以后,很多的xxxAdapter都没啥用了,直接implements接口就成~)

同样可以注册CallableProcessingInterceptor或者一个DeferredResultProcessingInterceptor用于更深度的集成异步request的生命周期

    @Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 注册异步的拦截器、默认的超时时间、任务处理器TaskExecutor等等
//configurer.registerCallableInterceptors();
//configurer.registerDeferredResultInterceptors();
//configurer.setDefaultTimeout();
//configurer.setTaskExecutor();
}

只是一般来说,我们并不需要注册这种精细的拦截器,绝大多数情况下,使用AsyncHandlerInterceptor是够了的。 (Spring MVC的很多默认设置,请参考WebMvcConfigurationSupport

区别使用

我觉得最主要的区别是:DeferredResult需要自己用线程来处理结果setResult,而Callable的话不需要我们来维护一个结果处理线程。 总体来说,Callable的话更为简单,同样的也是因为简单,灵活性不够; 相对地,DeferredResult更为复杂一些,但是又极大的灵活性,所以能实现非常多个性化的、复杂的功能,可以设计高级应用。

有些较常见的场景, Callable也并不能解决,比如说:我们访问A接口,A接口调用三方的服务,服务回调(注意此处指的回调,不是返回值)B接口,这种情况就没办法使用Callable了,这个时候可以使用DeferredResult

使用原则:基本上在可以用Callable的时候,直接用Callable;而遇到Callable没法解决的场景的时候,可以尝试使用DeferredResult

这里所指的Callable包括WebAsyncTask

总结

在Reactive编程模型越来越流行的今天,多一点对异步编程模型(Spring MVC异步模式)的了解,可以更容易去接触Spring5带来的新特性—响应式编程。 同时,异步编程是我们高效利用系统资源,提高系统吞吐量,编写高性能应用的必备技能。希望此篇文章能帮助到大家,运用到工作中~

然后,关于DeferredResult的高级使用场景,见下一篇博文:高级应用和源码分析篇

四、spring boot 加入拦截器后swagger不能访问问题

网上找的资料中大部分只说添加这个

// 注册拦截器
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
}

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
//...

}

参考:https://cloud.tencent.com/developer/article/1497804

SpringMVC之八:基于SpringMVC拦截器和注解实现controller中访问权限控制的更多相关文章

  1. 基于SpringMVC拦截器和注解实现controller中访问权限控制

    SpringMVC的拦截器HandlerInterceptorAdapter对应提供了三个preHandle,postHandle,afterCompletion方法. preHandle在业务处理器 ...

  2. spring mvc:内部资源视图解析器2(注解实现)@Controller/@RequestMapping

    spring mvc:内部资源视图解析器2(注解实现)  @Controller/@RequestMapping 访问地址: http://localhost:8080/guga2/hello/goo ...

  3. SpringMVC札集(09)——拦截器

    自定义View系列教程00–推翻自己和过往,重学自定义View 自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onL ...

  4. SpringMVC框架下的拦截器

    在eclipse的javaEE环境下:导包.... web.xml文件中的配置: <?xml version="1.0" encoding="UTF-8" ...

  5. springMVC学习(12)-使用拦截器

    一.拦截器配置和测试: 1)定义两个拦截器,(要实现HandlerInterceptor接口) HandlerInterceptor1: package com.cy.interceptor; imp ...

  6. springmvc(5)拦截器

    1.什么是拦截器 是指通过统一的拦截从浏览器发送到服务器的请求来完成相应服务增强 2.拦截器的基本原理 可以通过配置过滤器解决乱码问题 和过滤器非常相似 3.搭建工程 注意jar包 此时的工程是完成后 ...

  7. springmvc实现简单的拦截器

    SpringMVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的.在SpringMVC 中定义一个Interceptor 非常简单,主要有两种方式,第一种方 ...

  8. SpringMVC总结四:拦截器简单介绍

    首先要说一下HandlerExecutionChain: HandlerExecutionChain是一个执行链,当用户的请求到达DispatcherServlet的时候,DispatcherServ ...

  9. SpringMVC 学习笔记(六)拦截器

    5.1.处理器拦截器简介 Spring Web MVC的处理器拦截器(如无特殊说明,下文所说的拦截器即处理器拦截器) 类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理. ...

随机推荐

  1. 利用CSS变量实现悬浮效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. operator[],识别读操作和写操作

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  3. python3 堆排序

    思路: 1.建立堆 2.得到堆顶元素,为最大元素 3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序. 4.堆顶元素为第二大元素. 5.重复步骤3,直到堆变空. 动画 代码: de ...

  4. Quartz创建多个不同名字的scheduler实例

    _http://my.oschina.net/laiweiwei/blog/122280 需求创建多个不同的Scheduler实例,每个实例自主启动.关闭 问题 如果直接用 SchedulerFact ...

  5. js跨域详解

    跨域概念:Cross-origin resource sharing

  6. ss-libev 源码解析udp篇 (2)

    UDP relay的代码基本都在udprelay.c中,无论ss-local还是ss-server的代码都在一起,使用宏MODULE_LOCAL,MODULE_REMOTE等区分开.代码虽然不是很多, ...

  7. 程序设计入门-C语言基础知识-翁恺-第七周:指针与字符串-详细笔记(七)

    目录 第七周:指针与字符串 7.1 指针初步 7.2 字符类型 7.3 字符串 7.3 课后练习 第七周:指针与字符串 7.1 指针初步 sizeof 是一个运算符,给出某个类型或变量在内存中所占据的 ...

  8. lx 与cd 的计算方法

    Candela to lux calculation with distance in meters The illuminance Ev in lux (lx) is equal to the lu ...

  9. MPI 学习

    一.编译MPI mpic++ test.cc -o test 二.启动MPI mpiexec -np 10 ./test 三.几个例子 第一个进程向第二个发一个数,第二个进程向第三个进程发送一个数.. ...

  10. sync-settings(vscode)

    vscode插件以及设置 sync-download e45c6db33cd91d661e0cc545efb6817c