SpringMVC 框架的理解与分析
1.介绍
Spring框架是IOC容器思想的一个具体实现 ,而SpringMVC则是在SpringIOC 的容器基础上来进一步扩展的用于Web 环境的框架。
Spring IOC 是Spring框架内部的一个模块,而如果IOC 容器用于Web 环境的话,则容器启动初始化 需要伴随着Web容器(Tomcat/Weblogic/jboss)的启动而启动的,进而把IOC容器加载到Web环境中去;
下面是以tomcat 作为web 容器的例子进行分析 ,在tomcat中web.xml是应用的部署描述文件
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 配置Spring MVC Servlet -->
<servlet>
<servlet-name>yyqdata</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>yyqdata</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
1.1 ContextLoaderListener 简述
首先我们看看ContextLoaderListener
这里先简单的说一下ContextLoaderListener
看传承结构
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
package javax.servlet;
import java.util.EventListener;
/**
* Implementations of this interface receive notifications about
* changes to the servlet context of the web application they are
* part of.
* To receive notification events, the implementation class
* must be configured in the deployment descriptor for the web
* application.
* @see ServletContextEvent
* @since v 2.3
*/
public interface ServletContextListener extends EventListener {
ContextLoaderListener
(spring实现的)其实是servlet 规范的一个实现(不懂的可以网上搜索查看servlet 规范 的监听器机制)。
它的主要功能是关联到web服务器(tomcat)的生命周期,何时启动?进而会调用它的方法contextInitialized
会对spring 的IOC 容器触发初始化工作 根据配置的applicationContext.xml
1.2 DispatcherServlet 简述
DispatcherServlet
是Spring MVC 核心类用于请求的转发 在xml中的定义完全符合Servlet 规范的配置,用于拦截请求,后续会详细讲解
contextInitialized
初始化方法中 会传入一个对象就是 event.getServletContext()==ServletContext
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
ServletContext
(也是属于Servlet 规范的内容) 该对象就是web 容器和Spring 接口的一个耦合。实现请求转发到DispatcherServlet
。
备注:Web应用部署好以后,Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。你可以把ServletContext看成是一个全局对象,一个Web应用可能有多个Servlet,这些Servlet可以通过全局的ServletContext来共享数据,这些数据包括Web应用的初始化参数,Web应用目录下的文件资源等。因为ServletContext持有所有Servlet实例,还可以通过它来实现Servlet请求的转发。
2.上下文在web环境中的启动
测试代码
package com.sy.controller;
import com.sy.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author SFF
* @version 1.0.0
* @description
* @date 2020/10/26 - 23:11
*/
@Controller
@RequestMapping("/demo")
public class DemoController {
@Autowired
private DemoService demoService;
public DemoController() {
System.out.println("chushihua");
}
@RequestMapping("/index")
public String index(){
return "/demo/index";
}
}
package com.sy.service;
import org.springframework.stereotype.Service;
/**
* @author SFF
* @version 1.0.0
* @description
* @date 2020/10/28 - 22:08
*/
@Service
public class DemoService {
public DemoService() {
System.out.println("DemoService");
}
public void fun() {
System.out.println("DemoService:fun ");
}
}
继承体系
2.1 IOC 容器的启动过程
扩一步讲解IOC 容器的启动过程,容器启动加载ContextLoaderListener
然后调用方法 contextInitialized
在该方法中携带了一个ServletContext
对象
直接贴代码
1.开始初始化根(父)容器 也就是是Spring 的IOC
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());//携带ServletContext 对象
}
2.ContextLoader
的方法initWebApplicationContext
部分核心片段
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// servletContext 的 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 在容器初始化之前是没有值得初始化之后才会赋值 后面会看到 会把上下文对象赋值到 里面去
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!");
}
.................................
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
// 创建上面类型的上下文环境 创建一个还在初始化状态的context
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//初始化容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
.............
return this.context;
}
.........
}
configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
.........
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//设置xml 文件资源位置
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//!!!!!!直接添加了一个监听器 这个监听器会在 持有的ioc 容器结束时调用监听器内部类的一个方法
//然后转到DispatacherServlet 的初始化策略
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
customizeContext(sc, wac);
//核心方法ConfigurableWebApplicationContext 刷新容器
//这里的wac 其实就是webApplicationContext
wac.refresh();
}
4.refresh
方法
public interface WebApplicationContext extends ApplicationContext
到这里想必都是知道了 refresh ()
方法才是IOC 容器真正初始化的方法 这里就直接进入了业务层代码断点
public DemoService() {
// 业务层对象被初始化 根据ApplicationConText.xml
System.out.println("DemoService");
}
到此 web 环境里面的ioc 容器初始化完成
2.2 web容器的上下文设计
在SpringIOC 容器启动的过程中 会默认使用webApplicationContext
的实现作为IOC 容器 ,而这个实现就是XmlWebApplicationContext
注: 为什么是这个呢? 在框架初始化会加载一个文件 在ContextLoader
(ContextLoaderListener
的父类)的静态代码块中 加载了文件
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
文件内容
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
现在再来看看这个 XmlWebApplicationContext
继承结构 还是重ApplicationContext
实现来的 ,XmlWebApplicationContext
主要是做的是对于Web 环境的XML 定义的一个适配,比如默认的xml 路径
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
/** Default config location for the root context */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
/** Default prefix for building a config location for a namespace */
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
/** Default suffix for building a config location for a namespace */
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
每个ApplicationContext
的具体实现,熟悉Spring 初始化的都知道 ,主要的区别还是loadBeanDefinitions
这个方法的区别 loadBeanDefinitions
方法会是在ApplicationContext
的refresh ()
中根据不同的实现来调用具体的loadBeanDefinitions
下面来看看XmlWebApplicationContext
如何加装资源文件 先看2.1 .3 中路径的已经设置
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
protected String[] getConfigLocations() {
return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}
这里就定位到了自己设置的配置文件地址
到了这里如果没有设置资源文件位置呢 不烦做个测试 会走到这个方法
@Override
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
DEFAULT_CONFIG_LOCATION
就是前面默认的资源文件位置"/WEB-INF/applicationContext.xml";
然后继而加载beanDefinition 初始化bean
3.Spring MVC的设计和实现
3.1 介绍
我们都知道Spring MVC 是Spring框架对于MVC思想的一个具体实现,对于所有的基于servlet 规范的javaweb应用,在使用Spring MVC时还需要配置一个Dispatchservlet。 它是servlet 的一个具体实现,是实现Sun公司J2EE核心模式中的前端控制器模式,是对于过来的请求处理 转发 匹配 最后页面显示。也是Spring MVC 对于web 应用的一个核心类和核心入口方法。
3.2 Spring MVC 设计概括
- 对于前面web.xml 的分析可知,spring初始化的时候对contextLoadListening 和DispatchServlet 建立并初始化的过程。先是有web容器的生命周期方法调用contextLoadListening 里面方法初始化ioc 容器,然后在根据启动次序(可配置)
<load-on-startup>1</load-on-startup>
来完成对DispatchServlet 的初始化。contextLoadListening 初始化后会把容器对象放在 见2.1.2 29行代码
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
会在这个对象里面获取到IOC 容器并作为DispatcherServlet持有的上下文的双亲上下文
- 下面先看看DispatcherServlet的一个类继承结构
通过servlet 的一个生命周期函数 init() services () doget() doPost() distory() 等
DispatcherServlet是一个标准的servlet 所以他的初始化也会在init()方法为入口
下面就来看一个DispatcherServlet 的 init() 方法 这个方法是继承自 HttpServletBean
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
//设置一些配置的requiredProperties
// Set bean properties from init parameters.
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;
}
// Let subclasses do whatever initialization they like.
//====> 和兴方法 初始化 ServletBean
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
- initServletBean() 也是继承于 FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
// a) 获取到跟上下文 就是监听器创建的那个上下文对象 WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
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()) {
// 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();
}
if (wac == null) {
//b) 第一次创建容器上下文 并且把获取到的根上下文作为父级上下文
// No context instance is defined for this servlet -> create a local one
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);
}
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;
}
a) 获取根(父)上下文
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
b) 第一次创建容器上下文 并且把获取到的根上下文作为父级上下文
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();//默认的是 XmlWebApplicationContext.class;
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);
//创建好后 赋值环境对象
wac.setEnvironment(getEnvironment());
//设置上级上下文
wac.setParent(parent);
//根据自己配置的资源路径地址来加载配置或者bean的资源
wac.setConfigLocation(getContextConfigLocation());
//刷新容器 在这里面 ===> ioc 的核心方法 刷新容器 AbstractApplicationContext的方法
configureAndRefreshWebApplicationContext(wac);
return wac;
}
- DispatcherServlet 的容器 refresh() 刷新的是SpringMVC 里面的一些bean 对象和配置SpringMVC 的控制层对象 也就是在在SpringMVC.xml 里面的bean
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
..........
postProcessWebApplicationContext(wac);
applyInitializers(wac);
===> 核心方法中的最后一步 发布事件
wac.refresh();
}
//后续会用到这个方法 初始化的对象 SimpleApplicationEventMulticaster
initApplicationEventMulticaster()
// Last step: publish corresponding event.
finishRefresh();
我们看看着DispatherServlet 持有的容器(webApplicationContext)创建的最后一步做了什么
protected void finishRefresh() {
// Initialize lifecycle processor for this context.
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
//====> 发布事件 this ==>就是webApplicationContext
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
- 简略的说一下事件的发布 publishEvent(new ContextRefreshedEvent(this)); 一个事件 容器里面所有持有ContextRefreshedEvent类型的bean 回去匹配到这个事件 然后触发后续的运行
a) 还是 AbstractApplicationContext 的方法
protected void publishEvent(Object event, ResolvableType eventType) {
....
.
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//获取时间播发器 有默认实现ApplicationEventMulticaster 统一由该对象来发布
//===>
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
//解析 时间的类型为 ContextRefreshedEvent
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// getApplicationListeners(event, type) 为根据监听器类型和持有的类型匹配到事件
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
invokeListener(listener, event);
}
}
}
///===>listener.onApplicationEvent(event);
b) SourceFilteringListener 使用装饰者模式 持有 真正干活的 见2.1.3
private GenericApplicationListener delegate;
还有一层修饰
其实还是使用 GenericApplicationListener 的子类GenericApplicationListenerAdapter 中
@Override
public void onApplicationEvent(ApplicationEvent event) {
// delegate 对象是什么呢 看下面 所以 有 看 ==>c ContextRefreshListener.onApplicationEvent() 的方法中
this.delegate.onApplicationEvent(event);
}
回顾2.1.3
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
====>
public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
this.source = source;
this.delegate = (delegate instanceof GenericApplicationListener ?
(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
}
===>GenericApplicationListenerAdapter 的 构造方法
public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
Assert.notNull(delegate, "Delegate listener must not be null");
this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
this.declaredEventType = resolveDeclaredEventType(this.delegate);
}
// delegate 被出实话为 ContextRefreshListener()=
C) ContextRefreshListener 是 FrameworkServlet ((也是DispatherServlet 父类))的内部类
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//继续调用的 外部类的onApplicationEvent 方法
FrameworkServlet.this.onApplicationEvent(event);
}
}
//FrameworkServlet
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
//被子类 DispatherServlet 方法
onRefresh(event.getApplicationContext());
}
//DispatherServlet
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
- 到此终于到了DispatherServlet 的有一个核心方法initStrategies
3.3 initStrategies () 初始化
3.3.1 介绍
在这个方法里面 将会对SpringMVC的 DispatcherServlet 的一些核心组件 的一个初始化工作开始
在DispatcherServlet 定义了很多默认的bean的名字 ,也就是后面框架的一些核心组件的bean的名称
//文件解析器
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
//区域解析器
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
//样式解析器
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
//映射器处理器
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
//映射器适配器
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
//异常解析器
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
//试图名称转换器
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
//试图解析器
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
// 。。。
public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
其中还有一段代码 加载对应的默认类
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static {
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());
}
}
看看文件内容
# 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
// DispatherServlet
protected void initStrategies(ApplicationContext context) {
// 文件上传解析器
initMultipartResolver(context);
//支持国际化,区域解析器。每DispatcherServlet只能注册一个区域解析器
initLocaleResolver(context);
// 动态更换样式的支持(主题)
initThemeResolver(context);
//BeanNameUrlHandlerMapping: 查找spring容器中和请求的url同名的bean.
//BeanNameUrlHandlerMapping :通过对比url和bean的name找到对应的对象
//SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多
//DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时
//RequestMappingHandlerMapping :取代了上面一个
initHandlerMappings(context);
//SimpleControllerHandlerAdapter
// SimpleServletHandlerAdapter
//RequestMappingHandlerAdapter
// HttpRequestHandlerAdapter
// AnnotationMethodHandlerAdapter
initHandlerAdapters(context);
//异常解析处理
initHandlerExceptionResolvers(context);
//用于直接将请求转换为逻辑视图名。
initRequestToViewNameTranslator(context);
//视图解析器:定义了如何通过view 名称来解析对应View实例的行为
initViewResolvers(context);
//SessionFlashMapManager
initFlashMapManager(context);
}
3.3.2 初始化文件解析器 MultipartResolver
直接在容器里面查找 没有的就返回null 会在文件无法上传
MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private void initMultipartResolver(ApplicationContext context) {
try {
//直接在容器里面查找 没有的就返回null 会在文件无法上传
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");
}
}
}
按需要配置 如果你的应用需要文件上传 着还需要添加依赖 并设置bean 交由spring IOC 管理
- 添加依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
- 添加配置 在springMVC.xml(其实两个配置文件都可以 )
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" >
<!--上传文件的最大大小 -->
<property name="maxUploadSize" value="1024000"></property>
<!--文件的字符集-->
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
3.3.3 初始化区域解析器 LocaleResolver
一般没有配置 看3.3.1 中的默认配置 AcceptHeaderLocaleResolver 毫无疑问又是反射创建这个对象,有兴趣的同学可以追一下这个代码,这里就先不说了;
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 + "]");
}
}
}
3.3.4 初始化处理器映射器 HandlerMappings
RequestMappingInfoHandlerMapping 如何 和默认的两个一起被加载?
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
//在上下文中获取所有的 HandlerMapping 用于处理请求
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// 排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
//忽略错误 我们后续添加默认的处理器映射器
}
}
// 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);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
下面我来深度解析这个方法 这个方法也是困扰了我很久的一个方法 首先看这个方法 在容器里面获取所有的HandlerMapping
类型的bean 对象 这个方法是获取到了三个对象(按照前面配置的web.xml),令我困惑的一点是为什么或者说是什么时候注册的这三个bean对象到SpringIOC 里面去的。
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
最开始我的思路是找DispatcherServlet.properties 配置文件 然后查看配置文件里面配置的bean和这个方法获取到不一样。
于是我就换了一个思路就是在DefaultListableBeanFactory(至于为什么是这个方法 ,我就不多说了,简单的就是spring容器bean 工厂) 的内部维护beanDefinitionMap 的put 方法处打了一个断点 果然 看到了这三个bean是如何定义的 ,这里说明一下DefaultListableBeanFactory 是DispatcherServlet内部维护的子容器的工厂(需要跳过父容器的断点,也就是说第一次打算的是父容器bean定义的加载 是看不到HandlerMapping 加载的)
a) 看代码org.springframework.beans.factory.xml.BeanDefinitionParserDelegate 中 熟悉IOC 的都知道这个类是解析xml 文件获取bean的定义的
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//获取xml文件的命名空间定义
String namespaceUri = getNamespaceURI(ele);
//根据不同的 命名空间使用不同的解析器
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
。。。。。。。
}//[mvc:annotation-driven: null] 就是MvcNamespaceHandler
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
1.RequestMappingHandlerMapping
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
//findParserForElement(element, parserContext) 根据内容annotation-driven
//又获得了基于注解的解析器 AnnotationDrivenBeanDefinitionParser
// org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
return findParserForElement(element, parserContext).parse(element, parserContext);
}
//解析方法里面
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
···
//HANDLER_MAPPING_BEAN_NAME ==>org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);
··· 注册其他bean
- 在上面代码同样的方法中
//解析方法里面
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
....
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
//确保BeanNameUrlHandlerMapping 和默认的适配器被初始化
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
public static void registerDefaultComponents(ParserContext parserContext, Object source) {
registerBeanNameUrlHandlerMapping(parserContext, source);
registerHttpRequestHandlerAdapter(parserContext, source);
registerSimpleControllerHandlerAdapter(parserContext, source);
}
private static void registerBeanNameUrlHandlerMapping(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME)){
RootBeanDefinition beanNameMappingDef = new RootBeanDefinition(BeanNameUrlHandlerMapping.class);
beanNameMappingDef.setSource(source);
beanNameMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanNameMappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport
RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
beanNameMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
//BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME ===>org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping 注册完成
parserContext.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, beanNameMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(beanNameMappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
}
}
- [mvc:default-servlet-handler: null]
public BeanDefinition parse(Element element, ParserContext parserContext) {
//element==>mvc:default-servlet-handler: null
//findParserForElement(element, parserContext)==>class org.springframework.web.servlet.config.DefaultServletHandlerBeanDefinitionParser
return findParserForElement(element, parserContext).parse(element, parserContext);
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
//handlerMappingBeanName==>org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0
parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
}
至此 可以大致可以知道为什么在最开的是方法中 在ioc容器里面可以获取到三个默认的
- RequestMappingHandlerMapping
- BeanNameUrlHandlerMapping
- SimpleUrlHandlerMapping
也可以从细节中可以看出来前两个的HandlerMapping 的初始化时在web.xml 定义了开启注解开发 <mvc:annotation-driven/>
后面 SimpleUrlHandlerMapping
是开启了默认资源访问 <mvc:default-servlet-handler />
最后我还是抱有严谨的态度测试了以下 在配置文件的 <mvc:default-servlet-handler />
删除 IOC 容器也没有初始化SimpleUrlHandlerMapping
把 基于注解 <mvc:default-servlet-handler />
删除 也使得前面两个HandlerMapping 没有初始化 。
你以为这个方法到这里结束了吗 <mvc:default-servlet-handler />
和 <mvc:default-servlet-handler />
都没有配置的时候 ,springMVC 就不会给我默认的实现这个组件了吗? 答案是否定的 看代码 3.3.4 第一个28 行处 当在容器内部找不到 HandlerMapping 的时候,开会默认策略,读取配置文件里面的内容,也就是3.3.1第四代码块 里面的两个HandlerMapping
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
//在配置文件里面获取 //org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
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);
strategies.add((T) strategy);
}
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>();
}
}
终于到此 这个方法大致告一段落
3.3.5 初始化 处理器适配器 HandlerAdapters
这个方法答题类似于3.3.4 下面看代码结构 也是类似于在容器里面获取 获取不到的话就有默认的实现
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
//在容器获取
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
if (this.handlerAdapters == null) {//有默认的实现
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}
3.3.6 后续初始化
后续的初始化方法 都是和上述逻辑一致 这里就不详述了, 到此框架也都启动完成!!!!!
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
3.4 MVC 处理HTTP 请求
示例代码
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/index")
public String index(){
return "/demo/index";
}
// @RequestMapping("/index1")
// @ResponseBody
// public String index1(){
// publisher.publish();
// return "OK";
// }
@RequestMapping("/{index1}")
@ResponseBody
public String index1(@PathVariable("index1") String index1 ){
return index1;
}
}
3.4.1 HandlerMapping 设计原理
作为SpringMVC的核心组件之一,它在框架初始化的时候已经完成了自己的创建,在上面创建 HandlerMapping 的的过程中,我们可以看到不止是创建了HandlerMapping 它还有个属性Order 这些HandlerMapping 最后放在一个List里面并且根据Order 排序
SpringMVC 提供了对HandlerMapping 的具体实现 下面是一些HandlerMapping 的继承关系
下面就是SimpleUrlHandlerMapping 来分析 HandlerMapping 的设计和实现
在顶层的HandlerMapping 接口中 只定义了一个接口方法
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
我们再看看这个HandlerExecutionChain的定义
/**
* 处理器链,包括处理器handler对象 包括这个handler的所有的拦截器
* Handler execution chain, consisting of handler object and any handler interceptors.
* Returned by HandlerMapping's {@link HandlerMapping#getHandler} method.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see HandlerInterceptor
*/
public class HandlerExecutionChain {
正在使用实例代码测试的时候 进行debug 在这里输入栏输入的地址是
http://localhost:8080/yyqdata/demo/index1
根据我们的经验应该走的是下面在这个方法
@RequestMapping("/{index1}")
@ResponseBody
public String index1(@PathVariable("index1") String index1 ){
return index1;
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
............
// Determine handler for the current request.
//决定使用那个handler来处理请求
mappedHandler = getHandler(processedRequest);===》
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
注意: hm.getHandler() 根据请求获得封装好的 HandlerExecutionChain
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//handlerMappings 默认情况下面是三个 一个是开启注解的一个 还有两个是默认的在配置文件里面欧配置好的
// HandlerMapping 不同类型的HandlerMapping 用于不同的请求处理 有的是基于Servlet 有的是方法的注解@requestMapping
//有的是实现MVC 的Controller 接口的,在容器启动的时候会基于不同的请求出开启不同和HandlerMapping 在处理请求的时候用请求的地址去不同的HandlerMapping里面去匹配 匹配到第一个就再去 封装 HandlerExecutionChain
for (HandlerMapping hm : this.handlerMappings) {
.....//===>在这里由不同优先级别的 handlerMapping 来处理请求
//一旦有高优先级的处理好了 就不需要后面的来处理了
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
AbstractHandlerMapping
里面的getHandler
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//===> 下面
Object handler = getHandlerInternal(request);
.......
//获取到handler后再封装结果对象(主要是handler 方法和拦截器对象)
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;
AbstractHandlerMethodMapping
getHandlerInternal
===> lookupHandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
//第一个直接根据url来匹配
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...除了浏览所有映射外别无选择...
//遍历所有的url 映射关系来获取匹配到到的方法 吧匹配到的handler添加到matches
//把所有的遍历 通过各种规则 这里就不详细描述了
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
//如果匹配的到了多个而且 不知知道用使用那个 也是会报错
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
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);
}
上面的mappingRegistry :AbstractHandlerMethodMapping 中维护的
private final MappingRegistry mappingRegistry = new MappingRegistry();
再来看看 MappingRegistry 类的定义
/**
* A registry that maintains all mappings to handler methods, exposing methods
* to perform lookups and providing concurrent access.
*一个注册表,用于维护到处理程序方法的所有映射,公开用于执行查找的方法并提供并发访问
* <p>Package-private for testing purposes.
*/
我看看这个对象 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
的初始化就知道了
- . super.afterPropertiesSet();
2). 对所有的子容器的bean 也就SpringMVC的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);
}
}
if (beanType != null && isHandler(beanType)) {
//===>方法
detectHandlerMethods(beanName);
}
}
}
3) 在对bean里面的所有方法处理
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 获取bean的所有的方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
return getMappingForMethod(method, userType);
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
// 对符合要求的控制层方法进行处理
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
4). 保存映射关系
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;
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();
}
}
this.mappingLookup.put(mapping, handlerMethod);
这样的一个对象 他是维护什么的呢?
我的理解 key : 是一个 HandlerMappingInfo 这个对象是什么呢 可以用这个对象里面的各种匹配模式去匹配请求
value: 就是具体的请求方法 也就是controller里面的方法
如此这样就是 单请求来的时候 我们去匹配key 里面的的对象 一旦是匹配上了,就可以获取到对应的value ;也就是获取到请求应该看具体执行放入方法是哪一个。
3.5 SpringMVC 对请求的分发处理
纵观整个过程,DispatchServlet 是SpringMVC 里面可以说是最核心的一个类,包括建立了一个IOC的子容器,对HTTP的请求处理;因为他也是HttpServlet 的子类 通过doService 方法来响应HTTP的请求
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//保存系统的一些配置 快照
// 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());
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 对象,接受handler处理请求的结果
ModelAndView mv = null;
Exception dispatchException = null;
try {
// >>>>>> 请求里面是不是有文件 在根据配置的文件解析器来
//获取文件
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// >>>>>> mappedHandler 根据请求获取到相对于的handler 和封装响应的拦截器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//获取到处理器适配器 在执行handler之前 使用HandlerAdapter检查handler的服务器
//3 >>>>>>>>是不是按照Spring 的要求编写handler
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String meth sGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
}
// 拦截器的 PreHandle 方法执行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// >>>>>>>>> 通过 调用 ha.handle 实际上是触发了Controller的handleRequest 方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//判断是不是需要进行视图名的解析翻译
applyDefaultViewName(processedRequest, mv);
//>>>>拦截器的 请求后处理方法
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,
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//拦截器的最后一个方法被调用
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);
}
}
}
}
3.6 适配器模式
在前面的mappedHandler = getHandler(processedRequest)
方法里面可以看到 容器里面的不同类型的HandlerMapping 会以遍历的方式去获取和请求地址去匹配 以获取到与之对应的 切封装好拦截器 处理器 的对象 HandlerExecutionChain;
我们知道handler (处理请求的方式)的类型可能有多种 有基于类的 比如说是Servlet ,类实现Controller的 有基于方法的,也是我们日常开发使用的最多的一种 @requestMapping 的方式 也可以看到 mappedHandler.getHandler()
该接口返回的对象是一个Object 类型 ,下面的获取适配器来执行也是这个思想
试验代码
@Component("/home")
public class Test1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView();
Map<String,String> stringMap = new HashMap<>();
stringMap.put("aa","bb");
modelAndView.addAllObjects(stringMap);
return modelAndView;
}
}
// 更具请求的Handler类型来获取不同的 适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
//遍历所有的 handlerAdapters 来期望获取与之对应的适配器 好执行对应的方法
//adapter.supports(handler) 方法的核心就是各种判断 handler 是否是各种数据类型 是Controller 还是HandlerMethod(@RequestMapping 这种也是最复杂的)
//有或者是HttpHanlder 来决定使用何种的适配器 ,比如说这里是 是Controller类型 后期执行方法的时候就是
//强转成这个Controller类型 在执行接口方法 当然这个是最简单的 结合试验代码 基于
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//这里发生的强转
return ((Controller) handler).handleRequest(request, response);
}
3.7 SpringMVC 数据/视图的呈现
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//方法 根据 mv的返回值来判断 是否有 判断是否需要视图的展现
//现在目前都用ajax 请求就是没有这方面的
//如果没有视图就是执行返回的数据 会填充到response 里面去
3.7.1 model
下面以ajax 请求为例
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
..........
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//对返回的数据添加到 webRequest 细节就没看了 各种解析器解析GenericHttpMessageConverter数据 挺复杂的
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
//请求数据填充
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
3.7.2 view 视图的展现
页面的地址对应后台是个页面跳转 方法 比如
@GetMapping("/index")
public String index(){
return "index";
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
此时mv 的viewName 属性就是index 而不是和上面一样的是null
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
。。。。。。
// 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.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// 拦截器的最后一个方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name. 各种视图解析器解析
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
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.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// view 具体实现的组件 解析视图>>>>>
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
这里是ThymeleafView
public void render(final Map<String, ?> model, final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
renderFragment(this.markupSelectors, model, request, response);
}
renderFragment
的具体实现比较长 我的总结就是来说把数据通过模板引擎来加载到页面 然后再把页面数据写到响应数据里面去;在返回到前台到此 页面得以显示;