上一级页面:ssm-spring-boot速成学习索引
前言
33、请求处理-【源码分析】-Servlet API参数解析原理
WebRequest
ServletRequest
MultipartRequest
HttpSession
javax.servlet.http.PushBuilder
Principal
InputStream
Reader
HttpMethod
Locale
TimeZone
ZoneId
通过上一节的调试方法得到:
ServletRequestMethodArgumentResolver 用来处理以上的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @Nullable private static Class<?> pushBuilder; static { try { pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder" , ServletRequestMethodArgumentResolver.class.getClassLoader()); } catch (ClassNotFoundException ex) { pushBuilder = null ; } } @Override public boolean supportsParameter (MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType); } @Override public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); if (WebRequest.class.isAssignableFrom(paramType)) { if (!paramType.isInstance(webRequest)) { throw new IllegalStateException ( "Current request is not of type [" + paramType.getName() + "]: " + webRequest); } return webRequest; } if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { return resolveNativeRequest(webRequest, paramType); } return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class)); } private <T> T resolveNativeRequest (NativeWebRequest webRequest, Class<T> requiredType) { T nativeRequest = webRequest.getNativeRequest(requiredType); if (nativeRequest == null ) { throw new IllegalStateException ( "Current request is not of type [" + requiredType.getName() + "]: " + webRequest); } return nativeRequest; } @Nullable private Object resolveArgument (Class<?> paramType, HttpServletRequest request) throws IOException { if (HttpSession.class.isAssignableFrom(paramType)) { HttpSession session = request.getSession(); if (session != null && !paramType.isInstance(session)) { throw new IllegalStateException ( "Current session is not of type [" + paramType.getName() + "]: " + session); } return session; } else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) { return PushBuilderDelegate.resolvePushBuilder(request, paramType); } else if (InputStream.class.isAssignableFrom(paramType)) { InputStream inputStream = request.getInputStream(); if (inputStream != null && !paramType.isInstance(inputStream)) { throw new IllegalStateException ( "Request input stream is not of type [" + paramType.getName() + "]: " + inputStream); } return inputStream; } else if (Reader.class.isAssignableFrom(paramType)) { Reader reader = request.getReader(); if (reader != null && !paramType.isInstance(reader)) { throw new IllegalStateException ( "Request body reader is not of type [" + paramType.getName() + "]: " + reader); } return reader; } else if (Principal.class.isAssignableFrom(paramType)) { Principal userPrincipal = request.getUserPrincipal(); if (userPrincipal != null && !paramType.isInstance(userPrincipal)) { throw new IllegalStateException ( "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal); } return userPrincipal; } else if (HttpMethod.class == paramType) { return HttpMethod.resolve(request.getMethod()); } else if (Locale.class == paramType) { return RequestContextUtils.getLocale(request); } else if (TimeZone.class == paramType) { TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone : TimeZone.getDefault()); } else if (ZoneId.class == paramType) { TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault()); } throw new UnsupportedOperationException ("Unknown parameter type: " + paramType.getName()); } private static class PushBuilderDelegate { @Nullable public static Object resolvePushBuilder (HttpServletRequest request, Class<?> paramType) { PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null && !paramType.isInstance(pushBuilder)) { throw new IllegalStateException ( "Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder); } return pushBuilder; } } }
用例:
1 2 3 4 5 6 7 8 9 10 11 @Controller public class RequestController { @GetMapping("/goto") public String goToPage (HttpServletRequest request) { request.setAttribute("msg" ,"成功了..." ); request.setAttribute("code" ,200 ); return "forward:/success" ; } }
34、请求处理-【源码分析】-Model、Map原理
复杂参数:
Map
Model(map、model里面的数据会被放在request的请求域 request.setAttribute)
Errors/BindingResult
RedirectAttributes( 重定向携带数据)
ServletResponse(响应response)
SessionStatus
UriComponentsBuilder
ServletUriComponentsBuilder
用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @GetMapping("/params") public String testParam (Map<String,Object> map, Model model, HttpServletRequest request, HttpServletResponse response) { map.put("hello" ,"world666" ); model.addAttribute("world" ,"hello666" ); request.setAttribute("message" ,"HelloWorld" ); Cookie cookie = new Cookie ("c1" ,"v1" ); response.addCookie(cookie); return "forward:/success" ; } @ResponseBody @GetMapping("/success") public Map success (@RequestAttribute(value = "msg",required = false) String msg, @RequestAttribute(value = "code",required = false) Integer code, HttpServletRequest request) { Object msg1 = request.getAttribute("msg" ); Map<String,Object> map = new HashMap <>(); Object hello = request.getAttribute("hello" ); Object world = request.getAttribute("world" ); Object message = request.getAttribute("message" ); map.put("reqMethod_msg" ,msg1); map.put("annotation_msg" ,msg); map.put("hello" ,hello); map.put("world" ,world); map.put("message" ,message); return map; }
上面三位都是可以给request域中放数据,用request.getAttribute()
获取
接下来我们看看,Map<String,Object> map
与Model model
用什么参数处理器。
Map<String,Object> map
参数用MapMethodProcessor
处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MapMethodProcessor implements HandlerMethodArgumentResolver , HandlerMethodReturnValueHandler { @Override public boolean supportsParameter (MethodParameter parameter) { return (Map.class.isAssignableFrom(parameter.getParameterType()) && parameter.getParameterAnnotations().length == 0 ); } @Override @Nullable public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null , "ModelAndViewContainer is required for model exposure" ); return mavContainer.getModel(); } ... }
注意到
1 return mavContainer.getModel();
mavContainer.getModel()
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class ModelAndViewContainer { ... private final ModelMap defaultModel = new BindingAwareModelMap (); @Nullable private ModelMap redirectModel; ... public ModelMap getModel () { if (useDefaultModel()) { return this .defaultModel; } else { if (this .redirectModel == null ) { this .redirectModel = new ModelMap (); } return this .redirectModel; } } private boolean useDefaultModel () { return (!this .redirectModelScenario || (this .redirectModel == null && !this .ignoreDefaultModelOnRedirect)); } ... }
注意到
1 2 3 if (useDefaultModel()) { return this .defaultModel; }
查看this.defaultModel
的类型是BindingAwareModelMap
,进一步探究发现底层是
1 2 3 public class ExtendedModelMap extends ModelMap implements Model {}
即BindingAwareModelMap
既是Model也是Map
Model model
用ModelMethodProcessor
处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ModelMethodProcessor implements HandlerMethodArgumentResolver , HandlerMethodReturnValueHandler { @Override public boolean supportsParameter (MethodParameter parameter) { return Model.class.isAssignableFrom(parameter.getParameterType()); } @Override @Nullable public Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null , "ModelAndViewContainer is required for model exposure" ); return mavContainer.getModel(); } ... }
也是return mavContainer.getModel();
。这里跟MapMethodProcessor
方法内的处理办法一致。
最终也是使用BindingAwareModelMap
类型,BindingAwareModelMap
既是Model也是Map
查看BindingAwareModelMap
的继承链:
来到InvocableHandlerMethod
类的getMethodArgumentValues
方法
查看args,可以看到Map和Model实际上是用同一个对象来处理的
一直“恢复程序执行”,直到来到ServletInvocableHandlerMethod#invokeAndHandle
方法
查看mavContatiner的值,可以看到Map和Model实际上是用同一个对象来处理的,处理结果是图中的“hello”和"world"
目标方法执行完成
目标方法执行完成,将所有的数据都放在ModelAndViewContainer
(mavContaioner).
mavContainer包含要去的地址url,还包含Model的数据
将数据填充到Model And View
步入getModelAndView
方法
这个方法首先更新Model数据,
然后将数据填充到ModelAndView
1 2 3 4 5 6 private ModelAndView getModelAndView (ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { ModelAndView mav = new ModelAndView (mavContainer.getViewName(), model, mavContainer.getStatus());
如果你的数据是重定向到新网页的,这个方法还会帮你把数据绑定到新网页的请求上下文(RequestContex),以完成旧网页request携带的内容转移到新网页request上。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Nullable private ModelAndView getModelAndView (ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { if (model instanceof RedirectAttributes redirectAttributes) { Map<String, ?> flashAttributes = redirectAttributes.getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request != null ) { RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } }
定位processDispatchResult
方法
步出,回到org.springframework.web.servlet.DispatcherServlet#doDispatch
,即DispatcherServlet类的doDispatch
方法,
1 2 3 4 5 6 7 8 9 10 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); mappedHandler.applyPostHandle(processedRequest, response, mv); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
现在ha.handle
执行完成
1 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
方法继续往下执行,来到核心语句,这里执行拦截器的,后文会进行解析
1 mappedHandler.applyPostHandle(processedRequest, response, mv);
继续步过,来到核心语句,处理分发结果。这是下一小节的解析重点。
1 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
如何获取Map<String,Object> map
与Model model
的值
看看 Map<String,Object> map
与Model model
值是如何做到用request.getAttribute()
获取的。
根据上一小节的内容,所有的数据都放在 ModelAndView ,它包含要去的页面地址View,还包含Model数据。
回顾上一小节的末尾,先看ModelAndView 接下来是如何处理的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class DispatcherServlet extends FrameworkServlet { ... protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { ... try { ModelAndView mv = null ; ... mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ... } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException ("Handler dispatch failed" , err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } ... }
它包含这三个核心语句
1 2 3 4 5 6 7 8 9 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); mappedHandler.applyPostHandle(processedRequest, response, mv); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
步入processDispatchResult
方法,方法首先获取view的name,然后对view进行render,
1 2 3 4 5 6 7 8 9 10 11 12 private void processDispatchResult (HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } }
其中的核心语句:
1 render(mv, request, response);
步入render
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected void render (ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { View view; String viewName = mv.getViewName(); if (viewName != null ) { 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() + "'" ); } } }
注意到核心语句:
1 2 3 view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
resolveViewName
方法进入视图解析器的流程,以后会分析。
继续看render
方法的后半段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected void render (ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { try { if (mv.getStatus() != null ) { request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus()); response.setStatus(mv.getStatus().value()); } view.render(mv.getModelInternal(), request, response); } }
注意到核心语句
1 view.render(mv.getModelInternal(), request, response);
步入view.render
方法,来到InternalResourceView#render
方法
这里的view
属为InternalResourceView
类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class InternalResourceView extends AbstractUrlBasedView { @Override public void render (@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
注意到核心语句:
1 2 3 4 5 6 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
步入createMergedOutputModel
方法,这个方法将Model数据转移到转移到mergedModel(即HashMap中)
1 2 3 4 5 6 7 8 9 10 11 12 protected Map<String, Object> createMergedOutputModel (@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {if (model != null ) { mergedModel.putAll(model); } }
步出,回到InternalResourceView#render
方法,核心语句如下:
1 2 3 4 5 6 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
注意到核心语句:
1 2 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
步入renderMergedOutputModel
方法
1 2 3 4 5 6 7 8 9 @Override protected void renderMergedOutputModel ( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { exposeModelAsRequestAttributes(model, request); }
注意到核心语句:
1 2 3 exposeModelAsRequestAttributes(model, request);
步入exposeModelAsRequestAttributes
方法,方法遍历Model中的所有数据,将数据放在request域中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected void exposeModelAsRequestAttributes (Map<String, Object> model, HttpServletRequest request) throws Exception { model.forEach((name, value) -> { if (value != null ) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); } }
exposeModelAsRequestAttributes
方法看出,
Map<String,Object> map
,Model model
这两种类型数据可以给request域中放数据,
这个操作是在渲染view
(view.render()
)时进行的,
跳转操作发生在这之后
使用数据时,用request.getAttribute()
获取。
exposeModelAsRequestAttributes
方法是业务逻辑的最底层了,再往下是工具类。
该方法的继承关系如下:
35、请求处理-【源码分析】-自定义参数绑定原理
假设有下面的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class ParameterTestController { @PostMapping("/saveuser") public Person saveuser (Person person) { return person; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Data public class Person { private String userName; private Integer age; private Date birth; private Pet pet; } @Data public class Pet { private String name; private String age; }
封装过程用到ServletModelAttributeMethodProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor { @Override public boolean supportsParameter (MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this .annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); } @Override @Nullable public final Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { ... String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null ) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null ; BindingResult bindingResult = null ; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { ... } } if (bindingResult == null ) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null ) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException (binder.getBindingResult()); } } if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; } }
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型
36、请求处理-【源码分析】-自定义Converter原理
未来我们可以给WebDataBinder里面放自己的Converter;
下面演示将字符串“啊猫,3”
转换成Pet
对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void addFormatters (FormatterRegistry registry) { registry.addConverter(new Converter <String, Pet>() { @Override public Pet convert (String source) { if (!StringUtils.isEmpty(source)){ Pet pet = new Pet (); String[] split = source.split("," ); pet.setName(split[0 ]); pet.setAge(Integer.parseInt(split[1 ])); return pet; } return null ; } }); } }; }
SpringBootWeb开发-3
spring-boot-web-开发-3
参考、引用、致谢