三歪肝出了期待已久的SpringMVC(二)

简介: 这篇SpringMVC被催了很久了,这阵子由于做整合系统的事,所以非常非常地忙。这周末早早就回了公司肝这篇文章了。

再进去getHandler里边看看呗,里边又有几层,我们最后可以看到它根据路径去匹配,走到了lookupHandlerMethod这么一个方法

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  List<Match> matches = new ArrayList<Match>();
   // 获取路径
  List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
   // 对匹配的排序,找到最佳匹配的
  if (!matches.isEmpty()) {
   Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
   Collections.sort(matches, comparator);
   if (logger.isTraceEnabled()) {
    logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
      lookupPath + "] : " + matches);
   }
   Match bestMatch = matches.get(0);
   if (matches.size() > 1) {
    if (CorsUtils.isPreFlightRequest(request)) {
     return PREFLIGHT_AMBIGUOUS_MATCH;
    }
    Match secondBestMatch = matches.get(1);
    if (comparator.compare(bestMatch, secondBestMatch) == 0) {
     Method m1 = bestMatch.handlerMethod.getMethod();
     Method m2 = secondBestMatch.handlerMethod.getMethod();
     throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
       request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
    }
   }
   handleMatch(bestMatch.mapping, lookupPath, request);
   return bestMatch.handlerMethod;
  }
  else {
   return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
  }
 }

找拦截器大概也是上面的一个过程,于是我们就可以顺利拿到HandlerExecutionChain了,找到HandlerExecutionChain后,我们是先去拿对应的HandlerAdaptor。我们也去看看里边做了什么:

// 遍历HandlerAdapter实例,找到个合适的返回
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  for (HandlerAdapter ha : this.handlerAdapters) {
   if (ha.supports(handler)) {
    return ha;
   }
  }
 }

我们看一个常用HandlerAdapter实例RequestMappingHandlerAdapter,会发现他会初始化很多的参数解析器,其实我们经常用的@ResponseBody解析器就被内置在里边:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
  List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
  resolvers.add(new MatrixVariableMethodArgumentResolver());
  resolvers.add(new MatrixVariableMapMethodArgumentResolver());
  resolvers.add(new ServletModelAttributeMethodProcessor(false));
   // ResponseBody Requestbody解析器
  resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
  resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), t
  // 等等
  return resolvers;
 }

得到HandlerAdaptor后,随之而行的就是拦截器的前置处理,然后就是真实的mv = ha.handle(processedRequest, response, mappedHandler.getHandler())

这里边嵌套了好几层,我就不一一贴代码了,我们会进入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) || hasResponseStatus() || mavContainer.isRequestHandled()) {
    mavContainer.setRequestHandled(true);
    return;
   }
  }
   //.. 
  mavContainer.setRequestHandled(false);
  try {
      // 处理返回值
   this.returnValueHandlers.handleReturnValue(
     returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
  }
 }

处理请求的方法我们进去看看invokeForRequest

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
   Object... providedArgs) throws Exception {
   // 得到参数
  Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   // 调用方法
  Object returnValue = doInvoke(args);
  if (logger.isTraceEnabled()) {
   logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
  }
  return returnValue;
 }

我们看看它是怎么处理参数的,getMethodArgumentValues方法进去看看:

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
   Object... providedArgs) throws Exception {
   // 得到参数
  MethodParameter[] parameters = getMethodParameters();
  Object[] args = new Object[parameters.length];
  for (int i = 0; i < parameters.length; i++) {
   MethodParameter parameter = parameters[i];
   parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
   GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
   args[i] = resolveProvidedArgument(parameter, providedArgs);
   if (args[i] != null) {
    continue;
   }
      // 找到适配的参数解析器
   if (this.argumentResolvers.supportsParameter(parameter)) {
    try {
     args[i] = this.argumentResolvers.resolveArgument(
       parameter, mavContainer, request, this.dataBinderFactory);
     continue;
    }
    //.....
  }
  return args;
 }

这些参数解析器实际上在HandlerAdaptor内置的那些,这里不好放代码,所以我截个图吧:

63.jpg

针对于RequestResponseBodyMethodProcessor解析器我们看看里边做了什么:

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
   NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    // 通过Converters对参数转换
  Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
  String name = Conventions.getVariableNameForParameter(parameter);
  WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
  // ...
  mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
  return arg;
 }

再进去readWithMessageConverters里边看看:

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param,
   Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
  // ...处理请求头
  try {
   inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);
      // HttpMessageConverter实例去对参数转换
   for (HttpMessageConverter<?> converter : this.messageConverters) {
    Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
    if (converter instanceof GenericHttpMessageConverter) {
     GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
     if (genericConverter.canRead(targetType, contextClass, contentType)) {
      if (logger.isDebugEnabled()) {
       logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
      }
      if (inputMessage.getBody() != null) {
       inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
       body = genericConverter.read(targetType, contextClass, inputMessage);
       body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
      }
      else {
       body = null;
       body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType);
      }
      break;
     }
    }
    //...各种判断
  return body;
 }

看到这里,有没有看不懂,想要退出的感觉了??别慌,三歪带你们看看这份熟悉的配置:

<!-- 启动JSON返回格式 -->
 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
  <property name="messageConverters">
   <list>
    <ref bean="jacksonMessageConverter" />
   </list>
  </property>
 </bean>
 <bean id="jacksonMessageConverter"
  class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
  <property name="supportedMediaTypes">
   <list>
    <value>text/html;charset=UTF-8</value>
    <value>application/json;charset=UTF-8</value>
    <value>application/x-www-form-urlencoded;charset=UTF-8</value>
   </list>
  </property>
  <property name="objectMapper" ref="jacksonObjectMapper" />
 </bean>
 <bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />

我们在SpringMVC想要使用@ResponseBody返回JSON格式都会在配置文件上配置上面的配置,RequestMappingHandlerAdapter这个适配器就是上面所说的那个,内置了RequestResponseBodyMethodProcessor解析器,然后MappingJackson2HttpMessageConverter实际上就是HttpMessageConverter接口的实例

62.jpg

然后在返回的时候也经过HttpMessageConverter去将参数转换后,写给HTTP响应报文。转换的流程大致如图所示:

61.jpg

视图解析器后面就不贴了,大概的流程就如上面的源码,我再画个图来加深一下理解吧:

60.jpg

最后

SpringMVC我们使用的时候非常简便,在内部实际上帮我们做了很多(有各种的HandlerAdaptor),SpringMVC的请求流程面试的时候还是面得很多的,还是可以看看源码它帮我们做了什么,过一遍可能会发现自己能看懂以前的配置了。

现在已经工作有一段时间了,为什么还来写SpringMVC呢,原因有以下几个:

  • 我是一个对排版有追求的人,如果早期关注我的同学可能会发现,我的GitHub、文章导航的read.me会经常更换。现在的GitHub导航也不合我心意了(太长了),并且早期的文章,说实话排版也不太行,我决定重新搞一波。
  • 我的文章会分发好几个平台,但文章发完了可能就没人看了,并且图床很可能因为平台的防盗链就挂掉了。又因为有很多的读者问我:”你能不能把你的文章转成PDF啊?“
  • 我写过很多系列级的文章,这些文章就几乎不会有太大的改动了,就非常适合把它们给”持久化“。

基于上面的原因,我决定把我的系列文章汇总成一个PDF/HTML/WORD/epub文档。

目录
相关文章
|
SQL 数据库
20、绕过去除and、or、union select、空格的sql注入
20、绕过去除and、or、union select、空格的sql注入
310 0
|
算法
数学建模-------误差来源以及误差分析
数学建模-------误差来源以及误差分析
|
7月前
|
资源调度 前端开发 算法
鸿蒙OS架构设计探秘:从分层设计到多端部署
本文深入探讨了鸿蒙OS的架构设计,从独特的“1+8+N”分层架构到模块化设计,再到智慧分发和多端部署能力。分层架构让系统更灵活,模块化设计通过Ability机制实现跨设备一致性,智慧分发优化资源调度,多端部署提升开发效率。作者结合实际代码示例,分享了开发中的实践经验,并指出生态建设是未来的关键挑战。作为国产操作系统的代表,鸿蒙的发展值得每一位开发者关注与支持。
|
9月前
|
机器学习/深度学习 人工智能 自然语言处理
Agent Laboratory:AI自动撰写论文,AMD开源自动完成科研全流程的多智能体框架
Agent Laboratory 是由 AMD 和约翰·霍普金斯大学联合推出的自主科研框架,基于大型语言模型,能够加速科学发现、降低成本并提高研究质量。
669 23
Agent Laboratory:AI自动撰写论文,AMD开源自动完成科研全流程的多智能体框架
|
9月前
|
存储 SQL 缓存
PolarDB-X 在 ClickBench 数据集的优化实践
本文介绍了 PolarDB-X 在 ClickBench 数据集上的优化实践,PolarDB-X 通过增加优化器规则、优化执行器层面的 DISTINCT 和自适应两阶段 AGG、MPP 压缩等手段,显著提升了在 ClickBench 上的性能表现,达到了业内领先水平。
|
7月前
|
测试技术 数据库 数据安全/隐私保护
【YashanDB知识库】sys登录提示账户被锁,怎么处理?
【YashanDB知识库】sys登录提示账户被锁,怎么处理?
|
10月前
|
存储 弹性计算 分布式计算
阿里云服务器租用价格:包年包月收费标准与月付、1年、3年活动价格
租用阿里云服务器3个月、6个月、1年、3年多少钱?云服务器收费标准是怎样的?根据目前的价格信息,阿里云特价云服务器价格38元、99元、199元、298元,本文分享阿里云服务器最新的租用费用,包括包年包月的收费标准和月付3个月和6个月以及1年、3年活动价格表。
|
Prometheus 监控 Cloud Native
Gin 集成 prometheus 客户端实现注册和暴露指标
Gin 集成 prometheus 客户端实现注册和暴露指标
426 0
|
Go C语言
golang的类型转换
【9月更文挑战第28天】本文介绍了Go语言中的基本数据类型转换,包括数值类型之间的转换及字符串与数值类型的互转,提供了具体代码示例说明如何使用如`float64(a)`和`strconv.Atoi`等方法。同时,文章还讲解了接口类型转换,包括类型断言和类型开关的使用方法,并展示了如何在运行时获取具体类型。最后,提到了指针类型转换的注意事项及其应用场景。
193 7
|
存储 人工智能 移动开发
突发!Runway一夜删库跑路,HuggingFace已清空
活久见,Runway 一夜间清空 HuggingFace 和 GitHub,直接跑路了?很多人猜测,此事与版权纠纷有关,这就翻出了 Runway 和 Stability AI 之间的一段陈年旧案。