SpringMVC启动流程

文章目录

  • 引文
  • Handler
  • HandlerMapper
  • HandlerAdapter
  • @RequestMapping方法参数解析
  • @RequestMapping方法返回值解析
  • 文件上传流程
  • 拦截器解析


SpringMVC启动流程如下

在这里插入图片描述

引文



我们在使用SpringMVC时,传统的方式是在webapp目录下定义一个web.xml文件,比如:

<web-app><servlet><servlet-name>app</servlet-name><servletclass>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>app</servlet-name><url-pattern>/app/*</url-pattern></servlet-mapping>
</web-app>

SpringMVC 的启动过程如下所示:

  1. 启动Tomcat

  2. Tomcat读取到web.xml文件,创建DispatcherServlet对象,因为它的load-on-startup配置为1,表示tomcat启动时创建

  3. 调用DispatcherServlet对象的init()方法,因为说到底DispatcherServlet它还是一个Servlet,还是遵守Servlet的生命周期的。

    init()方法中会创建一个Spring容器,并且添加一个ContextRefreshListener监听器,该监听器会监听ContextRefreshedEvent事件(Spring容器创建完成就会发布这个事件)。也就是说spring容器启动完成后就会执行ContextRefreshListener中的onApplicationEvent事件,从而最终会执行到DespatcherServlet中的initStrategies(),这个方法会初始化更多内容:

    protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);// 其中比较重要的就是初始化 HandlerMapper和 HandlerAdapterinitHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);
    }
    

我们现在就注重讲讲HandlerMapper和 HandlerAdapter



Handler

什么是Handler?它其实表示请求处理器,在SpringMVC中有四种Handler:

  1. 实现了Controller接口的Bean对象
  2. 实现了HttpRequestHandler接口的Bean对象
  3. 添加了@RequestMapper注解的方法
  4. 一个HandlerFunction对象



详情如下:

实现了Controller接口的Bean对象,这里Bean的名字必须是要以/开头 不然不知道根据什么路径来映射到此方法

@Component("/test")
public class ZhouyuBeanNameController implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {System.out.println("hushang");return new ModelAndView();}}



实现了HttpRequestHandler接口的Bean对象

@Component("/test")public class ZhouyuBeanNameController implements HttpRequestHandler {@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("hushang");}
}



添加了@RequestMapping注解的方法

@RequestMapping
@Component
public class ZhouyuController {@RequestMapping(method = RequestMethod.GET, path = "/test")@ResponseBodypublic String test(String username) {return "hushang";}
}



一个HandlerFunction对象(以下代码中有两个):

@ComponentScan("com.hushang")
@Configuration
public class AppConfig {@Beanpublic RouterFunction<ServerResponse> person() {return route().GET("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello GET")).POST("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello POST")).build();}
}



HandlerMapper

HandlerMapper就是根据请求路径path去找到Handler,保存的就是路径和Handler之间的映射关系,可以理解为一个Map<path, Handler>

因为Handler有四种,所以SpringMVC中也有不同的HandlerMapper去查找不同的Handler。在SpringMVC中有一个DespatcherServlet.properties文件中有保存,SpringMVC会读取此文件,将其中的HandlerMapper都取出来并遍历,再通过createBean()方法进行创建化各个HandlerMapper,因为是bean,所以在创建过程中会经过BeanPostProcessor去找各个负责的Handler

  • BeanNameUrlHandlerMapping:负责Controller接口和HttpRequestHandler接口
  • RequestMappingHandlerMapping:负责@RequestMapper注解的方法
  • RouterFunctionMapping:负责RouterFunction对象

这些HandlerMapper是Bean对象,所以也有Bean的生命周期,RequestMappingHandlerMapping是在afterPropertiesSet()方法中去找Handler的。



BeanNameUrlHandlerMapping的寻找流程:

  1. 找出Spring容器中所有的BeanName

  2. 判断BeanName是不是以 / 开头

  3. 如果是,则把它当成一个Handler,并把beanName作为Key,Bean对象作为Value存入HandlerMapper中

  4. HandlerMapper就是一个Map



RequestMappingHandlerMapping的寻找流程:

  1. 找出Spring容器中所有的BeanType

  2. 判断BeanType是否有@Controller注解或@RequestMapping注解

  3. 判断成功则继续找加@RequestMapping注解的method

  4. 并解析@RequestMapping注解中的内容,比如method、path封装为一个RequestMappingInfo对象

  5. 最后把RequestMappingInfo对象作为Key,Method对象封装为HandlerMapper对象后作为value,存入registry中

    先通过path找到RequestMappingInfo对象,进行注解一些信息的匹配,比如请求方式是否满足,在通过RequestMappingInfo作为key再去拿到具体要执行的Method

  6. registry就是一个Map

RouterFunctionMapping的寻找流程会有些区别,但是大体是差不多的,相当于是一个path对应一个 HandlerFunction。



各个HandlerMapping除开负责寻找Handler并记录映射关系之外,自然还需要根据请求路径找到对应的Handler,在源码中这三个HandlerMapping有一个共同的父类AbstractHandlerMapping

AbstractHandlerMapping实现了HandlerMapping接口,并实现了getHandler(HttpServletRequest request)方法。

在这里插入图片描述



AbstractHandlerMapping会负责调用子类的getHandlerInternal(HttpServletRequest request)方法从而找到请求对应的Handler,然后AbstractHandlerMapping负责将Handler和应用中所配置的 HandlerInterceptor整合成为一个HandlerExecutionChain对象。

所以寻找Handler的源码实现在各个HandlerMapping子类中的getHandlerInternal()中,根据请求路径找到Handler的过程并不复杂,因为路径和Handler的映射关系已经存在Map中了。

比较困难的点在于,当DispatcherServlet接收到一个请求时,该利用哪个HandlerMapping来寻找 Handler呢?看源码:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}

从源码中可以看出来,就是遍历,三个HandlerMapper一个一个的遍历 ,通过request对象找到了就返回

默认遍历的顺序是

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMapping

所以BeanNameUrlHandlerMapping的优先级最高,所以如果一个实现了Controller接口的Bean和@RequestMapping注解修饰的方法,他们两个的path都是/test,但是最终是Controller接口的会生效。

至此,就通过path找到了Handler,接下来就是要去执行相应的Handler了



HandlerAdapter

找到了Handler之后,接下来就该去执行了,比如执行下面这个test()

@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(String username) {return "hushang";
}



入口是在DispatchServlet类的doService()方法,再调用doDispatch(request, response)方法。

目前有四种Handler,各个Handler的执行方式也是不一样的,如下所示

  • 实现了Controller接口的Bean对象,执行的是Bean对象中的handleRequest()
  • 实现了HttpRequestHandler接口的Bean对象,执行的是Bean对象中的handleRequest()
  • 添加了@RequestMapping注解的方法,具体为一个HandlerMethod,执行的就是当前加了注解的方法
  • 一个HandlerFunction对象,执行的是HandlerFunction对象中的handle()



按照我们现在的想法,处理的方式可能是如下所示

Object handler = mappedHandler.getHandler();
if (handler instanceof Controller) {((Controller)handler).handleRequest(request, response);
} else if (handler instanceof HttpRequestHandler) {((HttpRequestHandler)handler).handleRequest(request, response);
} else if (handler instanceof HandlerMethod) {((HandlerMethod)handler).getMethod().invoke(...);
} else if (handler instanceof HandlerFunction) {((HandlerFunction)handler).handle(...);
}



但是为了扩展性,SpringMVC是采用的适配模式,把不同的Handler适配成一个HandlerAdapter,后续再去执行HandlerAdapter的handle()方法,这样就执行不同种类的Handler对应的方法了

在DespatchServlet.properties文件中也有配置,针对不同的Handler也有不同的HandlerAdapter

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapter



具体逻辑是

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {// 这里的this.handlerAdapters就是上面properties文件中配置的四种HandlerAdapterif (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {// 这里会调用各种HandlerAdapter的supports()方法,如下所示,其实就是进行类型的判断 如果满足就返回true  这里就返回当前适配器if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("如果一个HandlerAdapter都没有匹配上就会抛异常......");
}// 就拿SimpleControllerHandlerAdapter来举例,就是判断当前Handler是否实现了Controller接口,我们最常用的RequestMappingHandlerAdapter它的supports()方法在它的父类中
public boolean supports(Object handler) {return (handler instanceof Controller);
}



根据Handler适配出了对应的HandlerAdapter后,就执行具体HandlerAdapter对象的handle()方法 了,因为这四种最终都是实现了HandlerAdapter接口,所以这里也就是直接调用各种HandlerAdapter对象的handler()方法

比如SimpleControllerHandlerAdapter,就是强制转换后直接执行方法

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);
}

其他两个HandlerAdapter也一样很简单,逻辑比较复杂的就是RequestMappingHandlerAdapter中的handler()方法,它执行的是加了@RequestMapping的方法,而这种方法的写法可以是多种多样,SpringMVC需要根据方法的定义去解析Request对象,从请求中获取出对应的数据然后传递给方法,并执行。

现在的这几种HandlerAdapter也是Bean对象,就比如RequestMappingHandlerAdapter,在创建它的时候也会去调用InitializingBean.afterPropertiesSet()方法去创建各种方法参数解析器(HandlerMethodArgumentResolver)和返回值解析器(HandlerMethodReturnValueHandler)

@Override
public void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();// 参数解析器if (this.argumentResolvers == null) {// getDefaultArgumentResolvers()就会去创建很多的方法参数解析器List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}// 数据绑定器参数解析器if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}// 返回值解析器if (this.returnValueHandlers == null) {// getDefaultReturnValueHandlers()就会去创建很多的返回值解析器List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}
}



@RequestMapping方法参数解析

当SpringMVC接收到请求,并通过HandlerMapper找到对应的Method后,就该执行该方法了,不过在执行之前需要根据方法定义的形参,从请求中获取到对应的值,然后将数据传递给方法并执行。

一个HttpServletRequest通常有:

  • request parameter
  • reqeust attribute
  • reqest session
  • request header
  • request body



比如下面几个方法

// 对应的前端url中传的值
// 表示要从request parameter中获取key为username的值
public String test(String username) {return "hushang";
}// 表示要从request parameter中获取key为uname的value
public String test(@RequestParam("uname") String username) {return "hushang";
}// 对应的是后端拦截器中自己的处理,对处理后的数据 用httpServletRequest.setAttribute(name, value)存
// 表示要从request attribute中获取key为username的value
public String test(@RequestAttribute String username) {return "hushang";
}// 	表示要从request session中获取key为username的value
public String test(@SessionAttribute String username) {return "hushang";
}// 表示要从request header中获取key为username的value
public String test(@RequestHeader String username) {return "hushang";
}// 表示获取整个请求体
public String test(@RequestBody String username) {return "hushang";
}



SpringMVC在解析方法参数时,就需要看参数到底是获取请求的哪些数据,源码中是通过HandlerMethodArgumentResolver来实现的,比如:

  • RequestParamMethodArgumentResolver:负责处理@RequestParam
  • RequestHeaderMethodArgumentResolver:负责处理@RequestHeader
  • SessionAttributeMethodArgumentResolver:负责处理@SessionAttribute
  • RequestAttributeMethodArgumentResolver:负责处理@RequestAttribute
  • RequestResponseBodyMethodProcessor:负责处理@RequestBody
  • 还有很多其他的…



在判断到底需要由哪一个HandlerMethodArgumentResolver来处理时,源码中就是直接遍历,然后分别调用他们的supportsParameter()方法判断是否支持

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {// 循环遍历ArgumentResolverfor (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {// 判断各个ArgumentResolver是否支持解析当前参数,如果支持就break 并返回if (methodArgumentResolver.supportsParameter(parameter)) {result = methodArgumentResolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;
}

当这里找到HandlerMethodArgumentResolver 并返回之后,就会调用他们的resolveArgument()方法真正去获取值



@RequestMapping方法返回值解析

而方法的返回值也有不同的情况,比如返回值是String,如果加了@ResponseBody注解则将String返回给浏览器,如果没有加此注解则根据这个String找到对应的页面返回。

在SpringMVC中,是通过HandlerMethodReturnValueHandler来处理返回值的

  • RequestResponseBodyMethodProcessor:处理加了@ResponseBody注解的情况
  • ViewNameMethodReturnValueHandler:处理没有加@ResponseBody注解并且返回值类型为String的情况
  • ModelMethodProcessor:处理返回值是Model类型的情况
  • 还有很多其他的…



这里就着重介绍RequestResponseBodyMethodProcessor,因为它处理的是加了@ResponseBody注解的情况,也是我们用的最多的情况。

我们如果返回String那还好,直接返回给浏览器,如果返回的是Map或者是Object这种复杂对象该如何处理再返回给浏览器嘞?

处理这块,SpringMVC会利用HttpMessageConverter来处理,比如默认情况下,SpringMVC会有4个HttpMessageConverter:

  • ByteArrayHttpMessageConverter:处理返回值为字节数组的情况,把字节数组返回给浏览器
  • StringHttpMessageConverter:处理返回值为字符串的情况,把字符串按指定的编码序列号后返回给浏览器
  • SourceHttpMessageConverter:处理返回值为XML对象的情况,比如把DOMSource对象返回给浏览器
  • AllEncompassingFormHttpMessageConverter:处理返回值为MultiValueMap对象的情况



StringHttpMessageConverter的源码也比较简单:

protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {HttpHeaders headers = outputMessage.getHeaders();if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {headers.setAcceptCharset(getAcceptedCharsets());}Charset charset = getContentTypeCharset(headers.getContentType());StreamUtils.copy(str, charset, outputMessage.getBody());
}



先看有没有设置Content-Type,如果没有设置则取默认的,默认为ISO-8859-1,所以默认情况下返 回中文会乱码,可以通过以下来中方式来解决:

@RequestMapping(method = RequestMethod.GET, path = "/test", produces = {"application/json;charset=UTF-8"})
......
@ComponentScan("com.hushang")
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(messageConverter);}
}



不过以上四个Converter是不能处理Map对象或User对象的,所以如果返回的是Map或User对象,那么得单独配置一个Converter,比如MappingJackson2HttpMessageConverter,这个Converter比较强大,能把String、Map、User对象等等都能转化成JSON格式。

@ComponentScan("com.hushang")
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(messageConverter);}
}



文件上传流程

我们要在SpringMvc中使用文件上传,刚开始我们会配置一个bean

默认的multipartResolver是StandardServletMultipartResolver,我这里使用CommonsMultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">



入口是DispatcherServlet.doDispatch(),请求刚开始就是经过multipartResolver去判断请求参数是否是文件上传multipart类型,如果是则将所有的文件类型form表单对应的文件part保存至一个Map中

接下来等到RequestParamMethodArgumentResolver这个参数解析器去解析,根据controller方法中的参数名去从这个Map中取

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {...try {// 请求刚开始就检查请求参数是否有Multipart文件上传对象processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 进行映射mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 找到最合适的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());...// Actually invoke the handler.// 具体执行handlemv = ha.handle(processedRequest, response, mappedHandler.getHandler());...}
}

接下来是checkMultipart()方法

// 我们自己配置了一个multipartResolver的bean,各个具体的子类有各自的实现逻辑,这里就拿StandardServletMultipartResolver举例
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {// 判断是不是文件上传请求if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {if ...else {try {// 去解析文件上传请求return this.multipartResolver.resolveMultipart(request);}catch (MultipartException ex) {...}}}// If not returned before: return original request.return request;
}
// new一个StandardMultipartHttpServletRequest对象返回,注意这个类型的HttpServletRequest在下面的代码中会出现
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}// 在创建StandardMultipartHttpServletRequest对象时 构造方法中就会调用下面的方法
private void parseRequest(HttpServletRequest request) {try {Collection<Part> parts = request.getParts();this.multipartParameterNames = new LinkedHashSet<>(parts.size());// 保存结果的MapMultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());// 遍历表单的每一个part,也就是form表单的每一行请求参数for (Part part : parts) {String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);ContentDisposition disposition = ContentDisposition.parse(headerValue);String filename = disposition.getFilename();// 如果part是文件,那么就会有filename,文本类型就没有if (filename != null) {if (filename.startsWith("=?") && filename.endsWith("?=")) {filename = MimeDelegate.decode(filename);}// 文件类型的part添加进行集合files.add(part.getName(), new StandardMultipartFile(part, filename));}else {this.multipartParameterNames.add(part.getName());}}// 将找出来的这个map传给父类中的multipartFiles这个属性// 然后就轮到了RequestParamMethodArgumentResolver这个参数解析器去解析,从这个Map中取setMultipartFiles(files);}catch (Throwable ex) {handleParseFailure(ex);}
}



接下来等到RequestParamMethodArgumentResolver这个参数解析器去解析,根据controller方法中的参数名去从这个Map中取

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);if (servletRequest != null) {// 这里对文件类型的参数进行处理,去上面存入Map中找对应的文件partObject mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {return mpArg;}}Object arg = null;MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);if (multipartRequest != null) {List<MultipartFile> files = multipartRequest.getFiles(name);if (!files.isEmpty()) {arg = (files.size() == 1 ? files.get(0) : files);}}if (arg == null) {// *解析参数值 : request.getParameter方式String[] paramValues = request.getParameterValues(name);if (paramValues != null) {arg = (paramValues.length == 1 ? paramValues[0] : paramValues);}}return arg;
}
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)throws Exception {MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);boolean isMultipart = (multipartRequest != null || isMultipartContent(request));// 如果参数类型是MultipartFileif (MultipartFile.class == parameter.getNestedParameterType()) {if (!isMultipart) {return null;}if (multipartRequest == null) {multipartRequest = new StandardMultipartHttpServletRequest(request);}// 文件上传请求刚进来时就经过文件上传类型判断,将所有的上传文件form表单中的part都存入了一个Map中// 根据方法参数中的name,去Map中找return multipartRequest.getFile(name);}......
}



拦截器解析

拦截器的具体实现是我们自定义一个类,实现下面的接口,

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;public interface HandlerInterceptor {// 在执行handle之前执行,如果返回了false则表示当前请求被拦截了,不会执行后续的方法了// 即使该方法返回了false,最下面的afterCompletion()方法也会执行default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return true;}// 如果上面的preHandle()方法返回了false,或者是handle执行除了异常,该方法都不会执行// 在handle正常执行结束后执行该方法default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {}// 一次请求完成后,最终都会调用该方法default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception {}}



底层实现,入口还是DispatcherServlet.doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 检查请求参数是否有Multipart文件上传对象processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 进行映射,通过handlerMapper获取到HandlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 找到最合适的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());...// 前置拦截器// 如果前置拦截器返回了false,就不会之后handle方法了,表示当前请求被拦截了if (!mappedHandler.applyPreHandle(processedRequest, response)) {// 返回false就不进行后续处理了return;}// 具体执行handlemv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);//执行后置拦截器mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {...}// 渲染视图// 同时,这里最后还会执行拦截器的afterCompletion()方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {// 这里会执行拦截器的afterCompletion()方法triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {// 这里会执行拦截器的afterCompletion()方法triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {...}
}
// 执行前置拦截的方式
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {// 循环遍历Interceptorfor (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);// 执行拦截器的preHandle()方法// 如果前置拦截器返回了false,就不会之后handle方法了,表示当前请求被拦截了if (!interceptor.preHandle(request, response, this.handler)) {// 但是还是会执行拦截器的afterCompletion()方法triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;
}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {// 执行各个拦截器的postHandle()方法,倒序的方式执行for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);interceptor.postHandle(request, response, this.handler, mv);}
}void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {// 执行拦截器的afterCompletion()方法,这里是倒序的方式执行for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);try {interceptor.afterCompletion(request, response, this.handler, ex);}catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/36995.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PerplexityAI与《连线》杂志纠纷事件深度分析

引言 最近&#xff0c;PerplexityAI&#xff0c;这家人工智能搜索领域的新秀公司&#xff0c;因被《连线》杂志指控剽窃内容和捏造事实而陷入困境。这起事件引发了广泛关注&#xff0c;也揭示了AI技术在信息检索和内容生成领域面临的一系列挑战。本文将对该事件进行详细分析&a…

Android Lint

文章目录 Android Lint概述工作流程Lint 问题问题种类警告严重性检查规则 用命令运行 LintAndroidStudio 使用 Lint忽略 Lint 警告gradle 配置 Lint查找无用资源文件 Android Lint 概述 Lint 是 Android 提供的 代码扫描分析工具&#xff0c;它可以帮助我们发现代码结构/质量…

最小生成树拓展应用

文章目录 最小生成树拓展应用理论基础 题单1. [新的开始](https://www.acwing.com/problem/content/1148/)2. [北极通讯网络](https://www.acwing.com/problem/content/1147/)3. [走廊泼水节](https://www.acwing.com/problem/content/348/)4. [秘密的牛奶运输](https://www.ac…

用英文介绍芝加哥(1):Making Modern Chicago Part 1 Building a Boomtown

Making Modern Chicago | Part 1: Building a Boomtown Link: https://www.youtube.com/watch?vpNdX0Dm-J8Y&listPLmSQiOQJmbZ7TU39cyx7gizM9i8nOuZXy&index4 Summary Summary of Chicago’s History and Development Urban Planning and Growth Chicago, often r…

无需向量量化的自回归图像生成

摘要 https://arxiv.org/pdf/2406.11838 传统观点认为&#xff0c;用于图像生成的自回归模型通常伴随着向量量化的标记。我们观察到&#xff0c;尽管离散值空间可以方便地表示分类分布&#xff0c;但它对于自回归建模来说并不是必需的。在这项工作中&#xff0c;我们提出使用扩…

战地战地风云最强的免费加速器 2024低延迟不卡顿加速器推荐

来喽来喽&#xff0c;steam夏季促销它又来喽&#xff0c;战地风云&#xff0c;第一人称射击游戏&#xff0c;而且这次迎来了史低&#xff0c;游戏背景设定为近未来&#xff08;公元2042年&#xff09;&#xff0c;会有动态的天气系统&#xff0c;以及改善后的破坏系统。该作为《…

开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(三)

一、前言 使用 FastAPI 可以帮助我们更简单高效地部署 AI 交互业务。FastAPI 提供了快速构建 API 的能力,开发者可以轻松地定义模型需要的输入和输出格式,并编写好相应的业务逻辑。 FastAPI 的异步高性能架构,可以有效支持大量并发的预测请求,为用户提供流畅的交互体验。此外,F…

关于Mac mini 10G网口的问题

问题: 购入一个10G网口的Mac mini M2&#xff0c;将其和自己的2.5G交换机连接&#xff0c;使用共享屏幕进行远程操作的过程中出现了频率极高的卡顿&#xff0c;几乎是几秒钟卡一下&#xff0c;使用ping进行测试发现卡的时候就ping不通了。测试使用Mac mini的无线网和雷电转2.5G…

React Native 开发常见问题及注意事项

本文只是使用时积累的一些经验 开发环境 1、Android Studio 依赖项下载慢 如果发现依赖下载非常慢&#xff0c;动不动十几KB的 参考&#xff1a;加速 Android Studio 依赖项下载 也可以切换数据源 修改 android/build.gradle中的jcenter()和google() repositories {// goo…

人脑计算机技术与Neuroplatform:未来计算的革命性进展

引言 想象一下&#xff0c;你在某个清晨醒来&#xff0c;准备开始一天的工作&#xff0c;而实际上你的大脑正作为一台生物计算机的核心&#xff0c;处理着大量复杂的信息。这并非科幻电影的情节&#xff0c;而是人脑计算机技术即将带来的现实。本文将深入探讨FinalSpark公司的…

选择适合你的8款原型设计工具

随着互联网的飞速发展&#xff0c;设计行业逐渐成为近年来的热门职业。设计师们需要的掌握的技能也越来越多&#xff0c;例如海报设计、名片设计、产品设计、网页设计等。产品原型设计就是产品设计中非常重要的一个阶段&#xff0c;主要目的是帮助用户更容易了解产品设计的思路…

深度学习 —— 1.单一神经元

深度学习初级课程 1.单一神经元2.深度神经网络3.随机梯度下降法4.过拟合和欠拟合5.剪枝、批量标准化6.二分类 前言 本套课程仍为 kaggle 课程《Intro to Deep Learning》&#xff0c;仍按之前《机器学习》系列课程模式进行。前一系列《Keras入门教程》内容&#xff0c;与本系列…

【机器学习】Whisper:开源语音转文本(speech-to-text)大模型实战

目录 一、引言 二、Whisper 模型原理 2.1 模型架构 2.2 语音处理 2.3 文本处理 三、Whisper 模型实战 3.1 环境安装 3.2 模型下载 3.3 模型推理 3.4 完整代码 3.5 模型部署 四、总结 一、引言 上一篇对​​​​​​​ChatTTS文本转语音模型原理和实战进行了讲解&a…

【语义分割系列】基于cityscape的DDRNet算法

基于cityscape的DDRNet算法 前言 DDRNet是专门为实时语义分割设计的高效主干。该模型由两个深度分支组成,在这两个分支之间执行多次双边融合,并且还设计了一个新的上下文信息抽取器,名为深度聚合金字塔池模块(DAPPM),用于扩大有效的接受域,并基于低分辨率特征映射融合…

计算机网络——数据链路层(数据链路层概述及基本问题)

链路、数据链路和帧的概念 数据链路层在物理层提供服务的基础上向网络层提供服务&#xff0c;其主要作用是加强物理层传输原始比特流的功能&#xff0c;将物理层提供的可能出错的物理连接改造为逻辑上无差错的数据链路&#xff0c;使之对网络层表现为一条无差错的链路。 链路(…

Steam夏促史低游戏推荐 Steam夏促哪有游戏值得入手

steam夏季促销来袭&#xff0c;有这很多的游戏都进行打折出售&#xff0c;而且还有这很多的游戏都迎来了史低&#xff0c;简直是白送&#xff0c;很多玩家都想趁着这个时间入手自己喜欢的游戏&#xff0c;为了方便大家了解&#xff0c;下面我给大家带来steam夏季促销史低的游戏…

CO-DETR利用coco数据集训练和推理过程

CO-DETR利用coco数据集训练和推理过程&#xff0c;参考链接 Co-DETR训练自己的数据集 文章目录 前言训练过程推理过程总结 前言 环境&#xff1a;PyTorch 1.11.0 Python 3.8(ubuntu20.04) Cuda 11.3 先是在github上下载CO-DETR模型 !git clone https://github.com/Sense-X/Co…

陌陌笔试--并发打印文件内最有钱的老板的消费金额(算法)

题目&#xff1a; 算法中需要打印消费前十老板的消费金额&#xff0c;解决保留两位小数&#xff0c;并发是 JAVA 中的常考题&#xff0c; 我这里简单模拟下了数据&#xff0c;关键数据是用户id和消费金额。 解题思路&#xff1a; 1. 最简单的思路是单线程&#xff0c;偷懒…

狂神说Java之 rabbitmq高级分布式事务

分布式事务的完整架构图 案例场景分析 案例一&#xff1a;用RestTemplate演示&#xff08;不可靠生产&#xff0c;会出现问题&#xff09; 创建一个订单模块 创建一个OrderDataBaseService服务 创建一个order的service服务&#xff0c;调用saveOrder()方法 创建一个运单模块…

软件设计流程和开发流程及规范(Word)

2 过程总体描述 2.1 过程概述 2.2 过程流程图 3 过程元素描述 3.1 产品方案 3.2 产品设计 3.3 产品实现 获取方式&#xff1a;本文末个人名片直接获取。 软件资料清单列表部分文档清单&#xff1a;工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#x…