@@ -741,4 +741,222 @@ protected ModelAndView handleInternal(HttpServletRequest request,
741741
742742### Session同步
743743
744- 可以看出,如果开启了synchronizeOnSession,那么** 同一个session的请求将会串行执行** ,这一选项默认是关闭的,当然我们可以通过注入的方式进行改变。
744+ 可以看出,如果开启了synchronizeOnSession,那么** 同一个session的请求将会串行执行** ,这一选项默认是关闭的,当然我们可以通过注入的方式进行改变。
745+
746+ ### 参数解析
747+
748+ #### 策略模式
749+
750+ 正如前面HandlerAdapter初始化-参数解析器一节提到的,HandlerAdapter内部含有一组解析器负责对各类型的参数进行解析。下面我们就常用的自定义参数和Model为例进行说明。
751+
752+ #### 自定义参数
753+
754+ 解析由RequestParamMethodArgumentResolver完成。
755+
756+ supportsParameter方法决定了一个解析器可以解析的参数类型,该解析器支持@RequestParam 标准的参数或是** 简单类型** 的参数,具体参见其注释。
757+
758+ 忽略复杂的调用关系,最核心的实现位于resolveName方法,部分源码:
759+
760+ ``` java
761+ @Override
762+ protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
763+ if (arg == null ) {
764+ String [] paramValues = request. getParameterValues(name);
765+ if (paramValues != null ) {
766+ arg = (paramValues. length == 1 ? paramValues[0 ] : paramValues);
767+ }
768+ }
769+ return arg;
770+ }
771+ ```
772+
773+ name就是方法的参数名,可以看出,参数解析** 就是根据参数名去request查找对应属性的过程** ,在这里参数类型并没有起什么作用。
774+
775+ #### Model
776+
777+ 解析由ModelMethodProcessor完成。
778+
779+ supportsParameter方法很简单:
780+
781+ ``` java
782+ @Override
783+ public boolean supportsParameter(MethodParameter parameter) {
784+ return Model . class. isAssignableFrom(parameter. getParameterType());
785+ }
786+ ```
787+
788+ 很直白了。
789+
790+ resolveArgument:
791+
792+ ``` java
793+ @Override
794+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
795+ NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
796+ return mavContainer. getModel();
797+ }
798+ ```
799+
800+ 忽略各种调用关系,** Model其实是一个BindingAwareModelMap对象,且每次请求(需要注入Model的前提下)都有一个新的该对象生成** 。类图:
801+
802+ ![ BindingAwareModelMap类图] ( images/BindingAwareModelMap.jpg )
803+
804+ #### 总结
805+
806+ 我们可以通过实现HandlerMethodArgumentResolver接口并将其注册容器的方式实现自定义参数类型的解析。
807+
808+ ### 返回值解析
809+
810+ 套路和上面是一样的,通常情况,我们返回的其实是view名,负责处理的是ViewNameMethodReturnValueHandler,
811+
812+ supportsReturnType方法:
813+
814+ ``` java
815+ @Override
816+ public boolean supportsReturnType(MethodParameter returnType) {
817+ Class<?> paramType = returnType. getParameterType();
818+ return (void . class == paramType || CharSequence . class. isAssignableFrom(paramType));
819+ }
820+ ```
821+
822+ handleReturnValue:
823+
824+ ``` java
825+ @Override
826+ public void handleReturnValue(Object returnValue, MethodParameter returnType,
827+ ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
828+ if (returnValue instanceof CharSequence ) {
829+ String viewName = returnValue. toString();
830+ mavContainer. setViewName(viewName);
831+ // 判断的依据: 是否以redirect:开头
832+ if (isRedirectViewName(viewName)) {
833+ mavContainer. setRedirectModelScenario(true );
834+ }
835+ }
836+ }
837+ ```
838+
839+ 可见这里并没有进行实际的处理,只是解析得到了最终的视图名称。
840+
841+ ### 视图渲染
842+
843+ 由DispatcherServlet的processDispatchResult方法完成,源码:
844+
845+ ``` java
846+ private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
847+ HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) {
848+ boolean errorView = false ;
849+ if (exception != null ) {
850+ // 一般不会到这个分支
851+ if (exception instanceof ModelAndViewDefiningException ) {
852+ mv = ((ModelAndViewDefiningException ) exception). getModelAndView();
853+ } else {
854+ Object handler = (mappedHandler != null ? mappedHandler. getHandler() : null );
855+ mv = processHandlerException(request, response, handler, exception);
856+ errorView = (mv != null );
857+ }
858+ }
859+ // Did the handler return a view to render?
860+ if (mv != null && ! mv. wasCleared()) {
861+ render(mv, request, response);
862+ if (errorView) {
863+ WebUtils . clearErrorRequestAttributes(request);
864+ }
865+ }
866+ }
867+ ```
868+
869+ 可以看出,处理** 根据是否抛出异常分为了两种情况** 。
870+
871+ 如果抛出了异常,那么processHandlerException方法将会遍历所有的HandlerExceptionResolver实例,默认有哪些参考MVC初始化-HandlerExceptionResolver检查一节。默认的处理器用于改变响应状态码、调用标注了@ExceptionHandler 的bean进行处理,如果没有@ExceptionHandler 的bean或是不能处理此类异常,那么就会导致ModelAndView始终为null,最终Spring MVC将异常向上抛给Tomcat,然后Tomcat就会把堆栈打印出来。
872+
873+ 如果我们想将其定向到指定的错误页面,可以这样配置:
874+
875+ ``` xml
876+ <bean class =" org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" >
877+ <property name =" defaultErrorView" value =" error" ></property >
878+ </bean >
879+ ```
880+
881+ 此处理器会返回一个非空的ModelAndView。
882+
883+ #### ModelAndView
884+
885+ 回过头来看一下这到底是个什么东西。类图:
886+
887+ ![ ModelAndView类图] ( images/ModelAndView.jpg )
888+
889+ 很直白。
890+
891+ 怎么生成的。RequestMappingHandlerAdapter.getModelAndView相关源码:
892+
893+ ``` java
894+ ModelMap model = mavContainer. getModel();
895+ ModelAndView mav = new ModelAndView (mavContainer. getViewName(), model, mavContainer. getStatus());
896+ ```
897+
898+ #### 渲染
899+
900+ DispatcherServlet.render简略版源码:
901+
902+ ``` java
903+ protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) {
904+ Locale locale = this . localeResolver. resolveLocale(request);
905+ response. setLocale(locale);
906+ View view;
907+ // 判断依据: 是否是String类型
908+ if (mv. isReference()) {
909+ // We need to resolve the view name.
910+ view = resolveViewName(mv. getViewName(), mv. getModelInternal(), locale, request);
911+ } else {
912+ // No need to lookup: the ModelAndView object contains the actual View object.
913+ view = mv. getView();
914+ }
915+ if (mv. getStatus() != null ) {
916+ response. setStatus(mv. getStatus(). value());
917+ }
918+ view. render(mv. getModelInternal(), request, response);
919+ }
920+ ```
921+
922+ resolveViewName方法将会遍历所有的ViewResolver bean,只要有一个解析的结果(View)不为空,即停止遍历。根据MVC初始化-ViewResolver检查一节和我们的配置文件可知,容器中有两个ViewResolver ,分别是: InternalResourceViewResolver和UrlBasedViewResolver。
923+
924+ ##### ViewResolver
925+
926+ 类图(忽略实现类):
927+
928+ ![ ViewResolver类图] ( images/ViewResolver.jpg )
929+
930+ resolveViewName方法的源码不再贴出,其实只做了一件事: 用反射创建并初始化我们指定的View,根据我们的配置,就是JstlView。
931+
932+ ##### View
933+
934+ 类图:
935+
936+ ![ JstlView类图] ( images/JstlView.jpg )
937+
938+ 渲染的核心逻辑位于InternalResourceView.renderMergedOutputModel,简略版源码:
939+
940+ ``` java
941+ @Override
942+ protected void renderMergedOutputModel(
943+ Map<String , Object > model, HttpServletRequest request, HttpServletResponse response) {
944+ // 将Model中的属性设置的request中
945+ exposeModelAsRequestAttributes(model, request);
946+ // 获取资源(jsp)路径
947+ String dispatcherPath = prepareForRendering(request, response);
948+ // Obtain a RequestDispatcher for the target resource (typically a JSP).
949+ RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
950+ // If already included or response already committed, perform include, else forward.
951+ if (useInclude(request, response)) {
952+ response. setContentType(getContentType());
953+ rd. include(request, response);
954+ } else {
955+ // Note: The forwarded resource is supposed to determine the content type itself.
956+ rd. forward(request, response);
957+ }
958+ }
959+ ```
960+
961+ 可以看出,对jsp来说,所谓的渲染其实就是** 将Model中的属性设置到Request,再利用原生Servlet RequestDispatcher API进行转发的过程** 。
962+
0 commit comments