原文链接: http://my.oschina.net/ojeta/blog/716550
DispatcherServlet
分析过ContextLoaderListener
的代码之后,再去分析DispatcherServlet
的代码会发现有很多的相似性。 在分析之前我们先看一下spring-mvc.xml
中的内容,它位于src/main/webapp/WEB-INF/spring-mvc.xml
,内容为
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="personal.wanghui.quickstart" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter" c:defaultCharset="UTF-8" />
</mvc:message-converters>
<mvc:argument-resolvers>
<bean class="personal.wanghui.quickstart.web.bind.method.SubjectMethodArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<!-- 定义JSP文件的位置 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/views/"
p:suffix=".jsp" />
<!-- 文件上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
</bean>
<!-- 容器默认的DefaultServletHandler处理 所有静态内容与无RequestMapping处理的URL -->
<mvc:default-servlet-handler />
</beans>
首先,看下DispatcherServlet
的继承结构:
结构不算复杂。注意到DispatcherServet
继承了Servlet
标准中的HttpServlet
。那么,根据Servlet的相关知识,DispatcherServlet
也要符合Servlet的生命周期,即初始化、处理请求并响应、销毁,对应的方法即是init()
、doGet()
/doPost()
..、destroy()
。而DispatcherServlet
只要重写这些方法,即可实现相应的功能。接下来我们一个一个分析。
重写init()
DispatcherServlet
并没有自己重写init()
,而是在HttpServletBean
中重写的。看源码org.springframework.web.servlet.HttpServletBean.init()
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); ---- (1)
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ---- (2)
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); ---- (3)
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); ----- (4)
initBeanWrapper(bw); ---- (5)
bw.setPropertyValues(pvs, true); ---- (6)
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
initServletBean(); ---- (7)
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
这里先介绍Spring的一个概念,叫做BeanWrapper。BeanWrapper
是一个接口,实现类是BeanWrapperImpl
,它可以将普通的JavaBean包装为BeanWrapper,然后可以通过void setPropertyValue(String propertyName, Object value)
和Object getPropertyValue(String propertyName)
等方法来实现对属性值的设置和获取。它有两点很重要的特性:
- 可以不用考虑属性值的类型,而直接调用
setPropertyValue()
方法即可,BeanWrapper内置了大量的属性编辑器PropertyEditor
,会在内部实现类型转换。 - propertyName可以支持嵌套,比如
setProperyValue("wife.name", "zcc")
。 另外,如果JavaBean的某属性类型,不在默认的属性编辑器的范围内,还可以自定义属性编辑器。更多关于BeanWrapper的知识,可以参考《Spring 3.x企业应用开发实战》第5章 Spring容器高级主题,5.1.4节 BeanWrapper。
核心代码逻辑梳理
- (1) 将
ServletConfig
中的参数包装成PropertyValues
。看下源码
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
new HashSet<String>(requiredProperties) : null;
Enumeration<String> en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = en.nextElement(); ---- [1]
Object value = config.getInitParameter(property); ---- [2]
addPropertyValue(new PropertyValue(property, value)); ---- [3]
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
if (missingProps != null && missingProps.size() > 0) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
[1]和[2]从ServletConfig
中取出每个通过web.xml配置的初始化参数的名和值,在[3]中将每个名值对都包装为PropertyValue
对象,并调用addPropertyValue(PropertyValue pv)
添加PropertyValue
对象。
- (2) 将
this
(指DispatcherServlet
)包装为BeanWrapper
。看下源码
public static BeanWrapper forBeanPropertyAccess(Object target) {
return new BeanWrapperImpl(target);
}
使用BeanWrapperImpl
将目标对象包装
- (3) 创建一个资源加载器,使可以从WebRoot路径下加载文件。看下源码
public class ServletContextResourceLoader extends DefaultResourceLoader {
private final ServletContext servletContext;
public ServletContextResourceLoader(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
protected Resource getResourceByPath(String path) {
return new ServletContextResource(this.servletContext, path);
}
}
这里介绍一下Spring的Resource体系。先看继承结构:
这里我们先不去看具体的实现类,先看看最顶层的两个接口的定义
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
由接口定义我们看出,所有的Resource
实现类,都会具有访问相应资源的常用方法,比如判断是否存在、是否可读、获取URL、获取文件对象、获取内容长度、获取文件名、以相对路径创建一个资源,并且还可以获取输入流。
接下来看下我们这里涉及到的子类ServletContextResource
,这里只看一下它的构造方法和getInputStream()
方法
public ServletContextResource(ServletContext servletContext, String path) {
// check ServletContext
Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext");
this.servletContext = servletContext;
// check path
Assert.notNull(path, "Path is required");
String pathToUse = StringUtils.cleanPath(path);
if (!pathToUse.startsWith("/")) {
pathToUse = "/" + pathToUse;
}
this.path = pathToUse;
}
@Override
public InputStream getInputStream() throws IOException {
InputStream is = this.servletContext.getResourceAsStream(this.path);
if (is == null) {
throw new FileNotFoundException("Could not open " + getDescription());
}
return is;
}
在构造方法中,只是将ServletContext
封装起来,然后对路径做一些小小的处理,如果路径不是以/开头的,就加上/。
在getInputStream()
中,获取路径的输入流直接调用ServletContext.getResourceAsStream(String path)
方法返回。
- (4) 为BeanWrapper注册自定义的属性编辑器,使实现如果属性的类型是
Resource
的话,如何实现类型转换。这里重点看一下ResourceEditor
的代码
public ResourceEditor(ResourceLoader resourceLoader, PropertyResolver propertyResolver) { ---- [1]
this(resourceLoader, propertyResolver, true);
}
public ResourceEditor(ResourceLoader resourceLoader, PropertyResolver propertyResolver, ---- [2]
boolean ignoreUnresolvablePlaceholders) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
this.propertyResolver = propertyResolver;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
@Override
public void setAsText(String text) {
if (StringUtils.hasText(text)) {
String locationToUse = resolvePath(text).trim();
setValue(this.resourceLoader.getResource(locationToUse)); ---- [3]
}
else {
setValue(null);
}
}
protected String resolvePath(String path) {
if (this.propertyResolver == null) {
this.propertyResolver = new StandardEnvironment();
}
return (this.ignoreUnresolvablePlaceholders ? this.propertyResolver.resolvePlaceholders(path) :
this.propertyResolver.resolveRequiredPlaceholders(path));
}
@Override
public String getAsText() {
Resource value = (Resource) getValue();
try {
// Try to determine URL for resource.
return (value != null ? value.getURL().toExternalForm() : "");
}
catch (IOException ex) {
// Couldn't determine resource URL - return null to indicate
// that there is no appropriate text representation.
return null;
}
}
代码中调用的是构造方法[1],[1]又调用了[2],可以看到在setAsText(String text)
和getAsText()
两个方法中,实现了String
类型的path和Resource
之间的相互转换。
[3]处实际上就调用的是ServletContextResourceLoader.getResource(String location)
,这个方法在ServletContextResourceLoader
的父类DefaultResourceLoader
中,代码如下
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
虽然在DefaultResourceLoader
中提供了getResourceByPath(String path)
的默认实现,但它是从classpath下加载资源的,而恰好在子类ServletContextResourceLoader
重写了此方法,使其可以从WebRoot下加载文件。
- (5) 初始化BeanWrapper,这里是一个空方法,子类也没有去重写。
- (6) 向BeanWrapper中设置属性值。
- (7) 空方法,留给子类实现。下面会详细说明这个方法。
小结 DispatcherServlet
在父类中重写了标准的HttpServlet
的init()
方法,为后续处理请求做一些初始化工作。在重写的init()
中,将初始化参数包装为PropertyValues
,将DispatcherServlet
包装为BeanWrapper
,利用BeanWrapper
的特性来设置属性。
initServletBean()
这个方法是在子类中实现的,代码路径org.springframework.web.servlet.FrameworkServlet.initServletBean()
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext(); ---- (1)
initFrameworkServlet(); ---- (2)
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
核心代码逻辑梳理
- (1) 初始化WebApplicationContext。前面已经构造了一个Spring容器,这里"又"构造了一个Spring容器。看下源码
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext()); ---- (1)
WebApplicationContext wac = null;
if (this.webApplicationContext != null) { ---- (2)
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext(); ---- (3)
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext); ---- (4)
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac); ---- (5)
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac); ---- (6)
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
核心代码逻辑梳理
- (1) 直接看源码
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
代码大意就是从ServletContext
中根据key(值为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
)获取WebApplicationContext
。
回想之前我们对ContextLoaderListener
的分析,它就是把Spring容器放到了ServletContext
中,而key恰好就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
,所以这里取到的就是之前通过ContextLoaderListener
初始化好的Spring容器。 所以,rootContext
就是Spring的根容器,也是父容器。
- (2) 是判断
this.webApplicationContext
是否为空,之前并没有向这个属性中写入值,所以这里是空的。变量wac
代表SpringMVC自己的容器,此时也还是null。 - (3) 查询是否有自己的WebApplication。源码如下
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
还是尝试从ServletContext
中获取WebApplicationContext
,但key是通过getContextAttribute()
确定的。默认情况下,如果没有调用setContextAttribute(String contextAttribute)
,那么getContextAttribute()
就是返回null
的。所以,这个方法的返回值也是null
。
- (4) 既然没有外部传入,在
ServletContext
中也没找到自己的SpringMVC容器,那么就创建一个。看源码
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass(); ---- (1)
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
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 wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); ---- (2)
wac.setEnvironment(getEnvironment()); ---- (3)
wac.setParent(parent); ---- (4)
wac.setConfigLocation(getContextConfigLocation()); ---- (5)
configureAndRefreshWebApplicationContext(wac); ---- (6)
return wac;
}
核心代码逻辑分析
- 方法以Spring父(根)容器作为参数传入。
- (1) 获取contextClass,在
ContextLoaderListener
也有这么个类似的方法。看下getContextClass()
方法的代码
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
public void setContextClass(Class<?> contextClass) {
this.contextClass = contextClass;
}
public Class<?> getContextClass() {
return this.contextClass;
}
如果没有调用过setContextClass(Class<?> contextClass)
,那么getContextClass()
的返回值就是XmlWebApplicationContext.class
。又一个XmlWebApplicationContext
。
- (2) 就是获取到contextClass,然后实例化,和Spring容器的实例化是一样的。
- (3) 向当前Spring容器设置环境对象。
- (4) 设置当前Spring容器与父容器的引用关系。由此,我们看到,SpringMVC容器和Spring容器是父子关系。
- (5) 设置配置文件路径。如果在配置
DispatcherServlet
时指定了contextConfigLocation
参数,这里就会获取到参数值。在哪里设置的?就是BeanWrapper
包装了DispatcherServlet
,然后setPropertyValues()
啊。 - (6) 一看方法名就很熟悉,和初始化父容器的方法名都一样的。提醒一下
org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
。看下源码
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); ---- [1]
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac); ---- [2]
applyInitializers(wac); ---- [3]
wac.refresh(); ---- [4]
}
核心代码逻辑梳理
- [1] 这里编程式的添加了一个应用监听器,关于应用监听器前面已经有所介绍了。直接看下
ContextRefreshListener
的源码
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
代码很简单,实现了ApplicationListener接口
,监听ContextRefreshedEvent
事件,当ContextRefreshedEvent
事件被分发后,会触发FrameworkServlet.this.onApplicationEvent(event);
,看下这个方法的代码:
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
onRefresh(ApplicationContext context)
交给了子类实现org.springframework.web.servlet.DispatcherServlet.onRefresh(ApplicationContext context)
好了,这条线先放这。等下面讲到事件分发的时候再说。
- [2] 对SpringMVC容器做一些后处理。目前,这是个空方法,可以交留子类重写。
- [3] 应用初始化器。依然与
ContextLoader.customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac)
类似,不再说了。 - [4] 与
ContextLoader.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
中的wac.refresh();
完全一样,也是调用AbstractApplicationContext.refresh()
。
这里注意,在AbstractApplicationContext.refresh()
中,最后会分发ContextRefreshedEvent
事件,此时由于在子容器中注册了一个监听ContextRefreshedEvent
事件的ApplicationListener
。那么根据之前的分析,此时会调用org.springframework.web.servlet.DispatcherServlet.onRefresh(ApplicationContext context)
,并且传入的参数就是SpringMVC容器。看下源码:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
会初始化一堆策略。在initStrategies(ApplicationContext context)
方法中,又分别调用了好多方法。我们逐一看一下。
initMultipartResolver(ApplicationContext context)
看下源码
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}
尝试从SpringMVC容器中获取上传文件解析器的bean,将其赋值给this.multipartResolver
;如果没找到会抛出NoSuchBeanDefinitionException
异常,catch到异常后,将this.multipartResolver
的值设置为null
。
initLocaleResolver(ApplicationContext context)
看下源码
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver + "]");
}
}
}
和上面方法大体类似,尝试获取本地语言解析器的bean。但这里会在没有找到这样的bean时,做一些默认处理,即this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
。看下getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface)
方法的源码:
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
List<T> strategies = getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException(
"DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
}
return strategies.get(0);
}
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key); ---- (1)
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value); ---- (2)
List<T> strategies = new ArrayList<T>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz); ---- (3)
strategies.add((T) strategy); ---- (4)
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<T>();
}
}
真正执行获取默认策略的是getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
,它返回一个List<T>
,getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
方法返回的是这个List<T>
中的第1个值。
- (1) 从
defaultStrategies
中根据接口的名字获取值,defaultStrategies
?很熟悉的变量名,之前在分析ContextLoaderListener
中遇到过。那defaultStrategies
到底是什么呢?看下源码
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}
嗯,变量的赋值逻辑也很相似。原来它就是实现了从classpath下读取DispatcherServlet.properties
文件。那看下DispatcherServlet.properties
中的内容吧。它位于spring-webmvc-[version].jar的org.springframework.web.servlet
包下。
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
原来是一些接口默认的实现类。那么defaultStrategies.getProperty(key)
返回的就是这个接口默认的实现类。如果有多个实现类会用逗号连接起来。
- (2) 通过逗号把这些实现类拆分成数组。
- (3) 在遍历获取到每个实现类的
Class<?>
后,去动态地创建SpringBean。这里简单看下源码org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(Class<T> beanClass)
public <T> T createBean(Class<T> beanClass) throws BeansException {
// Use prototype bean definition, to avoid registering bean as dependent bean.
RootBeanDefinition bd = new RootBeanDefinition(beanClass);
bd.setScope(SCOPE_PROTOTYPE);
bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader());
return (T) createBean(beanClass.getName(), bd, null);
}
要注意的是,动态创建的这些SpringBean,scope是prototype
的。
- (4) 将每个实现类都加入到
List<T> strategies
中,最后返回。
现在,我们梳理一下List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
方法具体的含义:就是根据接口名,到DispatcherServlet.properties
中找到它的实现类,然后动态地去创建为SpringBean。而Spring之所以将这些实现类配置在properties文件中放到jar包里,是不希望开发者去修改它。
initThemeResolver(ApplicationContext context)
与initLocaleResolver(ApplicationContext context)
类似
initHandlerMappings(ApplicationContext context)
看下源码
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) { ---- (1)
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); ---- (2)
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings); ---- (3)
}
}
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.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); ---- (4)
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
- (1)
this.detectAllHandlerMappings
默认为true
- (2)
BeanFactoryUtils.beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
实现了在当前容器和其父容器中查找类型为type的所有bean。 - (3) 对这些bean进行排序。之前我们分析过
AnnotationAwareOrderComparator
的用法,这里看下OrderComparator
的核心逻辑:
@Override
public int compare(Object o1, Object o2) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {
return -1;
}
else if (p2 && !p1) {
return 1;
}
// Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
int i1 = getOrder(o1);
int i2 = getOrder(o2);
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
protected int getOrder(Object obj) {
return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : Ordered.LOWEST_PRECEDENCE);
}
compare()
方法中,先看两个对象是否有实现了PriorityOrdered
接口,实现的比没实现的靠前。如果都实现了,或者都没实现,就根据getOrder()
方法获取order值,再根据数值大小判断,数值小的排前面。getOrder()
方法中,判断是否实现了Ordered
接口,如果实现了就通过Ordered
接口的getOrder()
方法获取排序值,如果没实现就排在最后。 看来,对所有HandlerMapping
接口的子类,如果是用注解@Order
指定顺序是无用的,自己实现的时候要注意。囧~
- (4) 如果前面都没有获取handlerMapping,那么就从默认策略中加载。
initHandlerAdapters(ApplicationContext context)
与initHandlerMappings(ApplicationContext context)
类似
initHandlerExceptionResolvers(ApplicationContext context)
与initHandlerMappings(ApplicationContext context)
类似
initRequestToViewNameTranslator(ApplicationContext context)
与initLocaleResolver(ApplicationContext context)
类似
initViewResolvers(ApplicationContext context)
与initHandlerMappings(ApplicationContext context)
类似
initFlashMapManager(ApplicationContext context)
与initLocaleResolver(ApplicationContext context)
类似
至此,SpringMVC容器也初始化完毕了,事件也分发了,该初始化的也都初始化了。但是initWebApplicationContext()
还没有完全执行完呢。再回顾一下代码:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext()); ---- (1)
WebApplicationContext wac = null;
if (this.webApplicationContext != null) { ---- (2)
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext(); ---- (3)
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext); ---- (4)
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac); ---- (5)
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac); ---- (6)
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
现在,(4)已经执行完了。接下来,根据this.refreshEventReceived
变量的值来判断要不要再执行onRefresh()
。如果先收到事件,会在FrameworkServlet.onApplicationEvent(ContextRefreshedEvent event)
中将this.refreshEventReceived
设置为true
,那么(5)也就不会执行了。
然后,还会判断this.publishContext
的值,默认是true
,那么(6)就是把当前的SpringMVC容器放入到ServletContext
中,主要看一下key是谁呢?看下getServletContextAttributeName()
方法的源码:
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
@Override
public final String getServletName() {
return (getServletConfig() != null ? getServletConfig().getServletName() : null);
}
可以看出SpringMVC容器放入ServletContext
中的key为"org.springframework.web.servlet.FrameworkServlet.CONTEXT.[servletName]"。例如,按我们最初给出的配置文件来看,key为"org.springframework.web.servlet.FrameworkServlet.CONTEXT.springServlet"。
最后的最后,在FrameworkServlet.initServletBean()
中,执行完this.webApplicationContext = initWebApplicationContext();
后,还调用了initFrameworkServlet()
,但这是个空方法,子类也没有去重写。
至此,DispatcherServlet的init()
方法就全部执行完成了。大体上做了3件事:
- 初始化参数
- 创建一个SpringMVC自己的容器,并与父容器之间建立引用关系
- 利用事件机制,初始化SpringMVC自己的一些组件
几个小问题
- 我们最初给出的web.xml的配置中,在配置
DispatcherServlet
时,还配置了这么一行代码<load-on-startup>1</load-on-startup>
,这是为了在Web容器启动时就调用Servlet
的init()
方法。 - 解答之前的一个问题:之前我们写了一个
ApplicationListener
的小例子,然后提了一个问题:如果项目中有SpringMVC会打印出两句;如果没有,则只会打印出第一句;如果将此myApplicationListener
配置到SpringMVC的配置文件中,则只会打印出第二句。这是为什么呢?
现在解答一下:现在我们知道了,SpringMVC容器和Spring容器是子与父的关系。
如果myApplicationListener
配置在父容器里,父容器初始化完成后肯定会打印日志,那么子容器呢?子容器的事件也会向父容器中分发的。为什么这么说呢?我们看这个方法AbstractApplicationContext.publishEvent(ApplicationEvent event)
@Override
public void publishEvent(ApplicationEvent event) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
getApplicationEventMulticaster().multicastEvent(event);
if (this.parent != null) {
this.parent.publishEvent(event);
}
}
在最后,会判断如果容器有父容器,也会向父容器分发事件的,而myApplicationListener
又是配置在父容器中的。所以,SpringMVC加载完毕后,还会打印出一条日志。
当然,如果没有SpringMVC,就只会打印出一条日志了。
如果myApplicationListener
配置在SpringMVC中,首先Spring容器中就并没有注册这个ApplicationListener
,所以Spring容器初始化完毕时,是不会打印日志的。打印的那条日志,只是在SpringMVC容器初始化完毕时打印的。
3. 前面我们说了这么多initXxx()
类的方法,我们重点看一个方法initHandlerMappings(ApplicationContext context)
,这个方法前面提到了,是初始化HandlerMappring
的,再总结一下它的逻辑:
- 先从SpringMVC容器和它的父容器Spring容器中找
- 找不到就找名字是
HANDLER_MAPPING_BEAN_NAME
的SpringBean - 如果还没有,就使用默认策略。
好,我们这里重点看第一步,当我们尝试从SpringMVC容器和Spring容器中找的时候,到底能不能找到呢?能找到几个呢?通过debug跟踪,我们发现运行到Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
HandlerMapping.class, true, false);
这一行时,是能够找到的,而且还是3个bean。分别是RequestMappingHandlerMapping
、BeanNameUrlHandlerMapping
和SimpleUrlHandlerMapping
。那这3个bean是哪里来的呢?
要解释清楚这个问题,还得先说明一个Spring的概念,叫命名空间(Namespace)。Spring通过提供命名空间来使开发者可以通过定义命名空间的方式,来扩展Spring容器在解析自定义xml配置时的方法。
什么是命名空间呢?凡是在Spring的xml配置文件,以<xxx:xxx xxx="xxx"></xxx:xxx>
形式存在的都是Spring的自定义命名空间。回忆之前spring-mvc.xml
中的内容。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 自动扫描且只扫描@Controller -->
<context:component-scan base-package="personal.wanghui.quickstart" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter" c:defaultCharset="UTF-8" />
</mvc:message-converters>
<mvc:argument-resolvers>
<bean class="personal.wanghui.quickstart.web.bind.method.SubjectMethodArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<!-- 定义JSP文件的位置 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/views/"
p:suffix=".jsp" />
<!-- 文件上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
</bean>
<!-- 容器默认的DefaultServletHandler处理 所有静态内容与无RequestMapping处理的URL -->
<mvc:default-servlet-handler />
</beans>
像<context:component-scan />
、<mvc:annotation-driven />
、<mvc:default-servlet-handler />
都是自定义命名空间。
那么实现一个自定义命名空间都有什么要素呢?以<mvc:annotation-driven />
举例吧。
- 得有一个xml语法文件xsd,例如:spring-mvc.xsd;
- 得有命名空间的url,例如:
xmlns:mvc="http://www.springframework.org/schema/mvc
; - 得有命名空间url和xsd文件的对应关系,例如
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
- 为了可以不通过网络去加载这个xsd文件,还得有一个的xsd的url形式与本地xsd文件路径的映射配置,看spring-webmvc-[version].jar下的
META-INF
文件夹下的spring.schemas
。内容是:
http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd=org/springframework/web/servlet/config/spring-mvc-4.0.xsd
http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-4.0.xsd
- 语法校验层面解决了,自定义命名空间怎么解析呢?看spring-webmvc-[version].jar下的
META-INF
文件夹下的spring.handlers
。内容是:
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
说明要使用MvcNamespaceHandler
去解析自定义命名空间了。打开MvcNamespaceHandler
类的源码:
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
}
}
看到了annotation-driven
、default-servlet-handler
等等,这就是mvc命名空间下的标签。那么annotation-driven
对应的就是使用AnnotationDrivenBeanDefinitionParser
去解析的。再看AnnotationDrivenBeanDefinitionParser
:
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
if (element.hasAttribute("enable-matrix-variables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
else if (element.hasAttribute("enableMatrixVariables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
configurePathMatchingProperties(handlerMappingDef, element);
......
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
......
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
parserContext.popAndRegisterContainingComponent();
return null;
}
}
核心逻辑梳理
- 它实现了
BeanDefinitionParser
接口,重写了parse(Element element, ParserContext parserContext)
方法 - 动态创建了一个
RequestMappingHandlerMapping
类型的RootBeanDefinition
,并通过BeanWrapper
的形式配置属性,注意它的order
是0。 - 然后调用
ParserContext.registerComponent(ComponentDefinition component)
,向容器注册组件。 parserContext.popAndRegisterContainingComponent();
,源码就不贴了。向容器注册了BeanNameUrlHandlerMapping
,order
是2。
以上,我们就把RequestMappingHandlerMapping
和BeanNameUrlHandlerMapping
找到了。使用相同的方法可以在<mvc:default-servlet-handler />
中找到SimpleUrlHandlerMapping
,它的order
没有设置,那么order
值就是最大值。 同理,可以找到3个HandlerAdapter
,RequestMappingHandlerAdapter
、HttpRequestHandlerAdapter
、SimpleControllerHandlerAdapter
。
重写destroy()
看下源码
@Override
public void destroy() {
getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
// Only call close() on WebApplicationContext if locally managed...
if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
((ConfigurableApplicationContext) this.webApplicationContext).close();
}
}
代码很简单,就是直接从自己的成员变量里中取到SpringMVC容器,然后调用它的close()
方法。
重写各种doXxx()
在FrameworkServlet
中,我们找到了好几个重写的doXxx()
方法,其中doGet()
、doPost()
、doPut()
、doDelete()
的内容都一样,都只有一句话processRequest(request, response);
,只有doOptions()
和doTrace()
有一些特殊处理。看下源码
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchOptionsRequest) {
processRequest(request, response);
if (response.containsHeader("Allow")) {
// Proper OPTIONS response coming from a handler - we're done.
return;
}
}
// Use response wrapper for Servlet 2.5 compatibility where
// the getHeader() method does not exist
super.doOptions(request, new HttpServletResponseWrapper(response) {
@Override
public void setHeader(String name, String value) {
if ("Allow".equals(name)) {
value = (StringUtils.hasLength(value) ? value + ", " : "") + RequestMethod.PATCH.name();
}
super.setHeader(name, value);
}
});
}
this.dispatchOptionsRequest
表明是否分发doOptions的请求,默认值是false
。那么,接下来就直接调用父类中的doOptions()
方法,只不过这里使用了一个匿名类HttpServletResponseWrapper
对response
做了包装,重写了一些逻辑,具体就不再赘述了。
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchTraceRequest) {
processRequest(request, response);
if ("message/http".equals(response.getContentType())) {
// Proper TRACE response coming from a handler - we're done.
return;
}
}
super.doTrace(request, response);
}
this.dispatchTraceRequest
表明是否分发doTrace的请求,默认值是false
。所以,直接调用父类的doTrance()
方法进行处理。
好了,那么现在所有的核心逻辑都指向processRequest(HttpServletRequest request, HttpServletResponse response)
方法,我们就来看看这个方法的源码。
processRequest(HttpServletRequest request, HttpServletResponse response)
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request); ---- (1)
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); ---- (2)
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); ---- (3)
initContextHolders(request, localeContext, requestAttributes); ---- (4)
try {
doService(request, response); ---- (5)
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes); ---- (6)
if (requestAttributes != null) {
requestAttributes.requestCompleted(); ---- (7)
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, startTime, failureCause); ---- (7)
}
}
核心逻辑梳理
- (1) 构建
LocaleContext
- (2) 构建
RequestAttributes
。看下getRequestAttributes()
方法的代码
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
requestAttributesHolder
的类型是ThreadLocal<RequestAttributes>
,试图从当前线程中获取RequestAttributes
。RequestAttributes
做了什么呢?主要是对HttpServletRequest
对象或HttpSession
的一个包装。再
protected ServletRequestAttributes buildRequestAttributes(
HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) {
if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
return new ServletRequestAttributes(request);
}
else {
return null; // preserve the pre-bound RequestAttributes instance
}
}
再看下ServletRequestAttributes
类
public ServletRequestAttributes(HttpServletRequest request) {
Assert.notNull(request, "Request must not be null");
this.request = request;
}
@Override
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
构造方法只是对HttpServletRequest
的一个包装,getAttribute()
的时候,需要指定scope
,scope
的取值定义在其实现的接口RequestAttributes
中,自己看代码就可以了,不再详述。
- (3) 注册可调用的拦截器。
- (4) 初始化上下文Holder。
- (5) 核心逻辑,但交给子类处理。一会详述。
- (6) 重置一些上下文中的变量。
- (7) 发布请求完成的事件,自己看源码吧,不难。目前Spring没有提供监听
ServletRequestHandledEvent
事件的监听器,如果开发者需要可以自己实现一个ApplicationListener
。
doService(HttpServletRequest request, HttpServletResponse response)
接下来重点说这个方法,在FrameworkServlet
中是一个抽象方法,真正的实现是在子类DispatcherServlet
中。看下源码:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); ---- (1)
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
doDispatch(request, response); ---- (2)
}
finally {
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
这个方法也不每行看了,就看2行。··
- (1) 中表明每次请求,都会把SpringMVC容器的引用放到请求里,key是
WEB_APPLICATION_CONTEXT_ATTRIBUTE
。也就是说可以通过request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)
获取到SpringMVC容器。 - (2) 再调用
doDispatch(HttpServletRequest request, HttpServletResponse response)
方法,这里才是真正处理请求的地方。
doDispatch(HttpServletRequest request, HttpServletResponse response)
直接看源码
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 {
processedRequest = checkMultipart(request); ---- (1)
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest); ---- (2)
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ---- (3)
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) { ---- (4)
return;
}
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ---- (5)
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv); ---- (6)
mappedHandler.applyPostHandle(processedRequest, response, mv); ----- (7)
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); ---- (8)
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
我们先大致的看一下逻辑,然后再逐一分析核心方法
- 先检查了一下上传请求
- 根据请求获得了一个
HandlerExecutionChain
- 从
HandlerExecutionChain
中获取了一个handler
,然后获得了一个HandlerAdapter
- 对请求做前处理
- 处理请求,获得
ModelAndView
- 设置默认的视图
- 对请求做后处理
- 处理请求分发结果
HttpServletRequest processedRequest = checkMultipart(request)
检查是否是上传文件的请求
HandlerExecutionChain mappedHandler = getHandler(processedRequest)
获取Handler,返回值是一个HandlerExecutionChain
。先不急去看方法实现,先看下HandlerExecutionChain
的类定义。
public class HandlerExecutionChain {
private final Object handler;
private HandlerInterceptor[] interceptors;
private List<HandlerInterceptor> interceptorList;
/**
* 只包含handler的构造方法
*/
public HandlerExecutionChain(Object handler) {
this(handler, (HandlerInterceptor[]) null);
}
/**
* 包含handler和拦截器的构造方法
*/
public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList<HandlerInterceptor>();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}
/**
* 获取handler
*/
public Object getHandler() {
return this.handler;
}
/**
* 获取所有拦截器
*/
public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
}
return this.interceptors;
}
/**
* 循环调用拦截器的preHandle()方法,如果返回false,则执行拦截器的afterCompletion()方法
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
/**
* 循环调用拦截器的postHandle()方法
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
/**
* 循环调用拦截的afterCompletion()方法
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}
它主要就是对handler
和其一堆拦截器interceptors
的封装,并提供了方法来获取handler,获取拦截器和执行拦截器中的方法。
好了,接下来我们来看看getHandler(HttpServletRequest request)
方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
代码很简单,遍历之前初始化好的handlerMappings
,哪个HandlerMapping
的getHandler(HttpServletRequest request)
方法的返回值不为空,则返回谁。当然这里就与这些HandlerMapping
的顺序有关了,排在前面的肯定先遍历到。
根据我们之前的分析,这里先遍历的是RequestMappingHandlerMapping
,那我们就先来看看它的getHandler(HttpServletRequest request)
方法。但发现这个方法的实现并不在RequestMappingHandlerMapping
中,而在它的父类AbstractHandlerMapping
中。所以,我们先看看RequestMappingHandlerMapping
的继承结构。
核心类是AbstractHandlerMapping
,通过继承WebApplicationContextObjectSupport
获得了Spring容器和ServletContext
,又实现了HandlerMapping
接口,重写了接口中唯一的方法getHandler(HttpServletRequest request)
,还实现了Ordered
接口,使子类支持排序。接下来就看一下AbstractHandlerMapping.getHandler(HttpServletRequest request)
方法的源码
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
方法被设置为final
,显然不希望子类重写,说明它是一个实现的足够好的模板方法。但也给子类留下2个可以重写的方法protected abstract Object getHandlerInternal(HttpServletRequest request)
和getHandlerExecutionChain(Object handler, HttpServletRequest request)
。getHandlerInternal()
是抽象方法,完全由子类实现;getHandlerExecution()
是protected
方法,并提供了默认实现。看下源码
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
chain.addInterceptors(getAdaptedInterceptors());
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
return chain;
}
读一下逻辑,先把handler
转换成HandlerExecutionChain
,然后给HandlerExecutionChain
添加拦截器,拦截器分2种,一种是普通的都要执行的,一种是Mapped
拦截器,需要有路径匹配的。最后构造出一个包含handler和拦截器的给HandlerExecutionChain
,这和我们之前分析的HandlerExecutionChain
的作用相匹配。
那接下来就看看,该怎样获取handler呢?代码位置:AbstractHandlerMethodMapping.getHandlerInternal(HttpServletRequest request)
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
又调用了AbstractHandlerMethodMapping.lookupHandlerMethod(String lookupPath, HttpServletRequest request)
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.urlMap.get(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.handlerMethods.keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException(
"Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
}
}
代码的大致逻辑是,先精确匹配,如果没匹配到,就遍历所有方法。如果找到好几个匹配的,就找那个最匹配的,如果有2个都是最匹配的,就抛异常。
这里有一处逻辑还没分析清晰,就是如果没匹配到,就遍历所有方法,所有方法是从取到的呢?
我们注意到,AbstractHandlerMethodMapping
实现了InitializingBean
接口,那么就得重写afterPropertiesSet()
方法。我们看
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
afterPropertiesSet()
方法直接调用了initHandlerMethods()
方法,在这个方法里,detectHandlerMethodsInAncestorContexts
默认是false
,那就不找父容器了,直接从子容器里取出所有的bean,然后遍历,要对那边beanName不是以SCOPED_TARGET_NAME_PREFIX
开头的,其通过isHandler(Class<?> beanType)
方法判断返回true
的bean,做进一步处理,也就是调用detectHandlerMethods(final Object handler)
。 接下来先看isHandler(Class<?> beanType)
,它是由子类实现的RequestMappingHandlerMapping.isHandler(Class<?> beanType)
@Override
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
就是以@Controller
或@RequestMapping
注解修饰的类,也就是我们业务代码中的Controller类。
然后看下detectHandlerMethods(final Object handler)
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType =
(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
// Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
mappings.put(method, mapping);
return true;
}
else {
return false;
}
}
});
for (Method method : methods) {
registerHandlerMethod(handler, method, mappings.get(method));
}
}
从所有的Controller类中,找到匹配的方法,哪些方法是匹配的呢?看getMappingForMethod(Method method, Class<?> handlerType)
,是由子类实现的RequestMappingHandlerMapping.getMappingForMethod(Method method, Class<?> handlerType)
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
info = createRequestMappingInfo(methodAnnotation, methodCondition);
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
带有@RequestMappring
注解的方法就是我们要找的方法,找到后取注解中的属性,然后封装成RequestMappingInfo
对象。
找到这些方法后调用registerHandlerMethod(Object handler, Method method, T mapping)
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
"' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
}
this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
}
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
}
将这些方法再放到this.handlerMethods
中。
好了,也就是说,我们在处理请求匹配的方法前,其实已经准备好了所有Controller中的方法了。接下来要做的就是在addMatchingMappings()
方法中,将请求和方法做一个匹配。
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.handlerMethods.get(mapping)));
}
}
}
具体要看RequestMappingInfoHandlerMapping.getMatchingMapping(RequestMappingInfo info, HttpServletRequest request)
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingCondition(request);
}
最后RequestMappingInfo.getMatchingCondition(HttpServletRequest request)
@Override
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());
}
不再具体读了,知道思路就好了。
好了,那我们最终知道了,返回的HandlerExecutionChain
中包含的handler
是一个HandlerMethod
对象。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())
找HandlerAdapter
。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
逻辑很简单,就是遍历所有HandlerAdapter
,如果其supports()
方法返回true
,则返回此HandlerAdapter
。如果没找到,就抛异常。
那么,我们之前分析了,现在this.handlerAdapters
中有RequestMappingHandlerAdapter
, HttpRequestHandlerAdapter
, SimpleControllerHandlerAdapter
。就逐一看看谁的supports()
方法返回true
吧。
既然HandlerAdapter
是一个接口,就先来看看它的继承结构:WebApplicationObjectSupport
刚才我们分析过了,就是实现了获取容器和ServletContext
的功能,WebContentGenerator
添加了更多与请求相关的内容。AbstractHandlerMethodAdapter
还实现了Ordered
接口,用于排序。注意RequestMappingHandlerAdapter
实现了InitializingBean
接口,会初始化一些东西。
首先看下RequestMappingHandlerAdapter.supports()
方法,这个方法定义在它的父类AbstractHandlerMethodAdapter
中
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
显然handler instanceof HandlerMethod
是true
,后面还留一个抽象方法,给子类一个机会来改变这个结果。在RequestMappingHandlerAdapter
中的supportsInternal()
方法中只是简单的返回了true
。所以,我们要找的HandlerAdapter
就是RequestMappingHandlerAdapter
了。
boolean mappedHandler.applyPreHandle(processedRequest, response)
执行预处理。就是调用HandlerExecutionChain
的applyPreHandle()
,前面已经分析过了,就是循环调用拦截器的preHandle()方法。
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
要处理具体的请求了。直接调用了AbstractHandlerMethodAdapter.handle(HttpServletRequest request, HttpServletResponse response, Object handler)
。看源码
@Override
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
protected abstract ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
什么都没做,只是对handler做了强制类型转换,因为此时已经知道了handler一定是HandlerMethod
。(为什么?看AbstractHandlerMethodAdapter.supports(Object handler)
)。然后看RequestMappingHandlerAdapter
实现的handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)
。
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
// Always prevent caching in case of session attribute management.
checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
}
else {
// Uses configured default cacheSeconds setting.
checkAndPrepare(request, response, true);
}
// 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) {
return invokeHandleMethod(request, response, handlerMethod);
}
}
}
return invokeHandleMethod(request, response, handlerMethod);
}
我们忽略前面处理缓存的逻辑,this.synchronizeOnSession
默认值是false
,继续看RequestMappingHandlerAdapter.invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)
。
private ModelAndView invokeHandleMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response); ---- (1)
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ---- (2)
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ---- (3)
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ---- (4)
ModelAndViewContainer mavContainer = new ModelAndViewContainer(); ---- (5)
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); ---- (6)
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); ---- (7)
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); ---- (8)
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
}
requestMappingMethod.invokeAndHandle(webRequest, mavContainer); ---- (9)
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest); ---- (10)
}
在分析这个方法之前,回忆一下之前提到RequestMappingHandlerAdapter
实现了InitializingBean
接口,我们先看看它都初始化什么了。
@Override
public void afterPropertiesSet() {
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
initControllerAdviceCache();
}
初始化了参数解析器、初始绑定参数解析器、返回值处理器,还有Controller切面。具体就先不看了。
另外这个类还在无参构造方法中初始化了几个messageConverters
,这里也要注意一下。源码就不贴了。
核心代码逻辑分析
- (1) 将请求和响应包装到
ServletWebRequest
中。看源码
public ServletWebRequest(HttpServletRequest request, HttpServletResponse response) {
this(request);
this.response = response;
}
ServletWebRequest
是ServletRequestAttributes
的子类,前面我们分析了ServletRequestAttributes
,就是对request
的一个包装,那么在ServletWebRequest
中加入了对response
的包装。
- (2) 获得数据绑定工厂。看源码
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
// Global methods first
for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
for (Method method : entry.getValue()) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
}
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
return createDataBinderFactory(initBinderMethods);
}
简单看下逻辑
- 先获得handlerType,也就是Controller的
Class<?>
- 看缓存里有没有Controller中关于初始绑定(initBinder)的方法,如果没有,就去找一下,找到放到缓存里。其中
INIT_BINDER_METHODS
的源码是这样的
public static final MethodFilter INIT_BINDER_METHODS = new MethodFilter() {
@Override
public boolean matches(Method method) {
return AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
}
};
也就是那些带@InitBinder
的方法。 3. initBinderAdviceCache
是一个Map
,key是ControllerAdviceBean
,value是对应的方法。接下来遍历所有的initBinderAdviceCache
,判断是否能应用到这个handlerType(也就是Controller)上。如果可以的话,解析到ControllerAdviceBean
,再遍历它对应的方法,然后创建初始绑定的方法,添加到initBinderMethods
中。看下createInitBinderMethod(Object bean, Method method)
的源码
private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {
InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
return binderMethod;
}
- 遍历Controller中的初始绑定方法,也都加入到
initBinderMethods
中。 - 最后创建了一个通过
createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
创建了一个InitBinderDataBinderFactory
。看下源码
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
throws Exception {
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
这里又有一个方法getWebBindingInitializer()
,我们前面分析过一些Initializer
的接口,是为程序提供扩展点用的,这里也是,就不细说了。
- (3) 获取模型工厂。看源码
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.modelAttributeCache.get(handlerType);
if (methods == null) {
methods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelAttributeCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
// Global methods first
for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
for (Method method : entry.getValue()) {
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
}
}
for (Method method : methods) {
Object bean = handlerMethod.getBean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
不再详细分析了,和getDataBinderFactory()
类似,是对@ModelAttribute
的一些处理。
- (4) 创建请求映射的方法。看源码
private ServletInvocableHandlerMethod createRequestMappingMethod(
HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
ServletInvocableHandlerMethod requestMethod;
requestMethod = new ServletInvocableHandlerMethod(handlerMethod);
requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
requestMethod.setDataBinderFactory(binderFactory);
requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
return requestMethod;
}
代码很简单,就是把handlerMethod
封装到ServletInvocableHandlerMethod
中,然后又设置了了参数解析器this.argumentResolvers
,返回值处理器this.returnValueHandlers
,数据绑定工厂binderFactory
,参数名发现者this.parameterNameDiscoverer
。如此,我们看到,对于一个方法的从参数解析、方法定义、到返回值处理都封装在了ServletInvocableHandlerMethod
中了,接下来肯定就要调用这个类中的方法来执行方法了。ServletInvocableHandlerMethod
的继承结构很简单,就不画图了。ServletInvocableHandlerMethod
-> InvocableHandlerMethod
-> HandlerMethod
。
在继续之前,我们先看下ServletInvocableHandlerMethod
的源码。先看它的构造方法
public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
initResponseStatus();
}
private void initResponseStatus() { // 当方法上写了@ResponseStatus时,返回的时候就使用注解中的属性值
ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
if (annotation != null) {
this.responseStatus = annotation.value();
this.responseReason = annotation.reason();
}
}
HandlerMethod
主要是对Controller的方法的封装,InvocableHandlerMethod
在HandlerMethod
的基础上添加了解析参数的功能,ServletInvocableHandlerMethod
在InvocableHandlerMethod
的基础上添加了解析返回值的功能。
- (5) 创建一个空的
ModelAndViewContainer
,之后代表返回值的视图和模型都会放到这个容器里。看下ModelAndViewContainer
的源码
public class ModelAndViewContainer {
private Object view;
private boolean requestHandled = false;
private final ModelMap defaultModel = new BindingAwareModelMap();
......
}
就是对处理中的model和view的一个记录,为什么说处理中呢?因为这里还有一个requestHandled
表明是否请求处理完了,所以这个对象是对方法处理中的model和view的记录,这点要和后面要讲到ModelAndView
类区分一下,ModelAndView
就是d对ModelAndViewContainer
中最后的结果做一个交接,得到方法处理完成后model和view的终态。
view我们是没法控制类型的,因为不知道业务代码最终的返回值是什么。但model是可以控制的,就是一个键值对。那看一下ModelMap
相关的类继承关系
其实就是一个LinkedHashMap
,不再细致分析了。
- (6) 从FlashMap中获取属性,添加到
ModelAndViewContainer
中。这个比较简单,就不看源码了。 - (7) 初始化模型。会调用那些
@ModelAttribute
的方法,将其返回值加入到Model中。源码不贴了。不过,从这里可以看到,每次调用每个HandlerMethod
之前,都会先调用@ModelAttribute
修饰的方法。 - (8) 是否忽略重定向中默认的模型,
this.ignoreDefaultModelOnRedirect
的默认值是false
。 - (9) 调用和处理请求。果然是调用
ServletInvocableHandlerMethod
中某个方法执行的。看下源码
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) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
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;
}
}
先大致看下逻辑
- 还是没有自己去调用,委托给
invokeForRequest()
方法去调用了,得到了返回值。 - 向
ServletWebRequest
中设置响应状态,之前分析了,ServletWebRequest
中封装了请求和响应。 - 如果返回值是
null
,做一系列判断,并且标记请求已处理完成。结束方法。 - 如有
responseReason
,就标记请求已处理完成。结束方法。 - 如果没有被3、4结束掉,那么标记请求还没处理完。
- 处理返回值。 逻辑很清楚,看几个重要的方法。
invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs)
这个方法在ServletInvocableHandlerMethod
的父类InvocableHandlerMethod
中,看源码
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Invoking [");
sb.append(getBeanType().getSimpleName()).append(".");
sb.append(getMethod().getName()).append("] method with arguments ");
sb.append(Arrays.asList(args));
logger.trace(sb.toString());
}
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}
只有2个核心逻辑:
- 解析参数
- 传入解析后的参数,执行方法调用,得到返回值 先看是如何解析参数的
private Object[] getMethodArgumentValues(NativeWebRequest request, 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);
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}
}
return args;
}
代码逻辑:先获取到这个方法的所有参数,然后遍历。尝试使用parameterNameDiscoverer
解析,如果解析的值不为空就解析成功了,继续下一个;如果没成功,则使用参数解析器argumentResolvers
去解析,原则是谁支持supportsParameter()
谁解析resolveArgument()
,解析失败抛异常。如果最终都没能解析,抛异常。
SpringMVC默认注册的参数解析器在RequestMappingHandlerAdapter.getDefaultArgumentResolvers()
方法中可以查看到
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
很多啊,我们这里看几个常用的参数解析器。
- 注解注入
Model
接口的 --ModelMethodProcessor
- 根据
@RequestParam
注解解析的 --RequestParamMethodArgumentResolver
- 从请求中直接根据key直接注入value的 --
RequestParamMethodArgumentResolver
- 把请求参数封装成一个对象的 --
ServletModelAttributeMethodProcessor
- 根据
@PathVariable
注解解析的 --PathVariableMethodArgumentResolver
- 直接注入
HttpServletRequest
对象的 --ServletRequestMethodArgumentResolver
- 直接注入
HttpServletResponse
对象的 --ServletResponseMethodArgumentResolver
- 根据
@RequestBody
注解解析的 --RequestResponseBodyMethodProcessor
代码就不再细致读了。
另外,细心的你可能发现了,RequestParamMethodArgumentResolver
和ServletModelAttributeMethodProcessor
分别加入了两次,只是构造方法带的参数一个是true
,一个是false
。那么true
和false
是控制什么的呢?
RequestParamMethodArgumentResolver
构造方法中的第2个参数是useDefaultResolution
,翻译过来是“使用默认的解析方式”,其实是说是否处理简单类型,false
表示不处理,true
表示处理。
也就是说,如果useDefaultResolution
是false
,就只处理那些参数前面带@RequestParam
注解的;
如果是useDefaultResolution
是true
,也可以处理那些简单类型。简单类型都包括什么呢?RequestParamMethodArgumentResolver.supportsParameter(MethodParameter parameter)
中有一行return BeanUtils.isSimpleProperty(paramType);
,明白了吧?ServletModelAttributeMethodProcessor
构造方法中的参数是annotationNotRequired
,翻译过来是“是否必须有注解”。是true
,则只解析带@ModelAttribute
的参数;是false
,则可以解析那种POJO类型的参数。
其实这么说也不是太准确,由于new ServletModelAttributeMethodProcessor(true)
是最后一个参数解析器,如果前面的解析器都不支持,就只能由它来处理了,通常情况下,由它去解析一个POJO参数。
这里多说一句。我们在spring-mvc.xml
中还注册了一个参数解析器,直接贴源码了
public class SubjectMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Subject.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
return SubjectUtils.getSubject();
}
}
是干什么的,就不用解释了吧?它在resolvers.addAll(getCustomArgumentResolvers());
中被加入到参数解析器List中去。
这里有个问题要思考一下,getCustomArgumentResolvers()
也就是直接返回this.customArgumentResolvers
。那么设置this.customArgumentResolvers
值的地方只有一处,那就是setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers)
方法。在哪里调用了这个方法呢?我们使用Eclipse的"Open Call Hierarchy"功能,看到它被org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.requestMappingHandlerAdapter()
调用了,但显然这是使用Java Config配置时才会执行的代码。那么,如果不是编译时指定了调用关系,就一定是通过反射调用的。这个思路,对于分析代码间的调用关系至关重要。那么,接下来的问题是,如果是反射调用的,这也就不好找了。还记得我们之前讲过命名空间吗?SubjectMethodArgumentResolver
就是通过<mvc:argument-resolvers />
配置的,自然就去相对应的解析类中找啊。具体我就不一步步去找了,调用是在这里AnnotationDrivenBeanDefinitionParser.getArgumentResolvers(Element element, ParserContext parserContext)
private ManagedList<?> getArgumentResolvers(Element element, ParserContext parserContext) {
Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
if (resolversElement != null) {
ManagedList<BeanDefinitionHolder> argumentResolvers = extractBeanSubElements(resolversElement, parserContext);
return wrapWebArgumentResolverBeanDefs(argumentResolvers, parserContext);
}
return null;
}
然后这个方法的返回值,通过BeanWrapper
的方式注入到customArgumentResolvers
属性中,也就是通过反射,调用了RequestMappingHandlerAdapter.setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers)
,代码如下
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);
if (argumentResolvers != null) {
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
好,接着看InvocableHandlerMethod.doInvoke(Object... args)
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
throw new IllegalStateException(getInvocationErrorMessage(ex.getMessage(), args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
throw new IllegalStateException(msg, targetException);
}
}
}
这才是最终方法调用的核心中的核心,其实超级简单,就是通过反射调用方法。好了,这时我们获得了方法调用完成的返回值了。
setResponseStatus(ServletWebRequest webRequest)
直接上源码
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
if (this.responseStatus == null) {
return;
}
if (StringUtils.hasText(this.responseReason)) {
webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
}
else {
webRequest.getResponse().setStatus(this.responseStatus.value());
}
// to be picked up by the RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
}
this.responseStatus
和this.responseReason
的值,我们之前在initResponseStatus()
中分析了,如果方法上有@ResponseStatus
注解,这两个变量才有值。
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest)
实际调用的是HandlerMethodReturnValueHandlerComposite.handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
。看源码
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +
returnType.getGenericParameterType() + "]");
}
if (returnValueHandler.supportsReturnType(returnType)) {
return returnValueHandler;
}
}
return null;
}
就是先找到那个能处理返回值的HandlerMethodReturnValueHandler
,再调用它的handleReturnValue()
方法。分析到这就没法往下看了,因为要根据返回值的具体情况具体分析了。
SpringMVC默认的返回值解析器是在RequestMappingHandlerAdapter.getDefaultReturnValueHandlers()
方法中可以查看到
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch-all
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
}
else {
handlers.add(new ModelAttributeMethodProcessor(true));
}
return handlers;
}
那我们就看2个常用返回值处理。一种是返回视图名,然后返回一个JSP。一种是方法加了@ResponseBody
注解,返回Json的。
- ViewNameMethodReturnValueHandler
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class.equals(paramType) || String.class.equals(paramType));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
else if (returnValue instanceof String) {
String viewName = (String) returnValue;
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
/**
* Whether the given view name is a redirect view reference.
* @param viewName the view name to check, never {@code null}
* @return "true" if the given view name is recognized as a redirect view
* reference; "false" otherwise.
*/
protected boolean isRedirectViewName(String viewName) {
return viewName.startsWith("redirect:");
}
}
代码很简单,直接实现了HandlerMethodReturnValueHandler
接口
- 判断是否支持。支持void和String的返回值
- 处理。返回值即是视图名。
- RequestResponseBodyMethodProcessor 嗯?它怎么又出现了,在解析
@RequestBody
参数时就有它。RequestResponseBodyMethodProcessor
就是带有2个功能,解析@RequestBody
的参数和@ResponseBody
的返回值。从它的继承结构中也可以证实这一点。
这里我们重点关注返回值的部分。
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
returnType.getMethodAnnotation(ResponseBody.class) != null);
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setRequestHandled(true);
if (returnValue != null) {
writeWithMessageConverters(returnValue, returnType, webRequest);
}
}
代码很简单,只说handleReturnValue()
,首先把ModelAndViewContainer
中的请求处理完成标识设置为true
。然后调用了writeWithMessageConverters()
方法,这个方法在其父类AbstractMessageConverterMethodProcessor
中
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest
webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
接下来看AbstractMessageConverterMethodArgumentResolver.createInputMessage(NativeWebRequest webRequest)
protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return new ServletServerHttpRequest(servletRequest);
}
将原始的HttpServletRequest
封装到ServletServerHttpRequest
中。
然后看AbstractMessageConverterMethodProcessor.createOutputMessage(NativeWebRequest webRequest)
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
return new ServletServerHttpResponse(response);
}
将原始的HttpServletResponse
封装到ServletServerHttpResponse
中。
最后看AbstractMessageConverterMethodProcessor.writeWithMessageConverters(T returnValue, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
Class<?> returnValueClass = returnValue.getClass();
HttpServletRequest servletRequest = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
return;
}
}
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
经过一系列的处理,最后是选择某个messageConverter
,可以先通过canWrite()
方法,看行不行,行的话,调用这个messageConverter
的write()
。关于HttpMessageConverter
的问题,前面我们分析了,是在RequestMappingHandlerAdapter
的无参构造方法里初始化的。
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList<HttpMessageConverter<?>>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
但是!这就证明,到了AbstractMessageConverterMethodProcessor.writeWithMessageConverters()
中,还是这4个吗?还是命名空间,想到了吧?加载<mvc:annotation-driven />
可能会调用RequestMappingHandlerAdapter.setMessageConverters(List<HttpMessageConverter<?>> messageConverters)
方法的。好,我们去看看。
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 只保留核心代码
ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
return null;
}
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
ManagedList<? super Object> messageConverters = new ManagedList<Object>();
if (convertersElement != null) {
messageConverters.setSource(source);
for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
messageConverters.add(object);
}
}
if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
messageConverters.setSource(source);
messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));
RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
messageConverters.add(stringConverterDef);
messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));
if (romePresent) {
messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
}
if (jaxb2Present) {
messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
if (jackson2Present) {
messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
}
else if (jacksonPresent) {
messageConverters.add(createConverterDefinition(
org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
}
}
return messageConverters;
}
果然.. 其实RequestMappingHandlerAdapter
中很多HttpMessageConverter
呢。后续就不再去分析了,处理ResponseBody
消息转换的类应该是MappingJackson2HttpMessageConverter
(如果你加入了Jackson2的jar的话)。
- (10) 返回ModelAndView对象
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
就是把ModelAndViewContainer
中一些信息转移到ModelAndView
中。如果请求已经处理完了(比如返回json),就返回null
。如果是重定向的,就把flashMap中的属性,放到请求的属性里。
applyDefaultViewName(request, mv)
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
}
如果没视图就设置一个默认的。什么时候会没视图呢?前面分析了,当方法返回值是void时,就没有视图。见public class ViewNameMethodReturnValueHandler.supportsReturnType(MethodParameter returnType)
。那么,看看怎么获取默认的视图吧。
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
return this.viewNameTranslator.getViewName(request);
}
在DispatcherServlet
的initRequestToViewNameTranslator(ApplicationContext context)
中初始化了viewNameTranslator
了,实现类是DefaultRequestToViewNameTranslator
,细节就不再看了。
mappedHandler.applyPostHandle(processedRequest, response, mv)
调用所有拦截器的postHandle()
方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
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);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
- 如果有异常,就处理异常。
- 如果
ModelAndView
不为空,调用render()
方法。 - 最后通过
mappedHandler.triggerAfterCompletion(request, response, null);
调用所有拦截器的afterCompletion()
方法。
重点看下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.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) { ---- [1]
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); ---- [2]
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
view.render(mv.getModelInternal(), request, response); ---- [3]
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
核心代码逻辑分析
- [1] 判断视图是否是引用的(isReference,这个含义不好翻..)?看下源码
public boolean isReference() {
return (this.view instanceof String);
}
显然,这里是返回true
。即表明接下来要通过视图解析器解析。
- [2] 解析视图名
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
也是从一堆viewResolvers
中找一个能解析出的视图不为空的,就返回它。那我们现在有几个viewResolvers
呢?其实,就一个,就是我们在spring-mvc.xml文件中配置的。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/views/"
p:suffix=".jsp" />
那我们就去InternalResourceViewResolver
中看看resolveViewName()
。看方法实现之前,先看下继承结构。
继承了WebApplicationContextSupport
,获得了Spring容器和ServletContext
,实现Ordered
接口,可以排序。看下ViewResolver
接口的定义
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
只有一个方法,根据视图名,解析出一个View
对象。在spring-mvc.xml,我们配置了一个JstlView
,看下它的继承结构:
注意InitializingBean
接口,会初始化一些东西。
好,看下InternalResourceViewResolver.resolveViewName()
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
跳过那些跟缓存相关的逻辑,重点关注view = createView(viewName, locale);
。具体由子类实现。 看UrlBasedViewResolver.createView(String viewName, Locale locale)
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
明确了两种处理方式:
- 以
redirect:
开头的,使用RedirectView
- 以
forward:
开头的,使用InternalResourceView
如果都不是,就调用父类的createView()
方法。那么就是AbstractCachingViewResolver.createView(String viewName, Locale locale)
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
protected abstract View loadView(String viewName, Locale locale) throws Exception;
呃.. 什么都没做,又交给子类了。看UrlBasedViewResolver.loadView(String viewName, Locale locale)
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
private View applyLifecycleMethods(String viewName, AbstractView view) {
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}
重点看buildView(String viewName)
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}
viewClass
肯定就是我们配置的org.springframework.web.servlet.view.JstlView
了,通过反射实例化。url
就是根据前缀+试图名+后缀拼接起来的。contentType
没指定,肯定就是null
了。
下面就是设置一些属性了。好了,终于得到View
了。
- [3] 渲染视图。看源码
AbstractView.render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, request, response);
}
只有3行核心代码,胜利在望了~~
- 创建合并的输出模型
protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
}
return mergedModel;
}
逻辑比较清楚,就是把staticAttributes
、pathVars
和业务代码中的Model(Map
形式)合并到一个LinkedHashMap
中。
- 准备响应
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
if (generatesDownloadContent()) {
response.setHeader("Pragma", "private");
response.setHeader("Cache-Control", "private, must-revalidate");
}
}
protected boolean generatesDownloadContent() {
return false;
}
判断是不是需要下载,默认是false
。而子类JstlView
也没有重写此方法。
- 渲染合并的输出模型 在
AbstractView
中,这是一个抽象方法,留给了子类实现。看``
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request); ---- (1)
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, requestToExpose); ---- (2)
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose); ---- (3)
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response); ---- (4)
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath); ---- (5)
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) { ---- (6)
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response); ---- (7)
}
}
- (1) 直接看源码
protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {
if (this.exposeContextBeansAsAttributes || this.exposedContextBeanNames != null) {
return new ContextExposingHttpServletRequest(
originalRequest, getWebApplicationContext(), this.exposedContextBeanNames);
}
return originalRequest;
}
this.exposeContextBeansAsAttributes
默认值是false
,而且this.exposedContextBeanNames
也是null
,所以直接返回原始的request
对象。
- (2) 看源码
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
for (Map.Entry<String, Object> entry : model.entrySet()) {
String modelName = entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
if (logger.isDebugEnabled()) {
logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
"] to request in view with name '" + getBeanName() + "'");
}
}
else {
request.removeAttribute(modelName);
if (logger.isDebugEnabled()) {
logger.debug("Removed model object '" + modelName +
"' from request in view with name '" + getBeanName() + "'");
}
}
}
}
把model
的key-value,都通过request.setAttribute(key, value)
放到request
中。
- (3) 看源码 在
InternelResourceView
中,这是一个空方法,但JstlView
重写了此方法。
@Override
protected void exposeHelpers(HttpServletRequest request) throws Exception {
if (this.messageSource != null) {
JstlUtils.exposeLocalizationContext(request, this.messageSource);
}
else {
JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
}
}
给请求绑定了处理国际化资源的对象。
- (4) 准备渲染。艾玛,终于要渲染了..
protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
throws Exception {
String path = getUrl();
if (this.preventDispatchLoop) {
String uri = request.getRequestURI();
if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
"to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
"(Hint: This may be the result of an unspecified view, due to default view name generation.)");
}
}
return path;
}
获取path,path之前计算过了,就是前缀+视图名+后缀。这里说下preventDispatchLoop
,默认值是true
,变量名翻译过来叫"避免循环分发",意思是,比如你请求的url是/test,但返回的path的结果还是/test,那SpringMVC肯定还要再去转发,这就形成循环分发了。
- (5) 获取请求分发器。
protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
return request.getRequestDispatcher(path);
}
这就是纯Servlet
的基础知识了。
- (6) 看源码
protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
return (this.alwaysInclude || WebUtils.isIncludeRequest(request) || response.isCommitted());
}
不多说了,3个都是false
。
- (7) 直接是
Servlet
的基础API了,请求转发。
至此,SpringMVC的代码就全部分析完了。