Spring原始碼解析(四):SpringMVC原始碼解析
SpringMVC是Spring一個非常重要的模組,從大體上看,在使用SpringMVC的時候,需要在web.xml中配置DispatcherServlet,這個DispatcherServlet可以看成是一個前端控制器的實現,web請求會通過它分發給各個對應的Controller,然後會看到ModelAndView資料的生成,並把ModelAndView資料交給對應的View檢視來進行呈現。下面我們來對SpringMVC的設計進行詳細的分析。
一、根上下文在容器中的啟動
SpringMVC作為Spring的一個模組,也是建立在IOC容器的基礎上的,首先我們來了解下Spring的IOC容器是如何在Web環境中載入並起作用的。如果要在web環境中使用IOC容器,需要為IOC容器設計一個啟動過程,把IOC容器匯入,這個啟動的過程是與web容器的啟動過程整合在一起的,我們以tomcat作為web容器來進行分析。先來看一個SpringMVC相關的web.xml中的配置
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
在這個web.xml中,定義了一個Servlet物件——DispatcherSerlvet,這個類是SpringMVC的核心類,它的實現原理我們後面再來分析,context-param引數的配置用來指定SpringIOC容器讀取Bean定義的xml檔案的路徑。在這個檔案中,一般用來定義Spring應用的Bean配置。最後,我們可以看到一個監聽器的配置——ContextLoaderListener,這個類作為SpringMVC的啟動類,負責完成IOC容器在web環境中的啟動工作,它是與web容器的生命週期相關聯的,下面我們來具體看看IOC容器在web容器中的啟動過程是如何實現的。
容器的啟動過程是與ServletContext相伴而生的,由ContextLoaderListener啟動的上下文為根上下文,此外,還有一個與Web MVC相關的上下文用來儲存控制器(DispatcherServlet)需要的MVC物件,作為根上下文的子上下文,構成一個層次化的上下文體系。
ContextLoaderListener實現了ServletContextListener介面,這個接口裡的函式會結合Web容器的生命週期被呼叫,因為ServletContextListener是ServletContext的監聽者,如果ServletContext發生變化會觸發相應的事件,這些事件包括在伺服器啟動,ServletContext被建立的時候回觸發contextInitialized方法;服務關閉時,ServletContext被銷燬的時候觸發contextDestroyed方法。IOC容器的初始化就是在contextInitialized方法中完成,這個方法的程式碼如下:
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
initWebApplicationContext方法的具體實現在的ContextLoaderListener父類ContextLoader中
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//如果已經有根上下文存在
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
//初始化上下文
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//對上下文的屬性進行一些設定並呼叫refresh方法初始化
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//將上下文物件設定到ServletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
首先來看看createWebApplicationContext方法中如何初始化根上下文
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//判斷作為IOC容器的類的型別
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//例項化IOC容器
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
//讀取對contextClass的配置
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
//如果配置了contextClass那麼就使用這個配置的class,當然前提是這個class可用
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
//否則使用預設的類,這個預設的上下文的類是org.springframework.web.context.support.XmlWebApplicationContext,
//是在ContextLoader.properties檔案中配置的
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
然後看看configureAndRefreshWebApplicationContext方法中對於容器的引數的一些配置,方法程式碼如下
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
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
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
//將ServletContext設定到容器中
wac.setServletContext(sc);
//設定配置檔案的位置引數
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// 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(sc, null);
}
customizeContext(sc, wac);
//呼叫refresh方法啟動容器的初始化
wac.refresh();
}
這就是IOC容器在web容器中的啟動過程,在初始化這個上下文之後,該上下文會被儲存到ServletContext中,這樣就建立了一個全域性的關於整個應用的根上下文。在後面我們還會看到,在DispatcherServlet進行自己的上下文初始化時,會將這個根上下文設定為DispatcherServlet自帶的上下文的雙親上下文。
二、Spring MVC的設計與實現
在前面的web.xml中我們看到,除了配置ContextLoaderListener之外,還要對DispatcherServlet進行配置。DispatcherServlet十分重要,它可以說是Spring MVC實現中最核心的部分,它作為一個前端控制器,所有的web請求都需要通過它來處理,進行轉發、匹配、資料處理後,轉由頁面進行展現,它的設計與分析也是下面分析Spring MVC的一條主線。
DispatcherServlet的工作大致可以分為兩個部分:一是初始化部分,DispatcherServlet通過這部分功能對MVC模組的其他部分進行了初始化,比如handlerMapping、ViewResolver等;另一個是對http請求進行響應,DispatcherServlet對請求的轉發及處理在這部分功能中完成。
2.1 DispatcherServlet的啟動及初始化
我們先來分析DispatcherServlet的初始化過程,作為一個Servlet,DispatcherServlet啟動時首先會呼叫它的init方法,這個方法的實現在它的父類HttpServletBean中,程式碼如下
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 獲取Servlet的初始化引數,對Bean的屬性進行設定
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// 呼叫子類的initServletBean方法進行具體的初始化,主要的初始化過程在這個方法中
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
initServletBean的實現在DispatcherServlet的父類FrameworkServlet中
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();
initFrameworkServlet();
}
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");
}
}
protected WebApplicationContext initWebApplicationContext() {
//獲取之前在ContextLoaderListener中儲存到ServletContext中的根上下文
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果當前類中的webApplicationContext不為空
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//將當前根上下文作為雙親上下文
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);
}
//設定上下文的相關屬性並呼叫refresh方法觸發初始化
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
//具體的初始化的過程
wac = createWebApplicationContext(rootContext);
}
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);
}
//將當前建立的上下文存到Servletcontext中,屬性名為
//org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//獲取需要初始化的上下文的對應的類,預設是XmlWebApplicationContext
Class<?> contextClass = getContextClass();
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);
//設定一些相關的屬性並將根上下文設定為雙親上下文,這樣一來根上下文中
//的Bean也可以被DispatcherServlet中的上下文使用
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
//設定相關屬性並呼叫refresh方法進行容器的初始化
configureAndRefreshWebApplicationContext(wac);
return wac;
}
configureAndRefreshWebApplicationContext與前面的ContextLoader中的configureAndRefreshWebApplicationContext有些類似
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//對上下文的屬性進行設定
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
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()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//呼叫refresh方法觸發容器的初始化
wac.refresh();
}
初始化的過程中還會呼叫DispatcherServlet中的onRefresh方法,onRefresh方法中會呼叫DispatcherServlet的initStrategies,這個方法會觸發整個Spring MVC框架的初始化
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);
}
對於上面的方法名稱,很容易理解。以initHandlerMappings方法為例來看看這個初始化過程,方法程式碼如下
private void initHandlerMappings(ApplicationContext context) {
//這個handlerMappings是個List,用來存放所有的HandlerMapping
this.handlerMappings = null;
//匯入所有的HandlerMapping,detectAllHandlerMappings預設為真,即預設的從所有的IOC容器中取
if (this.detectAllHandlerMappings) {
// 從容器中取得所有的HandlerMapping,包括父級容器,如果配置了<mvc:annotation-driven/>,
//那麼這裡預設是BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,
//matchingBeans的鍵分別是它們完整的類名
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {//也可以根據名稱從當前的IOC容器中取得handlerMapping
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.
}
}
// 如果都沒有取到的話,那麼從DispatcherServlet.properties中取預設設定的HandlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
這樣,DispatcherServlet就把所有的HandlerMapping初始化到了handlerMapping變數中。其他的初始化過程與HandlerMapping比較類似,都是從IOC容器中讀取配置。經過這些過程,DispatcherServlet的初始化就全部完成了。
2.2 SpringMVC的分發請求
在分析SpringMVC對請求的分發之前,我們先來看看HandlerMapping的設計原理,HandlerMapping在SpringMVC分發請求的過程中起著十分重要的作用,一般每個handlerMapping都持有一系列從url請求到Controller的對映,以RequestMappingHandlerMapping為例,RequstMappingHandlerMapping是我們經常會用到的一個HandlerMapping,當我們配置<mvc:annotation-driven/>時,DispatcherServlet中會用它作為HandlerMapping。
RequstMappingHandlerMapping的父類實現了InitializingBean,所以通過getBean例項化它時會呼叫它的afterPropertiesSet方法,該方法程式碼如下
public void afterPropertiesSet() {
//例項化一個BuilderConfiguration物件併為它設定一些屬性
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
//呼叫父類的afterPropertiesSet方法
super.afterPropertiesSet();
}
該方法中會呼叫它的父類AbstractHandlerMethodMapping中的afterPropertiesSet方法
public void afterPropertiesSet() {
//該方法用來將請求路徑與控制器中的方法進行匹配
initHandlerMethods();
}
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
//獲取上下文物件中所有的beanName
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
//遍歷所有的bean
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
//獲取它們的型別
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
//判斷是否為Controller或者RequestMapping註解,如果是則取得類中處理請求的方法
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
//獲取類的型別
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//這個map的key是Controller中的Method,value是RequestMappingInfo,
//MetadataLookup中的方法會在selectMethods中被回撥
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
//遍歷methods並註冊處理請求的方法
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
下面來看看selectMethods中是如何獲取到RequestMappingInfo的
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
Class<?> specificHandlerType = null;
//新增處理請求的bean(Controller)
if (!Proxy.isProxyClass(targetType)) {
handlerTypes.add(targetType);
specificHandlerType = targetType;
}
handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));
//遍歷這些bean
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
//doWithMethods中會呼叫MethodCallback的doWith方法對bean中的所有方法進行處理,包括父類的方法
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
//呼叫metadataLookup的inspect方法獲取RequestioMappingInfo
T result = metadataLookup.inspect(specificMethod);
//將方法新增到map中
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
具體取得RequestMappingInfo的過程在RequestMappingHandlerMapping的getMappingForMethod方法中
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class<?> ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
//這裡是真正生成RequestMappingHandlerMapping的地方
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, RequestCondition<?> customCondition) {
//這裡使用建造者模式構造RequestMappingHandlerMapping,build方法的實現在
return RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name())
.customCondition(customCondition)
.options(this.config)
.build();
}
public RequestMappingInfo build() {
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions());
return new RequestMappingInfo(this.mappingName, patternsCondition,
new RequestMethodsRequestCondition(methods),
new ParamsRequestCondition(this.params),
new HeadersRequestCondition(this.headers),
new ConsumesRequestCondition(this.consumes, this.headers),
new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition);
}
到這裡,我們不難看出RequestMappingInfo實際上是封裝了請求路徑到對應的處理的方法的對映。然後回到方法detectHandlerMethods中,接下來就是將處理請求的方法進行註冊
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//這個mappingRegistry是一個MappingRegistry的例項
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
//將對映與對應方法存起來
this.mappingLookup.put(mapping, handlerMethod);
//將請求路徑與對映物件存起來
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
//獲取RequestMappingInfo的name,將name與method存起來
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
//將對映物件和對映註冊物件存起來
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
這樣,所有請求的路徑到Controller中處理請求的方法的對映都被註冊到了RequestMappingHandlerMapping中。下面我們就來具體看看SpringMVC中對請求的分發處理的過程。讓我們重新回到DispatcherServlet中,前面說過,web請求的分發主要通過它來完成,作為一個Servlet,DispatcherServlet通過doService方法來相應http請求,下面我們來看看doService方法的實現
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));
}
}
}
// 將一些引數設定到request請求中
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
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);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
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);
multipartRequestParsed = (processedRequest != request);
// 根據請求獲取HandlerExecutionChain,這個mappedHandler是一個HandlerExecutionChain物件,
//HandlerExecutionChain中設定了一個攔截器鏈,用來對handler進行增強,handler實際就是請求對應的Controller中的處理方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 獲取對應的HandlerAdapter,如果是配置了<mvc:annotation-driven/>,那麼這裡是RequestMappingHandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 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;
}
}
//呼叫HandlerExecutionChain中的interceptor進行前置處理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 呼叫HandlerAdapter的handler方法對handler進行處理並將處理的結果封裝到ModelAndView中,為檢視展現提供資料
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果檢視為空那麼設定一個預設的檢視
applyDefaultViewName(processedRequest, mv);
//呼叫HandlerExecutionChain中的interceptor進行後置處理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//對ModelAndView檢視的展現
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
我們來看看getHandler中是如何獲取HandlerExecutionChain的
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//遍歷handlerMappings,呼叫HandlerMapping的getHandler方法獲取HandlerExecutionChain,
//在其中一個取到了立即返回
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;
}
該方法的實現在AbstractHandlerMapping中,是一個final方法
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);
}
//獲取HandlerExecutionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandlerInternal方法的實現在AbstractHandlerMethodMapping類中實現
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//獲取請求的路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
//根據請求路徑獲取對應的處理請求的HandlerMethod
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 + "]");
}
}
//將找到的HandlerMethod返回
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
//這個directPathMatches實際上是一個RequestMappingInfo的集合,getMappingsByUrl方法會從之前
//RequestMappingHandlerMapping初始化時快取到mappingRegistry中的urlLookup裡取得對應的RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
//從RequestMappingHandlerMapping初始化時快取到mappingRegistry中的mappingLookup裡獲取對應的HandlerMethod,
//並和RequestMappingHandlerMapping、RequestMappingInfo一起封裝到matches中
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
//返回一個最匹配的HandlerMethod
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) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
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(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
getHandlerExecutionChain的程式碼如下
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//建立一個HandlerExecutionChain
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//獲取請求的路徑
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//遍歷adaptedInterceptors,這個adaptedInterceptors是一個HandlerInterceptor的集合,集合中的interceptor是在初始化時新增進去的
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
//如果攔截器與路徑匹配的話就將攔截器新增到HandlerExecutionChain的interceptorList中
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
這樣就取到了所需要的HandlerExecutionChain,然後回到DispatcherServlet的doDispatch方法中,接下來我們看看這個方法中如何通過呼叫HandlerAdapter的handle方法獲取到ModelAndView,以RequestMappingHandlerAdapter為例,如果配置了<mvc:annotation-driven/>,就會以這個類作為HandlerAdapter的實現,具體的獲取ModelAndView的過程在handleInternal方法中
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
//呼叫處理請求的handlerMethod,將方法中的引數及返回的檢視封裝到ModelAndView中
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
再回到doDispatcher中,然後會呼叫processDispatchResult方法對檢視進行展現
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);
}
}
// 這裡是展現檢視的地方
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);
}
}
具體的展現檢視的方法在render方法中
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 從request中獲取locale資訊並設定到response中
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// 從ModelAndView物件中獲取檢視名稱並用ViewResolver解析得到檢視物件
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// 如果ModelAndView中已經包含檢視物件那麼直接取
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 {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
//呼叫檢視物件的render方法展現檢視
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
SpringMVC中封裝了很多檢視,比如JSP檢視,FreeMarker檢視,Velocity檢視,Excel和PDF檢視等等,我們來看看我們平常用的最多的JSP檢視是如何展現的。具體的render方法的實現在AbstractView中
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);
}
//將model中的資料都收集到一個map中
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//展現模型資料到檢視中
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
renderMergedOutputModel的實現在InternalResourceView中
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 將模型的資料設定到HTTPServletRequest中
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// 獲取檢視頁面的內部資源路徑
String dispatcherPath = prepareForRendering(request, response);
// 獲取RequestDispatcher
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
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(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
else {
// 將請求到資源上,比如JSP頁面,JSP頁面的展現由web容器負責,view只是轉發請求
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}
至此,SpringMVC就完成了將HTTP請求通過DispatcherServlet轉發給頁面呈現的過程。總結一下,SpringMVC的實現由以下三個步驟完成:
1、初始化handlerMapping時,http請求到Controller之間的對映會被載入到HandlerMapping中
2、當接收到http請求之後,DispatcherServlet會根據具體的URL資訊,在HandlerMapping中查詢,然後得到對應的HandlerExecutionChain。HandlerExecutionChain中封裝了對應的處理請求的HandlerMethod,然後HandlerAdapter會執行處理請求的方法,生成需要的ModelAndView物件。
3、得到ModelAndView物件之後,DispatcherServlet把獲取的模型資料交給特定的檢視物件,從而完成這些資料的呈現工作,呈現的過程由對應的檢視類的render方法完成。