系列文章目录
这是一关于JaverWeb中Servlet3.0 中的DeferredResult学习使用和深入理解
前言
例如:最近发现公司的用户模块在编写用户扫码登录时的用户在app端授权的功能用到了一个叫DeferredResult
的东西,进入到源码一看果然是Spring的东西。一开始并不了解这个东西的真正用意是什么,然后就去百度看了下这个博客。
DeferredResult应用场景(作者:方志朋)这篇博客里面详细的解释了完整的DeferredResult的使用场景和Demo代码示例。初学者可以很好的理解这个怎么去使用。
一、DeferredResult是什么?
DeferredResult 是 Spring Framework 提供的一种异步处理机制,用于在异步操作完成后返回结果。(这是GPT给我的回答),同样带着问题继续追问下面的属性代表着什么含义?
这是DeferredResult中所含有的所有的属性:
result:
类型: T,泛型类型,表示异步操作的结果。
描述: 用于存储异步操作的结果。当异步操作完成后,可以通过调用 setResult 方法将结果设置到 DeferredResult 中,从而完成异步请求。
timeoutResult:
类型: T,泛型类型,表示超时时返回的结果。
描述: 用于设置超时时返回的结果。如果在超时时间内异步操作未完成,可以通过设置 timeoutResult 来定义超时时返回的结果。
hasResult:
类型: boolean。
描述: 表示是否已经设置了结果。如果异步操作已完成,且结果已经设置,则 hasResult 为 true;否则为 false。
timeoutMillis:
类型: long。
描述: 表示超时时间,以毫秒为单位。指定在多长时间内等待异步操作完成。如果在超时时间内未完成,可以执行超时处理逻辑。
expirationHandler:
类型: Runnable。
描述: 在超时时执行的处理逻辑。可以通过设置 expirationHandler 属性来定义超时时的处理逻辑,例如设置默认结果或执行特定操作。
resultHandler:
类型: BiConsumer<T, Throwable>。
描述: 在异步操作完成时执行的处理逻辑。resultHandler 接受两个参数,第一个参数是异步操作的结果,第二个参数是异步操作的异常(如果有的话)。
可我再次问题它:他的原理是什么,他只是简单的回答了我:“它的实现原理涉及到 Spring MVC、Servlet 3.0+ 异步请求以及Java 的 CompletableFuture 等相关技术。”于是我带着问题去查看了Servlet3.1的规范 规范下载地址。
规范中我全局搜索关于Async字眼,我找的下面的一段话,是出现在规范中第2大节中的3.3.3小节中的第五点:
早在EJB中就规定了,是的:用Servlet时候
直接在响应对象上操作的其他线程”,也就意味着Servlet在3.0版本要求支持了异步的方式:
打开Servlet的Jar包发现在有多出了AsyncContext 来实现的,3.0以上的Servlet容器都已经按照这个标准实现了异步的处理请求和返回响应。例如我们常用的Undertow服务容器也使用了这个AsyncContext来提高服务得吞吐量。
至于使用方法规范文档中只给了简单的是由方式,并没有给出完整的使用方式。
我这里偷个懒,具体的使用方式网上搜一艘一大把:我这里贴出我认为一个比较写的好的博客,里面涵盖了AsyncContext的使用方法:Servlet3.0新特性:异步处理,太好用了
可以看看,大致看完你会发现这个功能和DeferredResult的功能类似,我现在已经逐渐抛砖引玉的介绍了Servlet3.0中的一个新特性,这里也只介绍这一个特性,感兴趣的同学可再去翻翻规范。
既然已经介绍到这里了,那Spring是如何帮我们封装的这个结果呢?
二、SpringMVC是怎么封装的?
1.前置知识准备:
- 首先回顾下SpringMVC在分配RequestMapping的时候用到了什么核心的组件?
既然是一个DeferredResult
是一个请求,那么对于SpringMVC跑不了的肯定是DispatcherServlet
,简单来说就是DispatcherServlet
根据handlerMapping
创建一个处理器执行链对象HandlerExecutionChain
。然后根据HandlerExecutionChain
里面的handler来获取HandlerAdapter
。
HandlerAdapter
调用handle方法处理请求。对DispatcherServlet
来说,其不关心底层细节,只需要找到handler对应的HandlerAdapter
然后交给HandlerAdapter
即可
也就是这里
图片截图代码中的
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
对于经常写Springboot项目的同学经常使用肯定就是众多Adapter中的RequestMappingHandlerAdapter
这个对于的那个文件:由于图片展示不全我这里使用代码的方式解释源码中的每个字段的含义:
// 是否忽略spring的xml配置,默认false
private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore");
// 找标注了@InitBinder方法
public static final MethodFilter INIT_BINDER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
// 找标注了 @ModelAttribute方法,但是没有标注@RequestMapping
public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
// 自定义的参数解析器
@Nullable
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
// 框架内部的参数解析器
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
// 标注了@InitBinder方法的参数解析器
@Nullable
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
// 自定义返回结果处理器
@Nullable
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
// 框架内部的返回结果处理器
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
// 框架内部视图解析器
@Nullable
private List<ModelAndViewResolver> modelAndViewResolvers;
// 框架内部内容协商管理器
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
// 信息转换器
private List<HttpMessageConverter<?>> messageConverters;
// 请求响应体通知/顾问器,主要用来在读取请求或者输出响应前做一些处理
private final List<Object> requestResponseBodyAdvice = new ArrayList<>();
// 数据绑定初始化器
@Nullable
private WebBindingInitializer webBindingInitializer;
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("MvcAsync");
@Nullable
private Long asyncRequestTimeout;
private CallableProcessingInterceptor[] callableInterceptors = new CallableProcessingInterceptor[0];
private DeferredResultProcessingInterceptor[] deferredResultInterceptors = new DeferredResultProcessingInterceptor[0];
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
private boolean ignoreDefaultModelOnRedirect = false;
private int cacheSecondsForSessionAttributeHandlers = 0;
private boolean synchronizeOnSession = false;
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
// 参数名发现/解析器
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
// beanFactory
@Nullable
private ConfigurableBeanFactory beanFactory;
// 如下是一些缓存
private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<>(64);
private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);
// @ControllerAdvice类中的@InitBinder方法
private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>();
private final Map<Class<?>, Set<Method>> modelAttributeCache = new ConcurrentHashMap<>(64);
// @ControllerAdvice类中的@ModelAttribute方法
private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache = new LinkedHashMap<>();
其实这里面目前我们目前只关心:
// 框架内部的返回结果处理器
HandlerMethodReturnValueHandlerComposite returnValueHandlers;
这个属性就可以了,因为这就是DeferredResult
在setResult的时候的核心
- HandlerMethodReturnValueHandlerComposite
这就涉及到 SpringMvc 的内容。当通过反射调用 controller 中的方法得到返回值的时候,需要根据返回值的类型调用不同的 returnValueHandlers 来处理。
先不说源码先说大致流程:它调用了 tomcat 里面 Request#startAsync 方法,也传递了 timeout 的时间,这个操作是让 tomcat 明白当前请求是一个异步请求,这样 tomcat 就不会直接将 new 的那个 deferredResult 返回给客户端,也不会销毁当前的 request 和 response 。
途中的红框标注了在处理返回值的时候使用什么样的方式来返回。
startDeferredResultProcessing
这个方法是怎么实现的?
真正执行异步的放在#startAsyncProcessing
这个方法中
我们似乎看到了一些曙光,嗯?这是什么#startAsync 这不是快回到Servletl里了?
果然 :
这里果然调用的是AsyncContext
的startAsync看到这里是不是忽然明白?原来GPT诚不欺我,原理果然是通过Servlet3.0规范实现的。接下来可能就是要了解不同厂家的服务器按照规范是如何实现的?接下来了解常见的EJB规范的容器 三条腿的猫-Tomcat
来看看Tomcat中的#startAsync是如何实现的?
三、Tomcat如何实现Servlet3.0中的Async?
这里属于Tomcat的范畴了
我看了有相对完善的博客了,我这里转载一篇。
Tomcat是如何实现异步Servlet的
总结
例如:以上就是今天要讲的内容,文章相对晦涩难懂,类似的文章有很多,我只是做了大部分知识的搬运工,这篇文章也是记录了我一点点了解DeferredResult的过程,和学习Servlet3.0的过程。