一、前言

版本:

springMVC 5.0.2RELEASE

JDK1.8

前端控制器的配置:

web.xml

    <!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!--加载类路径下的配置文件-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--服务器启动时创建对象,值越小,优先级越高,越先创建对象-->
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--注意不是/*,而是,因为/*还会拦截*.jsp等请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>

springmvc.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.smday"/> <!-- 视图解析器对象 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 开启SpringMVC框架注解的支持 -->
<mvc:annotation-driven/>
<!--放行静态资源-->
<mvc:default-servlet-handler/> </beans>

二、初始化

DispatcherServlet的启动与Servlet的启动过程紧密联系,我们通过以上继承图就可以发现。

1. 容器初始化

Servlet中定义的init()方法就是其生命周期的初始化方法,接着往下走,GenericServlet并没有给出具体实现,在HttpServletBean中的init()方法给出了具体的实现:

HttpServletBean.init()方法(忽略日志)

	@Override
public final void init() throws ServletException {
//根据初始化参数设置bean属性(我们设置了contextConfigLocation,故可以获取)
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//包装DispatcherServlet
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//获取资源加载器,用以加载springMVC的配置文件
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册一个ResourceEditor
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//该方法为空实现,可以重写,初始化BeanWrapper
initBeanWrapper(bw);
//最终将init-param读取的值spirng-mvc.xml存入contextConfigLocation中
bw.setPropertyValues(pvs, true);
}
} // 让子类实现初始化
initServletBean(); }

那就来看看FrameworfServlet.initServletBean()干了啥(基本都是日志记录,还有计时,省略了这些部分):

	/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
//WebApplicationContext的初始化
this.webApplicationContext = initWebApplicationContext();
//也是空实现,允许子类自定义
initFrameworkServlet();
}

所以重头戏就在initWebApplicationContext方法上,我们可以先来看看执行后的效果:

可以看到springMVC九大组件被赋值,除此之外webApplicationContext也已被赋值。

我们再来看看源码,看看其内部具体实现:FrameworkServlet.initWebApplicationContext()

protected WebApplicationContext initWebApplicationContext() {
//根容器查找
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
//在构建时注入了DispatcherServlet并且webApplicationContext已经存在->直接使用
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//如果context还没有refresh-->进行设置父级context以及application context的id等等操作
if (cwac.getParent() == null) {
//在没有显式父级的情况下注入了context实例->将根应用程序上下文设置为父级
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//在构造时未注入任何上下文实例-->从ServletContext中查询
wac = findWebApplicationContext();
}
if (wac == null) {
// ServletContext中没有-->就创建一个被本地的
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
//如果context不支持refresh或者在初始化的时候已经refresh-->就手动触发onfresh
onRefresh(wac);
}
//把当前建立的上下文存入ServletContext中,使用的属性名和当前Servlet名相关
if (this.publishContext) {
// 将上下文发布为servlet上下文属性
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}

根容器查找的方法

WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContextUtils.getWebApplicationContext

//SpringMVC支持Spring容器与Web容易同时存在,并且Spring容器视作根容器,通常由ContextLoaderListener进行加载。
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
//String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
} @Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
//根据ServletName.ROOT为键查找值
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
return (WebApplicationContext) attr;
}

Spring容器和Web容器如果同时存在,需要使用ContextLoaderListener加载Spring的配置,且它会以key为

WebApplicationContext.class.getName() + ".ROOT存到ServletContext中。

容器创建的方法

构建的时候没有任何Context实例注入,且ServletContext中也没有找到WebApplicationContext,此时就会创建一个local Context,这个方法允许显式传入父级容器作为参数。

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//默认:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;可以在初始化参数中指定contextClass
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//获取ConfigurableWebApplicationContext对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac); return wac;
}

我们可以发现:在这个过程中,Web容器的IoC容器被建立,也就是XmlWebApplicationContext,,从而在web容器中建立起整个spring应用。

configureAndRefreshWebApplicationContext(wac);

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//省略给ConfigurableWebApplicationContext对象设置一些值...
//每次context refresh,都会调用initPropertySources
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//初始化webApplication容器,重启
wac.refresh();
}

加载配置文件信息

其实也就是refresh()这个关键方法,之前了解过spring容器的初始化的过程,对这一步应该相当熟悉,还是分为三步:

  • BeanDefinition的Resource的定位,我们这定位到了classpath:springmvc.xml。

  • beanDefinition的载入过程,springMVC做了一些改变,比如定义了针对mvc的命名空间解析MvcNamespaceHandler。

  • 接着是beanDefinition在IoC中的注册,也就是把beanName:beanDefinition以键值对的形式存入beandefinitionMap中。

2. MVC的初始化

MVC的初始化在DispatcherServlet的initStratefies方法中执行,通过方法名,我们就可以得出结论,就是在这进行了对九大组件的初始化,其实基本上都是从IoC容器中获取对象:

	protected void initStrategies(ApplicationContext context) {
//文件上传解析器
initMultipartResolver(context);
//区域信息解析器,与国际化相关
initLocaleResolver(context);
//主题解析器
initThemeResolver(context);
//handler映射信息解析
initHandlerMappings(context);
//handler的适配器
initHandlerAdapters(context);
//handler异常解析器
initHandlerExceptionResolvers(context);
//视图名转换器
initRequestToViewNameTranslator(context);
//视图解析器
initViewResolvers(context);
//flashMap管理器
initFlashMapManager(context);
}

文件上传解析器

private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
}
catch (NoSuchBeanDefinitionException ex) {
// 默认是没有配置multipartResolver的.
this.multipartResolver = null;
}
}

配置文件上传解析器也很简单,只需要在容器中注册MultipartResolver即可开启文件上传功能。

区域信息解析器

private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); }
catch (NoSuchBeanDefinitionException ex) {
// 使用默认策略,利用反射创建对象
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
}
}

org.springframework.web.servlet.DispatcherServlet同级目录下的DispatcherServlet.properties文件中规定了几大组件初始化的默认策略。

handler映射信息解析

handlerMappings存在的意义在于为HTTP请求找到对应的控制器Controller。

	private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//从所有的IoC容器中导入HandlerMappings,包括其双亲上下文
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
//尝试从容器中获取
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
//保证至少有一个handlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}

接下来几个操作都差不多,就不赘述了。

总的来说,MVC初始化的过程建立在IoC容器初始化之后,毕竟要从容器中取出这些组件对象。

3. HandlerMapping的实现原理

HandlerExecutionChain

HandlerMapping在SpringMVC扮演着相当重要的角色,我们说,它可以为HTTP请求找到 对应的Controller控制器,于是,我们来好好研究一下,这里面到底藏着什么玩意。

HandlerMapping是一个接口,其中包含一个getHandler方法,能够通过该方法获得与HTTP请求对应的handlerExecutionChain,而这个handlerExecutionChain对象中持有handler和interceptorList,以及和设置拦截器相关的方法。可以判断是同通过这些配置的拦截器对handler对象提供的功能进行了一波增强。

RequestMappingHandlerMapping

我们以其中一个HandlerMapping作为例子解析一下,我们关注一下:

protected void initHandlerMethods() {
//获取所有上下文中的beanName
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
//得到对应beanName的Class
beanType = obtainApplicationContext().getType(beanName);
//判断是否为控制器类
if (beanType != null && isHandler(beanType)) {
//对控制器中的方法进行处理
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}

isHandler方法:判断该类是否存在@Controller注解或者@RequestMapping注解

	@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

detectHandlerMethods方法:

protected void detectHandlerMethods(final Object handler) {
//获取到控制器的类型
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//对类型再次进行处理,主要是针对cglib
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//遍历方法,对注解中的信息进行处理,得到RequestMappingInfo对象,得到methods数组
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
return getMappingForMethod(method, userType);
});
//遍历methods[Method,{path}]
for (Map.Entry<Method, T> entry : methods.entrySet()) {
//对方法的可访问性进行校验,如private,static,SpringProxy
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
//获取最终请求路径
T mapping = entry.getValue();
//注册
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}

mapping对象的属性:

methods对象中存储的元素:

注册方法在AbstractHandlerMethodMapping中实现:

public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
//处理方法的对象
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//判断映射的唯一性
assertUniqueMethodMapping(handlerMethod, mapping);
//将mapping信息和控制器方法对应
this.mappingLookup.put(mapping, handlerMethod);
//将path与处理器映射(一个方法可能可以处理多个url)
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
//控制器名的大写英文缩写#方法名
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
//跨域请求相关配置
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
//将所有配置统一注册到registry中
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}

至此,所有的Controller,以及其中标注了@RequestMapping注解的方法,都被一一解析,注册进HashMap中,于是,对应请求路径与处理方法就一一匹配,此时HandlerMapping也初始化完成。

三、请求响应处理

1. 请求分发

我们需要明确的一个点是,请求过来的时候,最先执行的地方在哪,是Servlet的service方法,我们只需要看看该方法在子类中的一个实现即可:

FrameworkServlet重写的service方法:

	@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取请求方法
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
//拦截PATCH请求
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}

其实最后都是调用了processRequest方法,该方法中又调用了真正的doService()方法,其中细节先不探讨,我们直奔,看看DispatcherServlet的这个doService干了哪些事情(DispatcherServlet这个类确实是核心中的核心,既建立了IoC容器,又负责请求分发):

	@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//忽略一大串前期准备,使其能够处理view 对象
//接着进入真正的分发
doDispatch(request, response);
}

doService:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try {
ModelAndView mv = null;
Exception dispatchException = null; try {
//如果是文件上传请求,对request进行包装,如果不是就原样返回
processedRequest = checkMultipart(request);
//文件上传请求标识符
multipartRequestParsed = (processedRequest != request); //为当前的request请求寻找合适的handler
mappedHandler = getHandler(processedRequest);
//如果没有handler可以处理该请求,就跳转到错误页面
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//为当前的request请求寻找合适的adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
//判断是否支持getLastModified,如果不支持,返回-1
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//执行注册拦截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 真正处理请求的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果mv!=null&&mv对象没有View,则为mv对象设置一个默认的ViewName
applyDefaultViewName(processedRequest, mv);
//执行注册拦截器的applyPostHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
//进行视图解析和渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }

需要注意的是,mappedHandler和HandlerAdapter都是从对应的集合中遍历查找,一旦找到可以执行的目标,就会停止查找,我们也可以人为定义优先级,决定他们之间的次序。

2. 请求处理

RequestMappingHandlerAdapter的handleInternal方法,含有真正处理请求的逻辑。

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//定义返回值变量
ModelAndView mav;
//对请求进行检查 supportedMethods和requireSession
checkRequest(request); // 看看synchronizeOnSession是否开启,默认为false
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
//Httpsession可用
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
//加锁,所有请求串行化
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 没有可用的Httpsession -> 没必要上锁
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 正常调用处理方法
mav = invokeHandlerMethod(request, response, handlerMethod);
}
//检查响应头是否包含Cache-Control
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
} return mav;
}

RequestMappingHandlerAdapter的invokeHandlerMethod方法,真正返回mv。

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//对HttpServletRequest进行包装,产生ServletWebRequest处理web的request对象
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//创建WebDataBinder对象的工厂
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//创建Model对象的工厂
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//将handlerMethod对象进行包装,创建ServletInvocableHandlerMethod对象
//向invocableMethod设置相关属性(最后是由invocableMethod对象调用invokeAndHandle方法
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//创建ModelAndViewContainer对象,里面存放有向域中存入数据的map
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//省略异步处理
//正常调用
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
//获取ModelAndView对象
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}

ServletInvocableHandlerMethod的invokeAndHandle方法:反射调用方法,得到返回值。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//获取参数,通过反射得到返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//设置响应状态
setResponseStatus(webRequest); if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
} mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}

参数解析过程

我们可以知道的是,传递参数时,可以传递Map,基本类型,POJO,ModelMap等参数,解析之后的结果又如何呢?我们以一个具体的例子举例比较容易分析:

    @RequestMapping("/handle03/{id}")
public String handle03(@PathVariable("id") String sid,
Map<String,Object> map){
System.out.println(sid);
map.put("msg","你好!");
return "success";
}
/**
* 获取当前请求的方法参数值。
*/
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//获取参数对象
MethodParameter[] parameters = getMethodParameters();
//创建一个同等大小的数组存储参数值
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
//参数处理器处理参数(针对不同类型的参数有不同类型的处理参数的策略)
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
if (args[i] == null) {
throw new IllegalStateException();
}
return args;
}

resolveArgument方法:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//获取注解的信息
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
//包装parameter对象
MethodParameter nestedParameter = parameter.nestedIfOptional();
//获取@PathVariable指定的属性名
Object resolvedName = resolveStringValue(namedValueInfo.name);
//
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
//根据name从url中寻找并获取参数值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
//没有匹配
if (arg == null) {
//如果有default值,则根据该值查找
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
//如果required为false,则可以不指定name,但默认为true。
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
} arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
//虽然匹配,路径中传入的参数如果是“ ”,且有默认的name,则按照默认处理
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
} if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}

getNameValueInfo方法:

	private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
//从缓存中获取
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
//创建一个namedValueInfo对象
namedValueInfo = createNamedValueInfo(parameter);
//如果没有在注解中指定属性名,默认为参数名
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
//更新缓存
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}

createNamedValueInfo:获取@PathVariable注解的信息,封装成NamedValueInfo对象

	@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
return new PathVariableNamedValueInfo(ann);
}

updateNamedValueInfo:

private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
if (info.name.isEmpty()) {
//如果注解中没有指定name,则为参数名
name = parameter.getParameterName();
if (name == null) {
throw new IllegalArgumentException(
"Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");
}
}
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
return new NamedValueInfo(name, info.required, defaultValue);
}

resolveName方法:

参数解析的过程:

  • 根据方法对象,获取参数对象数组,并创建存储参数的数组。
  • 遍历参数对象数组,并根据参数解析器argumentResolver解析。
  • 如果没有参数解析器,报错。
  • 参数解析时,先尝试获取注解的信息,以@PathVariable为例。
  • 根据指定的name从url中获取参数值,如果没有指定,则默认为自己传入的参数名。

传递页面参数

我们可能会通过Map、Model、ModelMap等向域中存入键值对,这部分包含在请求处理中。

我们要关注的是ModelAndViewContainer这个类,它里面默认包含着BindingAwareModelMap。

在解析参数的时候,就已经通过MapMethodProcessor参数处理器初始化了一个BindingAwareModelMap。



当然其实这里重点还是参数解析,至于数据为什么封装进map,就很简单了,无非是反射执行方法的时候,通过put将数据存入,当然最后的数据也就存在于ModelAndViewContainer中。

返回值解析

省略寻找返回值解析器的过程,因为返回值为视图名,所以解析器为:ViewNameMethodReturnValueHandler。

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof CharSequence) {
//获取视图名
String viewName = returnValue.toString();
//向mavContainer中设置
mavContainer.setViewName(viewName);
//是否是isRedirectViewName
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null){
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}

isRedirectViewName方法

	protected boolean isRedirectViewName(String viewName) {
//是否符合自定义的redirectPatterns,或者满足redirect:开头的名字
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}

最后通过getModelAndView获取mv对象,我们来详细解析一下:

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//Promote model attributes listed as @SessionAttributes to the session
modelFactory.updateModel(webRequest, mavContainer);
//如果请求已经处理完成
if (mavContainer.isRequestHandled()) {
return null;
}
//从mavContainer中获取我们存入的数据map
ModelMap model = mavContainer.getModel();
//通过视图名、modelmap、和status创建一个ModelAndView对象
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}

最后返回的都是ModelAndView对象,包含了逻辑名和模型对象的视图。

返回值解析的过程相对比较简单:

  • 根据返回的参数,获取对应的返回值解析器。

  • 获取视图名,如果是需要redirect,则mavContainer.setRedirectModelScenario(true);

  • 其他情况下,直接给mvcContainer中的ViewName视图名属性设置上即可。

  • 最后将mvcContainer的model、status、viewName取出,创建mv对象返回。

【总结】

参数解析、返回值解析两个过程都包含大量的解决策略,其中寻找合适的解析器的过程都是先遍历初始化的解析器表,然后判断是否需要异步处理,判断是否可以处理返回值类型,如果可以的话,就使用该解析器进行解析,如果不行,就一直向下遍历,直到表中没有解析器为止。

3. 视图解析

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 保证渲染一次,cleared作为标记
if (mv != null && !mv.wasCleared()) {
//渲染过程!!!
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
}

DispatcherServlet的render方法

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale); View view;
//获取视图名
String viewName = mv.getViewName();
if (viewName != null) {
//通过视图解析器viewResolvers对视图名进行处理,创建view对象
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
}
else {
view = mv.getView();
}
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}

获取视图解析器,解析视图名:

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception { //这里我们注册的是InternalResourceViewResolver
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}

UrlBasedViewResolver的createView方法:

	@Override
protected View createView(String viewName, Locale locale) throws Exception {
//如果解析器不能处理所给的view,就返回null,让下一个解析器看看能否执行
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
//判断是否需要重定向
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
//判断是否需要转发
}
//调用父类的loadView方法
return super.createView(viewName, locale);
}

最后返回的视图对象:

视图解析器 viewResolver --实例化 --> view(无状态的,不会有线程安全问题)

AbstractView的render方法

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception { //获取合并后的map,有我们存入域中的map,还有PathVariable对应的键值等
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//根据给定的model渲染内部资源,如将model设置为request的属性
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

InternalResourceView的renderMergedOutputModel

@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //将model中的值设置到request域中
exposeModelAsRequestAttributes(model, request); // 如果有的话,给request设置helpers
exposeHelpers(request); // 将目标地址设置到request中
String dispatcherPath = prepareForRendering(request, response); // 获取目标资源(通常是JSP)的RequestDispatcher。
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); // 如果已经包含或响应已经提交,则执行包含,否则转发。
if (useInclude(request, response)) {
response.setContentType(getContentType());
rd.include(request, response);
}
else {
// Note: 转发的资源应该确定内容类型本身。
rd.forward(request, response);
}
}

exposeModelAsRequestAttributes

protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {
//遍历model
model.forEach((modelName, modelValue) -> {
if (modelValue != null) {
//向request中设置值
request.setAttribute(modelName, modelValue);
}
else {
//value为null的话,移除该name
request.removeAttribute(modelName);
}
});
}

视图解析器

视图解析器(实现ViewResolver接口):将逻辑视图解析为具体的视图对象。



每个视图解析器都实现了Ordered接口,并开放order属性,order越小优先级越高。

按照视图解析器的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出异常。

视图

视图(实现View接口):渲染模型数据,将模型数据以某种形式展现给用户。

最终采取的视图对象对模型数据进行渲染render,处理器并不关心,处理器关心生产模型的数据,实现解耦。

SpringMVC源码学习:容器初始化+MVC初始化+请求分发处理+参数解析+返回值解析+视图解析的更多相关文章

  1. springMvc源码学习之:spirngMVC获取请求参数的方法2

    @RequestParam,你一定见过:@PathVariable,你肯定也知道:@QueryParam,你怎么会不晓得?!还有你熟悉的他 (@CookieValue)!她(@ModelAndView ...

  2. springMvc源码学习之:spirngMvc获取请求参数的方法

    一.      通过@PathVariabl获取路径中的参数 @RequestMapping(value="user/{id}/{name}",method=RequestMeth ...

  3. SpringMVC源码分析--容器初始化(三)HttpServletBean

    在上一篇博客springMVC源码分析--容器初始化(二)DispatcherServlet中,我们队SpringMVC整体生命周期有一个简单的说明,并没有进行详细的源码分析,接下来我们会根据博客中提 ...

  4. SpringMVC源码分析--容器初始化(五)DispatcherServlet

    上一篇博客SpringMVC源码分析--容器初始化(四)FrameworkServlet我们已经了解到了SpringMVC容器的初始化,SpringMVC对容器初始化后会进行一系列的其他属性的初始化操 ...

  5. SpringMVC源码分析--容器初始化(四)FrameworkServlet

    在上一篇博客SpringMVC源码分析--容器初始化(三)HttpServletBean我们介绍了HttpServletBean的init函数,其主要作用是初始化了一下SpringMVC配置文件的地址 ...

  6. springMVC源码分析--容器初始化(二)DispatcherServlet

    在上一篇博客springMVC源码分析--容器初始化(一)中我们介绍了spring web初始化IOC容器的过程,springMVC作为spring项目中的子项目,其可以和spring web容器很好 ...

  7. Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  8. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  9. SpringMVC源码学习之request处理流程

    目的:为看源码提供调用地图,最长调用逻辑深度为8层,反正我是springMVC源码学习地址看了两周才理出来的. 建议看完后还比较晕的,参照这个简单的模型深入底层,仿SpringMVC自己写框架,再来理 ...

  10. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

随机推荐

  1. redis list 基本操作

    写在前面的话 本篇笔记写在笔者刚工作时.如有问题,请指教. 简介 list是链表,redis list的应用场景很多,也是Redis 最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能 ...

  2. Linux 命令系列之 seq

    简介 seq -- print sequences of numbers seq 命令可以输出各种有规律的数字. 用法 usage: seq [-w] [-f format] [-s string] ...

  3. C#中分布式事务的超时处理问题

    事务是个很精妙的存在,我们在数据层.服务层.业务逻辑层等多处地方都会使用到. 在这里我只说下TransactionScope这个微软推荐使用的隐式事务.它是从Framework 2.0开始引入的一个事 ...

  4. Crossing River POJ过河问题

    A group of N people wishes to go across a river with only one boat, which can at most carry two pers ...

  5. Pytorch实现MNIST手写数字识别

    Pytorch是热门的深度学习框架之一,通过经典的MNIST 数据集进行快速的pytorch入门. 导入库 from torchvision.datasets import MNIST from to ...

  6. 深入理解Java线程状态转移

    目录 前言 状态转移图 1.0 新建态到就绪态 1.1 就绪态到运行态 1.2 运行态到就绪态 1.2.1 时间片用完 1.2.2 t1.yield() .Thread.yield(); 1.3 运行 ...

  7. vue如何添加jquery?

    1.首选通过npm安装jquery? 2.在build/webpack.base.conf文件当中引入jquery <pre>module.exports = { ... resolve: ...

  8. 尝试用Vue.js开发网页小游戏的过程

    准备 首先去官方下载并安装VSCODE,下载地址 https://code.visualstudio.com/.安装后打开会发现是英文版的,需要去安装插件来汉化.具体是在扩展插件搜索chinese,选 ...

  9. TensorFlow的图像NCHW与NHWC

    import tensorflow as tf x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] with tf.Session() as sess: a = t ...

  10. RedHat Linux server 6.5系统关机重启失败问题总结

    今天晚上升级服务,由于服务器(red hat Linux server 6.5操作系统)没有正常关机,再重启的过程中遇到了如下问题: 1 服务器配置挺高的,认为启动过程有点慢是正常的,当时就没有上心, ...