遇到一个非常奇怪的问题, 在浏览器中和PostMan中向后端发送请求, 得到的请求结果是不一样的
后端代码如下:
@Controller
@RequestMapping("/test")
@Slf4j
public class TestController {
@RequestMapping(value = "/test", method = RequestMethod.GET)
@ResponseBody
public Map testMethod(@RequestParam("id") long id) {
Map rst = new HashMap();
rst.put("a", String.valueOf(id));
rst.put("b", String.valueOf(RandomUtils.nextInt(20)));
return rst;
}
目的是希望通过jackson把返回数据格式化,
- 在浏览器中请求得到的响应是:
- 在postman中请求得到的响应是:
{
"a": "1000",
"b": "5"
}
看到两者请求返回数据的格式是不一样的, 需要对此进行探究一下
一. 首先从SpringMVC处理请求的流程说起
- Web服务启动的时候, 需要在sprinf-mvc context配置文中配置mvc:annotation-driven
SpringMVC中自带了自定义命名空间解析器
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
........
}
}
进入到AnnotationDrivenBeanDefinitionParser中会发现默认向bean容器注册了一个RequestMappingHandlerAdapter的beanDefinition
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
...........
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addRequestBodyAdvice(handlerAdapterDef);
addResponseBodyAdvice(handlerAdapterDef);
if (element.hasAttribute("ignore-default-model-on-redirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
// "ignoreDefaultModelOnRedirect" spelling is deprecated
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
if (argumentResolvers != null) {
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
if (asyncTimeout != null) {
handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
}
if (asyncExecutor != null) {
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
}
handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);
........
}
springMVC的context加载的时候会调用preInitializationSingletons, 会初始化singleton的bean对象, 这时候会初始化RequestMappingHandlerAdapter, RequestMappingHandlerAdapter的初始化中会加载一些MessageConverter.
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter
在AllEncompassingFormHttpMessageConverter中我们可以看到这样的逻辑
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
private static final boolean jackson2XmlPresent =
ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
private static final boolean gsonPresent =
ClassUtils.isPresent("com.google.gson.Gson", AllEncompassingFormHttpMessageConverter.class.getClassLoader());
可以看到, spring会判断有没有相关jar包依赖决定要不要加载对应的messageConverter
-
web服务收到外部请求, 会将该请求转发给DispatchServlet, DispatchServlet是一个实现Servlet接口的类, 是SpringMVC的前端分发servlet.
外部请求被引导到processRequest -> doService -> doDispatch, doDispatch中的逻辑相对复杂
- step1: 判断是不是multiPart类型的请求,
- step2: 根据request信息寻找对应的handler, 如果找不到handler, response返回错误信息
- step3: 寻找handlerAdapter
- step4: 判断对Last-Modified的支持
- step5: 调用拦截器的preHandle方法
- step6: 调用handler.handle方法
- step7: 执行拦截器的postHandle方法
- step8: 处理页面跳转
-
具体分下一下2所述的流程
- 外部请求被引导到processRequest函数中执行
- 有个threadLocal用于配置localContext, 虽然我暂时也不知道这个玩意是干啥的
- 调用doService
- reset local context, 恢复为之前的localContext
- doService -> doDispatch
- doDispatch
- 检查是不是multiPart的请求
- getHandler, 获得HandlerExecutionChain
- 调用getHandlerAdapter, 获得HandlerAdapter ha
- 调用handler拦截器的preProcess
- 调用ha.handler执行实际操作
- 调用handler拦截器的postProcess
- 处理dispatch结果
- 获得HandlerExecutionChain流程
- 根据系统获得的handlerMappings(一般有两个: RequestMappingHandlerMapping和BeanNameUrlHandlerMapping), 调用每个handler的getHandler方法
- 调用HandlerMapping的getHandler方法获得HandlerExecutionChain
- 获得HandlerMethod, 调用 lookUpHandlerMethod
- 先根据请求路径(绝对路径)查询直接匹配的元素, 如果每一个RequestMappingInfo的各个条件(例如method条件, header条件等)都满足, 则添加到临时容器中
- 若没有命中的, 则尝试查询所有的RequestMappingInfo最终尝试获取满足条件的RequestMappingInfo
- 如果有多个满足条件的, 则会进行排序比较找到最满足条件的
- 获得HandlerMethod, 调用 lookUpHandlerMethod
- 获得HandlerExecutionChain, 遍历adaptedInterceptors找到匹配请求路径的拦截器(拦截器可以指定excludePath和includePath, 如果请求路径match excludePath则不匹配, 若请求路径匹配任一includePath, 则匹配)
- 获得HandlerAdapter的过程: 遍历handlerAdapter, 找到第一个能support handler的handlerAdapter
- 调用HandlerAdapter的handle方法
- 首先进行一些基础校验check, 以及是否需要session等一些校验
- 调用invokeHandlerMethod
- 调用invokeAndHandle: 首先反射调用函数, 获得请求结果
- 调用returnValueHandlers对计算结果进行处理
- selectHandler, 这个实际上是遍历系统的returnvalueHandlers, 然后判断returnValueHandler是不是支持返回结果的returnType, 最终会匹配RequestResponseBodyMethodProcessor(判断逻辑是外围类是不是有ResponseBody注解或者返回值所在的method有没有ResponseBody注解)
- 调用上述返回的HandlerMethodReturnValueHandler的handleReturnValue函数, 该函数中会调用writeWithMessageConverters函数, 在这个函数中, 会根据请求的header获得mediaType, 然后不同的mediaType会匹配到不同的messageConverter上, 写的格式也不一样
- 还记得上述的RequestMappingHandlerAdapter初始化中加载的一堆messageConverter?
- 外部请求被引导到processRequest函数中执行
HandlerAdapter是什么?
在HandlerMapping返回处理请求的Controller实例后,需要一个帮助定位具体请求方法的处理类,这个类就是HandlerAdapter.
HandlerAdapter是处理器适配器,Spring MVC通过HandlerAdapter来实际调用处理函数。例如Spring MVC自动注册的AnnotationMethodHandlerAdpater.
HandlerAdapter定义了如何处理请求的策略,通过请求url、请求Method和处理器的requestMapping定义,最终确定使用处理类的哪个方法来处理请求,并检查处理类相应处理方法的参数以及相关的Annotation配置,确定如何转换需要的参数传入调用方法,并最终调用返回ModelAndView。
DispatcherServlet中根据HandlerMapping找到对应的handler method后,首先检查当前工程中注册的所有可用的handlerAdapter,根据handlerAdapter中的supports方法找到可以使用的handlerAdapter。
通过调用handlerAdapter中的handler方法来处理及准备handler method的参数及annotation(这就是spring mvc如何将request中的参数变成handle method中的输入参数的地方),最终调用实际的handler method。
最终问题明确了, 浏览器请求的时候会默认添加一个content-type的header, 而postman并不会带上, 这样会导致最终根据header匹配到不同的messageConverter, 从而返回不同的格式