Spring源码系列- 第10章-SpringMVC中的HandlerAdapter源码解析

2023-11-01 10:21

本文主要是介绍Spring源码系列- 第10章-SpringMVC中的HandlerAdapter源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 第10章-SpringMVC中的HandlerAdapter源码解析
    • 测试类
      • HelloController
      • index.jsp
    • BeanNameUrlHandlerMapping简介
    • HandlerAdapter概述
      • DispatcherServlet#doDispatch()请求派发
      • DispatcherServlet#getHandlerAdapter()
      • HttpRequestHandlerAdapter#supports()
      • SimpleControllerHandlerAdapter#supports()
      • RequestMappingHandlerAdapter
        • AbstractHandlerMethodAdapter#supports()
        • RequestMappingHandlerAdapter#supportsInternal()
      • DispatcherServlet#doDispatch()
    • 举例说明HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter如何与BeanNameUrlHandlerMapping联动
      • 测试类-HelloHttpRequestHandler
      • DispatcherServlet#getHandlerAdapter()
      • DispatcherServlet#doDispatch()
      • HttpRequestHandlerAdapter#handle()
      • 测试类-HelloSimpleController
      • 小总结
    • RequestMappingHandlerAdapter中的参数解析器、返回值处理器=>概述
      • DispatcherServlet#doDispatch()
      • AbstractHandlerMethodAdapter#handle()
      • RequestMappingHandlerAdapter#handleInternal()
      • RequestMappingHandlerAdapter#invokeHandlerMethod( )
        • 什么是WebDataBinderFactory?
        • argumentResolvers参数解析器
        • returnValueHandlers返回值解析器
      • 15个参数解析器和27个返回值解析器是什么时候有值的?
        • RequestMappingHandlerAdapter#afterPropertiesSet()
        • RequestMappingHandlerAdapter#getDefaultArgumentResolvers()
        • RequestMappingHandlerAdapter#getDefaultReturnValueHandlers()
    • RequestMappingHandlerAdapter中的参数解析器工作流程
      • DispatcherServlet#doDispatch()
      • RequestMappingHandlerAdapter#invokeHandlerMethod()准备执行目标方法
      • ServletInvocableHandlerMethod#invokeAndHandle()真正开始执行目标方法
      • InvocableHandlerMethod#invokeForRequest( )目标方法的反射执行过程
      • InvocableHandlerMethod#getMethodArgumentValues()获取方法的请求参数
      • HandlerMethodArgumentResolverComposite准备循环27个参数解析器
      • HandlerMethodArgumentResolverComposite#supportsParameter()
      • HandlerMethodArgumentResolverComposite#getArgumentResolver()循环判断哪个参数解析器支持这个参数
      • RequestParamMethodArgumentResolver判断@RequestParam注解的参数解析器
      • RequestParamMapMethodArgumentResolver参数解析器
      • PathVariableMethodArgumentResolver判断@PathVariable注解的参数解析器
      • 返回到InvocableHandlerMethod#getMethodArgumentValues( )
      • 返回到InvocableHandlerMethod#invokeForRequest()
      • 返回到ServletInvocableHandlerMethod#invokeAndHandle()
      • SpringMVC到底能写哪些参数?
    • RequestMappingHandlerAdapter中的返回值解析器工作流程
      • ServletInvocableHandlerMethod#invokeAndHandle()
      • HandlerMethodReturnValueHandlerComposite#handleReturnValue()
      • HandlerMethodReturnValueHandlerComposite#selectHandler()循环15个返回值处理器找到合适的返回值处理器
      • ViewNameMethodReturnValueHandler#supportsReturnType()
      • 返回到HandlerMethodReturnValueHandlerComposite#handleReturnValue()
      • ViewNameMethodReturnValueHandler#handleReturnValue()开始处理返回值
      • 返回到RequestMappingHandlerAdapter#invokeHandlerMethod()
      • RequestMappingHandlerAdapter#getModelAndView()进行视图解析相关工作
      • 返回到DispatcherServlet#doDispatch()
      • DispatcherServlet#applyDefaultViewName()
      • 返回到DispatcherServlet#doDispatch()
      • springmvc能写哪些返回值
    • 视图解析器解析流程
      • DispatcherServlet#processDispatchResult()处理返回值
      • DispatcherServlet#render()渲染ModelAndView
      • DispatcherServlet#resolveViewName()
      • RequestResponseBodyMethodProcessor即是返回值解析器也是参数解析器

  1. 文章优先发布在Github,其它平台会晚一段时间,文章纠错与更新内容只在Github:https://github.com/youthlql/JavaYouth
  2. 转载须知:转载请注明GitHub出处,让我们一起维护一个良好的技术创作环境。
  3. 如果你要提交 issue 或者 pr 的话建议到 Github 提交。笔者会陆续更新,如果对你有所帮助,不妨Github点个Star~。你的Star是我创作的动力。

         

第10章-SpringMVC中的HandlerAdapter源码解析

测试类

HelloController

package cn.imlql.web.controller;@Controller
public class HelloController {public HelloController() {System.out.println("HelloController.....");}@AutowiredHelloService helloService;@GetMapping("/hello")public String sayHello(String name,@RequestParam("user") String user,HttpSession session) {String mvc = helloService.say(user + ":MVC" + name);session.setAttribute("msg", mvc);return "index.jsp";}
}

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>$Title$</title></head><body>$END$结果: <h1 style="color: red">${sessionScope.msg}</h1></body>
</html>

其余的类和前面一样,不列举了。

BeanNameUrlHandlerMapping简介

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {/*** Checks name and aliases of the given bean for URLs, starting with "/".*/@Override  //决定使用哪个url注册protected String[] determineUrlsForHandler(String beanName) {List<String> urls = new ArrayList<>();if (beanName.startsWith("/")) {urls.add(beanName);}String[] aliases = obtainApplicationContext().getAliases(beanName);for (String alias : aliases) {if (alias.startsWith("/")) {urls.add(alias);}}return StringUtils.toStringArray(urls);}}
  1. 很简单,意思就是只要你的BeanName是以/开头,就会将这个Bean封装成一个BeanNameUrlHandlerMapping映射。
  2. 具体路径怎么写呢?你可以在你的类上写一个@Controller("/helloReq") ,这样就有路径了。代表的意思就是/helloReq这个URL和由哪个handler来处理的映射关系被保存在了BeanNameUrlHandlerMapping里
  3. 如果不懂的话可以待会看下面的例子

handler就是咱们常说的XXXController

HandlerAdapter概述

  1. 上面我们从HandlerMapping这个映射中心找到了由哪个Controller执行哪个方法
  2. 按照我们最简单的想法就是直接method.invoke()反射执行
  3. 但是实际上我们要考虑
    1. 是哪个对象执行方法?
    2. 方法里的参数有几个?
    3. 参数类型是什么?参数类型大概率有我们自己写的类,怎么处理
    4. 多个参数我们还要一个一个赋值。
    5. 怎么返回?是直接返回值,还是跳转页面,等等
  4. 其实是比较复杂的,springmvc就写了一个HandlerAdapter来处理这些复杂的逻辑

还是跟HandlerMapping一样,继续Debug DispatcherServlet#doDispatch(HttpServletRequest, HttpServletResponse)

浏览器输入:http://localhost:8080/springmvc_source_test/hello?name=zhangsan&user=haha开始测试

DispatcherServlet#doDispatch()请求派发

在这里插入图片描述

    //SpringMVC处理请求的核心流程protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null; //handler(目标方法)的执行链boolean multipartRequestParsed = false; //文件上传标志//对异步请求的支持(Servlet3.0以后才有的,Webflux)WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request); //检查当前是否文件上传请求multipartRequestParsed = (processedRequest != request);//构造出了【目标方法+拦截器整个链路】决定使用哪个Handler处理当前请求 Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {  //如果找不到人处理,就send 404noHandlerFound(processedRequest, response);return;}//适配器怎么找的、 Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}//所有拦截器的 preHandle 执行if (!mappedHandler.applyPreHandle(processedRequest, response)) {return; //使用 mappedHandler整个链}//真正来执行目标方法 Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);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);} //处理结果processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

F7进入getHandlerAdapter(mappedHandler.getHandler())

DispatcherServlet#getHandlerAdapter()

在这里插入图片描述

上面几个默认的适配器还是在DispatcherServlet.properties配置的4个默认适配器,然后在初始化九大组件的时候放到容器里

HttpRequestHandlerAdapter#supports()

    @Override //想让他工作,实现HttpRequestHandler接口即可public boolean supports(Object handler) { //写一个HttpRequestHandler的实现也能处理请求return (handler instanceof HttpRequestHandler);}

这里判断当前handler【就是咱们写的Controller】是否实现了HttpRequestHandler接口,是的话就直接返回这个Adapter,不往下走了

SimpleControllerHandlerAdapter#supports()

    @Overridepublic boolean supports(Object handler) {return (handler instanceof Controller);}

同理,这里判断当前handler是否实现了Controller接口,是的话就直接返回这个Adapter,不往下走了

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter自己没有写supports方法,它用的是父类的AbstractHandlerMethodAdapter的

AbstractHandlerMethodAdapter#supports()
    public final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}

这个就是判断当前handler是不是HandlerMethod类型的,前面大致讲过只要标注了@RequestMapping注解的handler最终都会被封装成HandlerMethod

在这里插入图片描述

RequestMappingHandlerAdapter#supportsInternal()
    protected boolean supportsInternal(HandlerMethod handlerMethod) {return true;}

DispatcherServlet#doDispatch()

在这里插入图片描述

举例说明HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter如何与BeanNameUrlHandlerMapping联动

测试类-HelloHttpRequestHandler

@Controller("/helloReq") //BeanNameUrlHandlerMapping 就会把他注册进去
public class HelloHttpRequestHandler implements HttpRequestHandler {//启用 HttpRequestHandlerAdapter//处理请求@Overridepublic void handleRequest(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {response.getWriter().write("HelloHttpRequestHandler....");}
}

@Controller("/helloReq")里的/helloReq即是BeanName也是URL。这里就是之前说的BeanName以/为开头的Bean,它的URL和handler的映射关系会被保存在BeanNameUrlHandlerMapping里,如下图

在这里插入图片描述

DispatcherServlet#getHandlerAdapter()

在这里插入图片描述

因为我们的HelloHttpRequestHandler正好实现了HttpRequestHandler,所以就会直接返回HttpRequestHandlerAdapter

DispatcherServlet#doDispatch()

我们直接来到准备执行的地方

在这里插入图片描述

F7进入ha.handle(processedRequest, response, mappedHandler.getHandler())

HttpRequestHandlerAdapter#handle()

在这里插入图片描述

直接来到了这里,接着就调用我们自定义的HelloHttpRequestHandler的handleRequest方法。

测试类-HelloSimpleController

//@RequestMapping("/ssss")
@Controller("/helloSimple")
public class HelloSimpleController implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {return null;}
}

实现Controller接口的就是SimpleControllerHandlerAdapter来处理,原理和HttpRequestHandlerAdapter几乎一样。

小总结

  1. 很显然BeanNameUrlHandlerMapping和HttpRequestHandlerAdapter的结合,一个类只能处理一个URL路径的请求。并且这里的参数处理都很麻烦,不像@RequestMapping那么方便
  2. 远不如RequestMappingHandlerMapping和RequestMappingHandlerAdapter的结合来的方便。它两结合后,一个类里可以写多个@RequestMapping注解标注的方法,一个类里就可以处理多个URL请求,并且处理参数和返回值都很方便。
  3. 所以下面就重点讲RequestMappingHandlerAdapter

RequestMappingHandlerAdapter中的参数解析器、返回值处理器=>概述

DispatcherServlet#doDispatch()

在这里插入图片描述

AbstractHandlerMethodAdapter#handle()

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);
}

AbstractHandlerMethodAdapter是RequestMappingHandlerAdapter的父类,之前说过

RequestMappingHandlerAdapter#handleInternal()

    protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// 会话锁,每一个用户和服务器交互无论发了多少请求都只有一个会话,限制用户的线程 Execute invokeHandlerMethod in synchronized block if required.if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) { //高并发可以限制一个用户一次进来一个请求mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available -> no mutex necessarymav = invokeHandlerMethod(request, response, handlerMethod);}}else {//执行目标方法 No synchronization on session demanded at all...mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;}//session锁这个东西不适用于高并发场景,所以spring默认是不适用它,应该是可以开启的,具体怎么开启我没研究,可以看官方文档private boolean synchronizeOnSession = false;

在这里插入图片描述

RequestMappingHandlerAdapter#invokeHandlerMethod( )

    /*** Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}* if view resolution is required.* @since 4.2* @see #createInvocableHandlerMethod(HandlerMethod)*/@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {//把原生的request,response封装到一个对象中方便后续只用这一个参数就行【装饰器模式】ServletWebRequest webRequest = new ServletWebRequest(request, response);try { //数据绑定器WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);//获取到模型工厂 Model(要交给页面的数据) View(我们要去的 视图),视图可以理解为页面或者图片等等ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 这里就是做了一些增强,比如我们可以直接通过ServletInvocableHandlerMethod拿到返回值,可以很方便的拿到其它我们需要的信息ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) { //参数解析器invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) { //返回值解析器invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);//以上的 几个核心组件都挺重要的  ModelAndViewContainer是以后处理过程中产生的ModelAndView数据临时存储容器// 也是整个请求处理流程,线程共享数据ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);//异步请求有关的AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}//真正开始执行目标方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}
什么是WebDataBinderFactory?
public String sayHello(Person person){ 
//...
}
  1. 假设方法里有一个Person类型的参数,Person里的属性就是age,name,sex这些的
  2. 前端传的参数名刚好能和Person的属性对上,WebDataBinder就会帮我们自动绑定上。
argumentResolvers参数解析器

在这里插入图片描述

注意有一些解析器,比如RequestResponseBodyMethodProcessor即是返回值解析器也是参数解析器

returnValueHandlers返回值解析器

在这里插入图片描述

15个参数解析器和27个返回值解析器是什么时候有值的?

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {// ...
}

我们可以看到RequestMappingHandlerAdapter也是实现了InitializingBean接口,所以它什么时候有值,和RequestMappingHandlerMapping是一样的逻辑

  1. DispatcherServlet#onRefresh()开始初始化九大组件,就会开始初始化HandlerAdapter
  2. 首先是创建DispatcherServlet.properties里指定的四个HandlerAdapter实现类的对象。还是用createBean来创建HandlerAdapter的
  3. 其中RequestMappingHandlerAdapter创建完对象后,因为它实现了InitializingBean,所以会调用RequestMappingHandlerAdapter#afterPropertiesSet()
  4. 在afterPropertiesSet()中就直接new了所有默认的参数解析器和返回值解析器
RequestMappingHandlerAdapter#afterPropertiesSet()
    public void afterPropertiesSet() { //初始化以后// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();  //初始化 ControllerAdvice 【异常处理相关的功能】if (this.argumentResolvers == null) {  //拿到底层所有的默认 argumentResolversList<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); //把这些resolver统一组合到一个对象里面,方便管控}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}
RequestMappingHandlerAdapter#getDefaultArgumentResolvers()
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);// Annotation-based argument resolution//这里放了一个RequestParamMethodArgumentResolver,但是它的第二个参数是false,第二个参数是什么呢?我们后面说它的时候再讲resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));resolvers.add(new RequestParamMapMethodArgumentResolver());resolvers.add(new PathVariableMethodArgumentResolver());resolvers.add(new PathVariableMapMethodArgumentResolver());resolvers.add(new MatrixVariableMethodArgumentResolver());resolvers.add(new MatrixVariableMapMethodArgumentResolver());resolvers.add(new ServletModelAttributeMethodProcessor(false));resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));resolvers.add(new RequestHeaderMapMethodArgumentResolver());resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));resolvers.add(new SessionAttributeMethodArgumentResolver());resolvers.add(new RequestAttributeMethodArgumentResolver());// Type-based argument resolutionresolvers.add(new ServletRequestMethodArgumentResolver());resolvers.add(new ServletResponseMethodArgumentResolver());resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));resolvers.add(new RedirectAttributesMethodArgumentResolver());resolvers.add(new ModelMethodProcessor());resolvers.add(new MapMethodProcessor());resolvers.add(new ErrorsMethodArgumentResolver());resolvers.add(new SessionStatusMethodArgumentResolver());resolvers.add(new UriComponentsBuilderMethodArgumentResolver());if (KotlinDetector.isKotlinPresent()) {resolvers.add(new ContinuationHandlerMethodArgumentResolver());}// Custom argumentsif (getCustomArgumentResolvers() != null) {resolvers.addAll(getCustomArgumentResolvers());}// Catch-allresolvers.add(new PrincipalMethodArgumentResolver());//这里也放了一个RequestParamMethodArgumentResolver,但是它的第二个参数是trueresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));resolvers.add(new ServletModelAttributeMethodProcessor(true));return resolvers;}
RequestMappingHandlerAdapter#getDefaultReturnValueHandlers()
    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);// Single-purpose return value typeshandlers.add(new ModelAndViewMethodReturnValueHandler());handlers.add(new ModelMethodProcessor());handlers.add(new ViewMethodReturnValueHandler());handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));handlers.add(new StreamingResponseBodyReturnValueHandler());handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));handlers.add(new HttpHeadersReturnValueHandler());handlers.add(new CallableMethodReturnValueHandler());handlers.add(new DeferredResultMethodReturnValueHandler());handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));// Annotation-based return value typeshandlers.add(new ServletModelAttributeMethodProcessor(false));handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));// Multi-purpose return value typeshandlers.add(new ViewNameMethodReturnValueHandler());handlers.add(new MapMethodProcessor());// Custom return value typesif (getCustomReturnValueHandlers() != null) {handlers.addAll(getCustomReturnValueHandlers());}// Catch-allif (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));}else {handlers.add(new ServletModelAttributeMethodProcessor(true));}return handlers;}

RequestMappingHandlerAdapter中的参数解析器工作流程

DispatcherServlet#doDispatch()

在这里插入图片描述

AbstractHandlerMethodAdapter#handle(HttpServletRequest , HttpServletResponse , Object )RequestMappingHandlerAdapter#handleInternal(HttpServletRequest , HttpServletResponse , HandlerMethod )上面说过,跳过

RequestMappingHandlerAdapter#invokeHandlerMethod()准备执行目标方法

在这里插入图片描述

ServletInvocableHandlerMethod#invokeAndHandle()真正开始执行目标方法

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//目标方法的反射执行过程Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}}

InvocableHandlerMethod#invokeForRequest( )目标方法的反射执行过程

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//获取方法的请求参数Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}return doInvoke(args); //就是反射执行}

在这里插入图片描述

开始进入正题

InvocableHandlerMethod#getMethodArgumentValues()获取方法的请求参数

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//直接拿到方法的所有参数,getMethodParameters是HandlerMethod类的,在之前构造handler的时候就已经通过反射将相关信息保存好了// 我们之前讲过标注@RequestMapping注解的handler最终会被封装成HandlerMethodMethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}//准备args数组(和parameters一样长),挨个确定每个参数都是什么值Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs); //先去已提供的参数中找if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) { //支持这种参数的解析器也会被放到缓存,下一次进来,就不用27个人挨个判断throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;
}

在这里插入图片描述

HandlerMethodArgumentResolverComposite准备循环27个参数解析器

在这里插入图片描述

HandlerMethodArgumentResolverComposite#supportsParameter()

    public boolean supportsParameter(MethodParameter parameter) {return getArgumentResolver(parameter) != null;}

HandlerMethodArgumentResolverComposite#getArgumentResolver()循环判断哪个参数解析器支持这个参数

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); //先看缓存中有没有if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;
}

这里就是循环27个参数解析器,看哪一个能解析这种类型的参数。因为参数解析器太多,我们就举几个常用的例子

RequestParamMethodArgumentResolver判断@RequestParam注解的参数解析器

package org.springframework.web.method.annotationpublic boolean supportsParameter(MethodParameter parameter) {if (parameter.hasParameterAnnotation(RequestParam.class)) {//注意这里是为了不跟下面的RequestParamMapMethodArgumentResolver产生冲突if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && StringUtils.hasText(requestParam.name()));}else {return true;}}else {if (parameter.hasParameterAnnotation(RequestPart.class)) {return false;}parameter = parameter.nestedIfOptional();if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {return true;}else if (this.useDefaultResolution) {return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());}else {return false;}}}

这个就是判断参数有没有标@RequestParam注解

注意这个是org.springframework.web.method.annotation包下的

在这里插入图片描述

在这里插入图片描述

  1. 第一次进入RequestParamMethodArgumentResolver#supportsParameter(MethodParameter parameter)时,我们在前面讲过,在RequestMappingHandlerAdapter#getDefaultArgumentResolvers()添加默认解析器的时候,spring往参数解析器里加了两个
 	 //这里放了一个RequestParamMethodArgumentResolver,但是它的第二个参数是false,第二个参数是什么呢?我们后面说它的时候再讲resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));//这里也放了一个RequestParamMethodArgumentResolver,但是它的第二个参数是trueresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
  1. 这里的true和false指定的就是useDefaultResolution这个东西

  2. 然后我们验证一下第二个RequestParamMethodArgumentResolver

在这里插入图片描述

  1. useDefaultResolution这个看名字应该就是前面25个处理器都用不到的时候,就用这个默认中的默认处理器第26个RequestParamMethodArgumentResolver(双重默认)
  2. 那什么时候用第27个ServletModelAttributeMethodProcessor这个处理器呢?当第26个在上面那几个if else中返回false,就会用最后这个,至于什么时候返回false,我没有深入研究。

RequestParamMapMethodArgumentResolver参数解析器

@RequestParam Map<String,Object> params

public boolean supportsParameter(MethodParameter parameter) {RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&!StringUtils.hasText(requestParam.name()));
}

判断有没有标@RequestParam注解,并且类型是Map

PathVariableMethodArgumentResolver判断@PathVariable注解的参数解析器

    public boolean supportsParameter(MethodParameter parameter) {if (!parameter.hasParameterAnnotation(PathVariable.class)) {return false;}if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);return (pathVariable != null && StringUtils.hasText(pathVariable.value()));}return true;}

相信大家已经能猜到了,这里就是判断参数上有没有标注@PathVariable注解

返回到InvocableHandlerMethod#getMethodArgumentValues( )

在这里插入图片描述

具体的参数赋值过程,就是一些数据类型的转换,可以自己去看下。

args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

返回到InvocableHandlerMethod#invokeForRequest()

在这里插入图片描述

接下来就是交给反射去执行

返回到ServletInvocableHandlerMethod#invokeAndHandle()

在这里插入图片描述

拿到返回值了,接下来交给返回值处理器

SpringMVC到底能写哪些参数?

详见官方文档-参数相关

RequestMappingHandlerAdapter中的返回值解析器工作流程

@Controller
public class HelloController {public HelloController() {System.out.println("HelloController.....");}@AutowiredHelloService helloService;@GetMapping("/hello") public String sayHello(String name, @RequestParam("user") String user,HttpSession session, HttpServletRequest request, //原生的session对象@RequestHeader("User-Agent") String ua) { //@RequestParam Map<String,Object> params:所有请求参数全封装进来// @RequestHeader("User-Agent") String  ua 获取指定请求头的值String header = request.getHeader("User-Agent");//方法的签名,到底能写那些?//详细参照 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments//https://www.bilibili.com/video/BV19K4y1L7MT?p=32String mvc = helloService.say(user + ":MVC" + name);session.setAttribute("msg", mvc);//SpringMVC的目标方法能写哪些返回值//https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-typesreturn "index.jsp";}
}

ServletInvocableHandlerMethod#invokeAndHandle()

在这里插入图片描述

我们可以看到RequestResponseBodyMethodProcessor比ViewNameMethodReturnValueHandler优先级高,接下来我们细讲

HandlerMethodReturnValueHandlerComposite#handleReturnValue()

    @Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//找到合适的返回值处理器.HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());} //返回值处理器. 处理返回值handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}

HandlerMethodReturnValueHandlerComposite#selectHandler()循环15个返回值处理器找到合适的返回值处理器

    private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);//还是老样子for循环for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}if (handler.supportsReturnType(returnType)) {return handler;}}return null;}

我们就直接放行,看最终找到的是哪个

在这里插入图片描述

ViewNameMethodReturnValueHandler#supportsReturnType()

    public boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();//返回值是void,或者字符串return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));}

返回到HandlerMethodReturnValueHandlerComposite#handleReturnValue()

在这里插入图片描述

ViewNameMethodReturnValueHandler#handleReturnValue()开始处理返回值

    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue instanceof CharSequence) { //只要是字符串就是跳转到的页面地址String viewName = returnValue.toString();mavContainer.setViewName(viewName);if (isRedirectViewName(viewName)) { //是否是重定向的方式  redirect:mavContainer.setRedirectModelScenario(true);}}else if (returnValue != null) {// should not happenthrow new UnsupportedOperationException("Unexpected return type: " +returnType.getParameterType().getName() + " in method: " + returnType.getMethod());}}

在这里插入图片描述

然后就准备返回了

返回到RequestMappingHandlerAdapter#invokeHandlerMethod()

在这里插入图片描述

RequestMappingHandlerAdapter#getModelAndView()进行视图解析相关工作

    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {//modelFactory准备模型数据  (请求域数据共享)session里面的数据搬家到request域modelFactory.updateModel(webRequest, mavContainer);if (mavContainer.isRequestHandled()) {return null;}ModelMap model = mavContainer.getModel();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);if (request != null) { //重定向数据的共享,RedirectView。先把数据移到request,再把request移到sessionRequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav;}

在这里插入图片描述

注意如果你返回值写index而不是index.jsp,它会给你报404。意思就是它不会给我们加jsp后缀。

返回到DispatcherServlet#doDispatch()

在这里插入图片描述

DispatcherServlet#applyDefaultViewName()

    private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {if (mv != null && !mv.hasView()) { //如果没有指定跳转的页面String defaultViewName = getDefaultViewName(request); //给一个默认页面if (defaultViewName != null) {mv.setViewName(defaultViewName);}}}protected String getDefaultViewName(HttpServletRequest request) throws Exception {return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);}@Nullable //把请求转成视图名(我们要跳转的页面地址)的翻译器【没啥用】private RequestToViewNameTranslator viewNameTranslator;

这里不是重点,我直接告诉你结果吧,默认页面就是把request的请求路径直接拿来当要去的页面地址 。

比如你的请求路径是@GetMapping("/hello.html"),但是你返回值写的是void,那么它就会给你返回到hello.html页面

返回到DispatcherServlet#doDispatch()

在这里插入图片描述

来到拦截器的后置处理环节

在这里插入图片描述

然后来到处理结果环节

springmvc能写哪些返回值

官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types

视图解析器解析流程

DispatcherServlet#processDispatchResult()处理返回值

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) { //如果有异常处理异常if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();//即使有异常,这里也会返回ModelAndView}else {  //定义无数种异常解析器就会得到不同的异常解析效果Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// 动态策略。 Did the handler return a view to render?   为啥?@ResponseBody(提前在解析返回值的时候,就已经把数据写出去了,所以这一步就没有了)if (mv != null && !mv.wasCleared()) {render(mv, request, response); //渲染ModeAndView,来解析模型和视图;最终决定响应效果if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);}}

进入此方法

在这里插入图片描述

DispatcherServlet#render()渲染ModelAndView

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);View view;String viewName = mv.getViewName();if (viewName != null) {// We need to resolve the view name.  关键还是这里view = resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isTraceEnabled()) {logger.trace("Rendering view [" + view + "] ");}try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "]", ex);}throw ex;}}

DispatcherServlet#resolveViewName()

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,Locale locale, HttpServletRequest request) throws Exception {if (this.viewResolvers != null) {//一样的for循环视图解析器for (ViewResolver viewResolver : this.viewResolvers) {View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}}return null;
}

不过默认的视图解析器只有一个

在这里插入图片描述

这里没有拼接前缀和后缀,所以我们必须要自己写.jsp或者.html

RequestResponseBodyMethodProcessor即是返回值解析器也是参数解析器

    @Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(RequestBody.class);}@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));}public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {parameter = parameter.nestedIfOptional();Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());String name = Conventions.getVariableNameForParameter(parameter);if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);if (arg != null) {validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());}}if (mavContainer != null) {mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());}}return adaptArgumentIfNecessary(arg, parameter);}@Override  //如果返回值标注了 @ResponseBody注解。会被这个人拦截处理器public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}

由于现在的前后端分离时代,我们直接返回视图的需求已经很少很少了。现在一般都是直接返回数据,所以视图解析器详细原理不再细述,后面就直接开始讲异常处理流程。

这篇关于Spring源码系列- 第10章-SpringMVC中的HandlerAdapter源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/322294

相关文章

IDEA运行spring项目时,控制台未出现的解决方案

《IDEA运行spring项目时,控制台未出现的解决方案》文章总结了在使用IDEA运行代码时,控制台未出现的问题和解决方案,问题可能是由于点击图标或重启IDEA后控制台仍未显示,解决方案提供了解决方法... 目录问题分析解决方案总结问题js使用IDEA,点击运行按钮,运行结束,但控制台未出现http://

解决Spring运行时报错:Consider defining a bean of type ‘xxx.xxx.xxx.Xxx‘ in your configuration

《解决Spring运行时报错:Considerdefiningabeanoftype‘xxx.xxx.xxx.Xxx‘inyourconfiguration》该文章主要讲述了在使用S... 目录问题分析解决方案总结问题Description:Parameter 0 of constructor in x

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug