Skip to content

Commit db1e881

Browse files
committed
spring-mvc: 完成
1 parent 42e2cc8 commit db1e881

File tree

6 files changed

+1292
-5
lines changed

6 files changed

+1292
-5
lines changed
50.4 KB
Loading

note/images/JstlView.jpg

50.1 KB
Loading

note/images/ModelAndView.jpg

8.5 KB
Loading

note/images/ViewResolver.jpg

8.63 KB
Loading

note/spring-mvc.md

Lines changed: 219 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)