2.3 请求处理
RequestMappingHandlerAdapter.handleInternal:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod){
ModelAndView mav;
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
Session 同步
可以看出,如果开启了 synchronizeOnSession,那么 同一个 session 的请求将会串行执行 ,这一选项默认是关闭的,当然我们可以通过注入的方式进行改变。
参数解析
策略模式
正如前面 HandlerAdapter 初始化-参数解析器一节提到的,HandlerAdapter 内部含有一组解析器负责对各类型的参数进行解析。下面我们就常用的自定义参数和 Model 为例进行说明。
自定义参数
解析由 RequestParamMethodArgumentResolver 完成。
supportsParameter 方法决定了一个解析器可以解析的参数类型,该解析器支持 @RequestParam 标准的参数或是 简单类型 的参数,具体参见其注释。为什么此解析器可以同时解析 @RequestParam 注解和普通参数呢?玄机在于 RequestMappingHandlerAdapter 方法在初始化参数解析器时其实初始化了 两个 RequestMappingHandlerAdapter 对象 ,getDefaultArgumentResolvers 方法相关源码:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
}
useDefaultResolution 参数用于启动对常规类型参数的解析,这里的常规类型指的又是什么呢?
实际上由 BeanUtils.isSimpleProperty 方法决定:
public static boolean isSimpleProperty(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
}
public static boolean isSimpleValueType(Class<?> clazz) {
return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() ||
CharSequence.class.isAssignableFrom(clazz) ||
Number.class.isAssignableFrom(clazz) ||
Date.class.isAssignableFrom(clazz) ||
URI.class == clazz || URL.class == clazz ||
Locale.class == clazz || Class.class == clazz);
}
忽略复杂的调用关系,最核心的实现位于 resolveName 方法,部分源码:
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
name 就是方法的参数名,可以看出,参数解析 就是根据参数名去 request 查找对应属性的过程 ,在这里参数类型并没有起什么作用。
参数名是从哪里来的
方法名获取的入口位于 RequestParamMethodArgumentResolver 的 resolveArgument 方法:
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
}
getNamedValueInfo 方法最终完成对 MethodParameter 的 getParameterName 方法的调用:
public String getParameterName() {
ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
if (discoverer != null) {
String[] parameterNames = (this.method != null ?
discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
if (parameterNames != null) {
this.parameterName = parameterNames[this.parameterIndex];
}
this.parameterNameDiscoverer = null;
}
return this.parameterName;
}
显然,参数名的获取由接口 ParameterNameDiscoverer 完成:

默认采用 DefaultParameterNameDiscoverer,但此类其实相当于 StandardReflectionParameterNameDiscoverer 和 LocalVariableTableParameterNameDiscoverer 的组合,且前者先于后者进行解析。
StandardReflectionParameterNameDiscoverer.getParameterNames:
@Override
public String[] getParameterNames(Method method) {
Parameter[] parameters = method.getParameters();
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
if (!param.isNamePresent()) {
return null;
}
parameterNames[i] = param.getName();
}
return parameterNames;
}
此类被注解 UsesJava8 标注,其原理就是利用的 jdk8 的-parameters 编译参数,只有在加上此选项的情况下才能用反射的方法获得真实的参数名,所以一般情况下 StandardReflectionParameterNameDiscoverer 是无法成功获取参数名的。
LocalVariableTableParameterNameDiscoverer 利用了 ASM 直接访问 class 文件中的本地变量表来得到变量名,下面是使用 javap -verbose 命令得到的本地变量表示例:
但是默认情况下 javac compiler 是不生成本地变量表这种调试信息的,需要加 -g 参数才可以,那为什么在我们的测试 Controller 中却可以获得呢,玄机就在于 idea 的下列设置:
取消这项设置的勾选再次运行程序便出问题了:
Model
解析由 ModelMethodProcessor 完成。
supportsParameter 方法很简单:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
很直白了。
resolveArgument:
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return mavContainer.getModel();
}
忽略各种调用关系, Model 其实是一个 BindingAwareModelMap 对象,且每次请求(需要注入 Model 的前提下) 都有一个新的该对象生成 。类图:

总结
- 我们可以通过实现 HandlerMethodArgumentResolver 接口并将其注册容器的方式实现自定义参数类型的解析。
- 为了防止出现参数名获取不到的问题,应优先使用 @RequestParam 注解直接声明需要的参数名称。
返回值解析
套路和上面是一样的,通常情况,我们返回的其实是 view 名,负责处理的是 ViewNameMethodReturnValueHandler,
supportsReturnType 方法:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
handleReturnValue:
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
// 判断的依据: 是否以 redirect:开头
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
}
可见这里并没有进行实际的处理,只是解析得到了最终的视图名称。
视图渲染
由 DispatcherServlet 的 processDispatchResult 方法完成,源码:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) {
boolean errorView = false;
if (exception != null) {
//一般不会到这个分支
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
}
可以看出,处理 根据是否抛出异常分为了两种情况 。
如果抛出了异常,那么 processHandlerException 方法将会遍历所有的 HandlerExceptionResolver 实例,默认有哪些参考 MVC 初始化-HandlerExceptionResolver 检查一节。默认的处理器用于改变响应状态码、调用标注了 @ExceptionHandler 的 bean 进行处理,如果没有 @ExceptionHandler 的 bean 或是不能处理此类异常,那么就会导致 ModelAndView 始终为 null,最终 Spring MVC 将异常向上抛给 Tomcat,然后 Tomcat 就会把堆栈打印出来。
如果我们想将其定向到指定的错误页面,可以这样配置:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="error"></property>
</bean>
此处理器会返回一个非空的 ModelAndView。
ModelAndView
回过头来看一下这到底是个什么东西。类图:

很直白。
怎么生成的。RequestMappingHandlerAdapter.getModelAndView 相关源码:
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
渲染
DispatcherServlet.render 简略版源码:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) {
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
//判断依据: 是否是 String 类型
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
} else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
}
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
resolveViewName 方法将会遍历所有的 ViewResolver bean,只要有一个解析的结果(View) 不为空,即停止遍历。根据 MVC 初始化-ViewResolver 检查一节和我们的配置文件可知,容器中有两个 ViewResolver ,分别是: InternalResourceViewResolver 和 UrlBasedViewResolver。
ViewResolver
类图(忽略实现类):

resolveViewName 方法的源码不再贴出,其实只做了一件事: 用反射创建并初始化我们指定的 View,根据我们的配置,就是 JstlView。
View
类图:

渲染的核心逻辑位于 InternalResourceView.renderMergedOutputModel,简略版源码:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
// 将 Model 中的属性设置的 request 中
exposeModelAsRequestAttributes(model, request);
// 获取资源(jsp) 路径
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
rd.include(request, response);
} else {
// Note: The forwarded resource is supposed to determine the content type itself.
rd.forward(request, response);
}
}
可以看出,对 jsp 来说,所谓的渲染其实就是 将 Model 中的属性设置到 Request,再利用原生 Servlet RequestDispatcher API 进行转发的过程 。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论