Spring5源码14-SpringMVC-DispatcherServlet初始化
欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
SpringMVC
的实现原理是通过 servlet
拦截所有的URL来达到控制的目的。而所使用的Servlet
即是 DispatcherServlet
。
简单来说,就是所有的请求(这里说的比较绝对,仅为了表述用,具体拦截多少请求是通过 <url-pattern>
标签配置)都是首先请求到 DispatcherServlet
,由 DispatcherServlet
来根据路径等信息转发到不同的处理器,再将结果返回。
我们来看一下 DispatcherServlet
的结构,如下:
可以看到,DispatcherServlet
本质上是一个Servlet
。在上面讲述 Servlet
生命周期的时候我们说过,Servlet
仅初始化一次,并且调用init
方法进行初始化。DispatcherServlet
调用的是 HttpServletBean#init
方法,下面我们来具体看看。
1. DispatcherServlet 的初始化
1.1 HttpServletBean#init
DispatcherServlet
的初始化过程主要是通过将当前的Servlet
类型实例转换为 BeanWrapper
类型实例,以便于使用Spring
中提供注入功能进行对应属性的注入。
同时上面也提到了,在这里面会完成 WebApplicationContext
的具体初始化。
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 1. 封装及验证初始化参数
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 2. 将当前 Servlet 实例转化为 BeanWrapper 实例
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 3. 注册于相对于 Resource 的属性编辑器
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// 4. 属性注入
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// 5. servletBean的初始化
initServletBean();
}
上面的流程大致梳理如下:
-
封装及验证初始化参数:
ServletConfigPropertyValues
除了封装属性外还有对属性验证的功能。public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException { Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? new HashSet<>(requiredProperties) : null); // 获取 web.xml 中 DispatcherServlet 配置的 init-params 属性内容 Enumeration<String> paramNames = config.getInitParameterNames(); while (paramNames.hasMoreElements()) { String property = paramNames.nextElement(); Object value = config.getInitParameter(property); // 保存 init-params 属性 addPropertyValue(new PropertyValue(property, value)); if (missingProps != null) { missingProps.remove(property); } } // Fail if we are still missing properties. if (!CollectionUtils.isEmpty(missingProps)) { throw new ServletException( "Initialization from ServletConfig for servlet '" + config.getServletName() + "' failed; the following required properties were missing: " + StringUtils.collectionToDelimitedString(missingProps, ", ")); } }
-
将当前
Servlet
实例转化为BeanWrapper
实例:PropertyAccessorFactory.forBeanPropertyAccess
是Spring提供的工具方法,主要用于将指定实例转化为 Spring 中可以处理的BeanWrapper
类型的实例 -
注册于相对于
Resource
的属性编辑器:在 对当前实例属性注入过程中一旦遇到 Resource 类型的属性就会使用 ResourceEditor 去解析 -
属性注入 BeanWrapper 为Spring中的方法,支持Spring的自动注入
-
servletBean
的初始化实际上,在
ContextLoaderListener
加载的时候就已经创建了WebApplicationContext
实例(父容器),在这里则是对WebApplicationContext
的进一步的初始化补充。
@Override
protected final void initServletBean() throws ServletException {
....
long startTime = System.currentTimeMillis();
try {
// 委托给 initWebApplicationContext 方法来完成进一步的初始化
this.webApplicationContext = initWebApplicationContext();
// 留给子类覆盖操作
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
...
}
关于 initWebApplicationContext
的实现是在 FrameworkServlet#initWebApplicationContext
中完成,下面我们就来看看其实现过程。
1.2 FrameworkServlet#initWebApplicationContext
下面我们看看 FrameworkServlet#initWebApplicationContext
的代码如下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 1. 如果 webApplicationContext 在构造函数的时候被注入,则 wac != null, 则可以直接使用
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
// 2.刷新上下文环境
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 3. 如果构造函数并没有注入,则wac为null,根据 contextAttribute 属性加载 WebApplicationContext
if (wac == null) {
// 根据 contextAttribute 属性加载 WebApplicationContext
wac = findWebApplicationContext();
}
// 4. 如果上面两个方式都没有加载到 WebApplicationContext,则尝试自己加载
if (wac == null) {
// 自己尝试创建WebApplicationContext
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// 5.刷新
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
其实我们可以看到,第1,3,4步都是在找 WebApplicationContext
的实例。第2,5步才是真正的初始化。
1.2.1 WebApplicationContext 的获取
结合上面的代码我们可以看到 寻找 WebApplicationContext
的实例可以分为三步:
- 判断是否通过构造函数注入了
WebApplicationContext
。若注入直接使用 - 尝试根据
contextAttribute
属性加载WebApplicationContext
。 - 如果第二步仍未加载成功,则尝试自己创建
WebApplicationContext
。 下面我们来一步一步分析
1.2.1.1 构造注入的WebApplicationContext
在调用 initWebApplicationContext
方法时 第一步的判断条件就是 this.webApplicationContext != null
。如果this.webApplicationContext != null
。则说明 WebApplicationContext
是通过构造注入的方式注入进来,可以直接使用。
这里需要注意,
因为 Servlet 只会初始化一次
,所以这里的this.webApplicationContext
不会是之前初始化留下的值。
1.2.1.2 通过 contextAttribute 属性获取 WebApplicationContext
即通过 web.xml
文件中配置的servlet
参数 contextAttribute
来查找 ServerContext
中对应的属性。这里可以回忆一下,在ContextLoaderListener
中,已经将创建好的WebApplicationContext
实例保存到了ServletContext
中,其key值默认为 WebApplicationContext.class.getName() + ".ROOT"
。不过这个key值是可以更改的。这里即是通过 contextAttribute 作为 key 来尝试去ServletContext中获取WebApplicationContext。contextAttribute 在初始化 DispatcherServlet 的时候可以通过 setContextAttribute 进行设置。默认的contextAttribute 为null。
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
...
public String getContextAttribute() {
return this.contextAttribute;
}
1.2.1.3 尝试自己创建WebApplicationContext
上面两种方式都没有找到 WebApplicationContext
,则就只能重新创建 WebApplicationContext
实例了。
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 获取 Servlet 初始化参数 contextClass,即获取WebApplicationContext 的具体类型,如果没有配置默认是 XmlWebApplicationContext 类型。
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 反射创建,并设置属性
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
// 获取web.xml 配置的 DispatcherServlet init-params contextConfigLocation 属性
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 配置 WebApplicationContext 实例并刷新
configureAndRefreshWebApplicationContext(wac);
return wac;
}
1.2.1.4 configureAndRefreshWebApplicationContext
我们这里额外注意一个方法:configureAndRefreshWebApplicationContext
。
无论是通过构造注入还是单独创建,都会调用 configureAndRefreshWebApplicationContext
方法来对已经创建的 WebApplicationContext
实例进行配置及刷新。其实就是刷新Spring容器
。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
// 设置一些属性,ServletContext、ServletConfig等
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// 这里调用了 AbstractApplicationContext#refresh
wac.refresh();
}
其实关键就再最后一步,调用了AbstractApplicationContext#refresh
方法,而这个方法正是整个Spring初始化的开始。关于该方法,这里可以简单的理解是完成了Spring容器的功能。详见Spring5源码11-容器刷新refresh方法(注解版)
综上之后,WebApplicationContext
已经获取完毕,下面开始进行进一步的初始化。
2. onRefresh-初始化九大核心组件
onRefresh
是 FrameworkServlet
提供的模板方法,供子类实现。我们来看看 DispatcherServlet#onRefresh
的实现内容,主要用于 刷新Spring
在Web功能实现中所必须使用的全局变量。
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
// 默认策略配置文件
private static final Properties defaultStrategies;
@Override
protected void onRefresh(ApplicationContext context) {
// 初始化策略
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// 初始化多文件上传解析器
initMultipartResolver(context);
// 初始化国际化解析器
initLocaleResolver(context);
// 初始化主题解析器
initThemeResolver(context);
// 初始化 HandlerMappering
initHandlerMappings(context);
// 初始化 HandlerAdapter
initHandlerAdapters(context);
// 初始化 Handler异常解析器
initHandlerExceptionResolvers(context);
// 初始化RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
// 初始化 视图解析器
initViewResolvers(context);
// 初始化 FlashMapManager
initFlashMapManager(context);
}
需要提一句,这里的 defaultStrategies
属性是加载了默认的配置文件 DispatcherServlet.properties
。这个文件和 DispatcherServlet
同级,里面记录了各种默认的加载策略。上面初始化了一大堆的东西,下面我们来一句一句分析。
2.1 initMultipartResolver(context)
这一步顾名思义,就是配置多文件解析器
。在 Spring 中,MultipartResolver
主要用来处理文件上传
。默认情况下,Spring是没有 Multipart
处理的。如果想使用 Spring 的 Multipart
,则需要手动注入 Multipart 解析器,即MultipartResolver
。这样Spring 会检查每个请求是否包含 Multipart,如果包含,那么 MultipartResolver 就会解析它。一般我们可以注入 CommonsMultipartResolver
。
其实现代码也很简单,从容器中获取beanName
为 multipartResolver
解析器,并保存到 DispatcherServlet
中。
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}
2.2 initLocaleResolver(context)
这里是初始化国际化解析器
。代码如下:
private void initLocaleResolver(ApplicationContext context) {
try {
// 从容器中获取
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.localeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
// todo 获取默认的
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
如果获取不到,默认的从getDefaultStrategy
方法获取:
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
List<T> strategies = getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException(
"DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
}
return strategies.get(0);
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
if (defaultStrategies == null) {
try {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
// 去DispatcherServlet所在类路径下找一个 DispatcherServlet.properties 文件
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
// 获取Name
String key = strategyInterface.getName();
// 从配置文件中获取value
String value = defaultStrategies.getProperty(key);
// 获取到value 之后就是对value的处理,添加返回。
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(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(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return Collections.emptyList();
}
}
首先从容器中获取 beanName 为 localeResolver
,如果获取不到,就获取默认的,默认是去DispatcherServlet所在类路径下找一个 DispatcherServlet.properties
文件,获取对应的默认值:
默认为
AcceptHeaderLocaleResolver
。
一般情况下, localeResolver
有三种注入实例:
AcceptHeaderLocaleResolver
: 基于URL 参数的配置 。他会根据请求的URL后缀来判断国际化场景。比如 :“http://xxx?local=zh_CN”
。local参数也可以是en_US。CookieLocaleResolver
: 基于Cookie的国际化配置。他是通过浏览器的 Cookies 设置取得Local对象。SessionLocaleResolver
:基于Session
的配置,他通过验证用户会话中预置的属性来解析区域。常用的是根据用户本次会话过程中语言来决定语言种类。如果该会话属性不存在,则会根据 http的accept-language
请求头确认国际化场景。
2.3 initThemeResolver(context)
在Web开发中经常会遇到通过主题Theme 来控制网页风格,这将进一步改善用户体验。简单的说,一个主题就是一组静态资源(比如样式表和图片),他们可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常类似。代码跟 第1.3.2 小节差不多,简单说一下三个常用的主题解析器:
FixedThemeResolver
:用于选择一个固定的主题SessionThemeResolver
:用于主题保存在用户的http session中CookieThemeResolver
:实现用户所选的主题,以cookie的形式存放在客户端的机器上
2.4 initHandlerMappings(context)
当客户端发出 Request
时, DispatcherServlet
会将 Request
提交给 HandlerMapping
,然后HandlerMapping
根据WebApplicationContext
的配置来回传给DispatcherServlet
相应的Controller
。
在基于Spring mvc 的web应用程序中,我们可以为DispatcherServlet
提供多个 HandlerMapping
供其使用。DispatcherServlet
在选用 HandlerMapping
的过程中,将会根据我们所指定的一系列Handler 的优先级进行排序
,然后优先使用优先级在前的HandlerMapping
。如果当前HandlerMapping能够返回可用的Handler,DispatcherServlet
则是使用当前返回的Handler 进行Web请求的处理,而不再询问其他HandlerMapping
,否则DispatcherServlet将按照各个HandlerMapping 的优先级进行询问,知道获取到一个可用的Handler 为止。其代码如下:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 如果启用所有的HandlerMapping。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 寻找所有的HandlerMapping类型的类
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
// 按照优先级进行排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
// 如果使用指定的参数,从容器中获取beanName 为 handlerMapping 的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.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
// 如果handlerMappings 为null。则使用默认策略指定的HandlerMapping
if (this.handlerMappings == null) {
// todo 默认的策略
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
这里注意:
-
detectAllHandlerMappings
参数用来判断是否启用所有的HandlerMapping
。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的HandlerMapping。 -
默认策略指定的
HandlerMapping
是从DispatcherServlet.properties
配置文件中取出的值,以org.springframework.web.servlet.HandlerMapping
(HandlerMapping 的全路径类名) 作为key所取出的value。
关于几种HandlerMapping,我们这里来简单看看。后面详细讲解。
-
BeanNameUrlHandlerMapping
:以beanName 作为key值 -
RequestMappingHandlerMapping
:完成@Controller
和@RequestMapping
的解析,并将解析保存。请求发送时与请求路径进行匹配对应找到合适的Handler。RequestMappingHandlerMapping
实现了InitializingBean
接口,会在afterPropertiesSet
方法中- 调用时机:解析@Controller和@RequestMapping注解是在 afterPropertiesSet方法中进行的。匹配调用则是在 DispatcherServlet doDispatch方法中的getHandler中调用了HandlerMapper中的getHandler中的getHandlerInternal方法。
-
SimpleUrlHandlerMapping
:基本逻辑是通过注入SimpleurlHandlerMapping
的mapping属性,mapping key为url, value为handler(beanName)。这里需要注意Controller必须要实现Controller接 口
。
2.5 initHandlerAdapters(context)
初始化处理器适配器。这里使用了适配器模式来设计。
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
// 如果启用所有的HandlerAdapter。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
// 寻找所有的适配器并排序
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
// 没有启用则从Spring 容器中获取 beanName = handlerAdapter 并且类型是 HandlerAdapter 类型的bean。
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.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
// 如果还没有获取到适配器,则使用默认策略的适配器。
// 从 DispatcherServlet.properties 中获取 org.springframework.web.servlet.HandlerAdapter 为key值的value加载到容器中。
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
可以看到逻辑和上面的HandlerMapping 基本相同。
这里我们简单介绍一下三个 HandlerAdapter:
HttpRequestHandlerAdapter
: Http请求处理器适配器。- HTTP请求处理适配器仅仅支持 HTTP 请求处理器的适配。他简单的将HTTP请求对象和响应对象传递给HTTP请求处理器的实现,他并不需要返回值。主要应用在基于 HTTP的远程调用实现上。
SimpleControllerHandlerAdapter
: 简单控制器处理器适配器- 这个实现类将HTTP请求适配到了一个控制器的实现进行处理。这里的控制器的实现是一个简单的控制器接口的 实现。简单控制器处理器适配器被设计成一个框架类的实现,不需要被改写,客户化的业务逻辑通常在控制器接口的实现类中实现的。
RequestMappingHandlerAdapter
: 请求映射处理器适配器- 这个实现类需要通过注解方法映射和注解方法处理器协同工作。它通过解析声明在注解控制器的请求映射信息来解析相应的处理器方法来处理当前的http请求,在处理的过程中,他通过反射来发现探测处理器方法的参数,调用处理器方法,并映射返回值到模型和控制器对象。最后返回模型和控制器对象给作为主控制器的派遣器Servlet。
2.6 initHandlerExceptionResolvers(context)
基于 HandlerExceptionResolver
接口的异常处理,使用这种方式只需要实现 org.springframework.web.servlet.HandlerExceptionResolver#resolveException
方法,该方法返回一个 ModelAndView
对象,在方法内部对异常的类型进行判断,然后尝试生成对应的 ModelAndView
对象,如果该方法返回了null。则Spring会继续寻找其他的实现了HandlerExceptionResolver
接口的bean,直至找到一个可以返回ModelAndView
的 HandlerExceptionResolver
。
至于代码逻辑,和上面基本相同,这里不再赘述。
2.7 initRequestToViewNameTranslator(context)
当Controller处理器方法没有返回一个View
对象或者逻辑视图名称,并且在该方法中没有直接放Response 的输出流中写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过 Spring 定义的 org.springframework.web.servlet.RequestToViewNameTranslator
接口的 getViewName
方法实现的。Spring默认提供了一个实现 org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
可以看一下其支持的属性
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
// 分隔符
private static final String SLASH = "/";
// 视图前缀
private String prefix = "";
// 视图后缀
private String suffix = "";
// 默认的分隔符
private String separator = SLASH;
// 如果首字符是分隔符是否需要去除,默认true
private boolean stripLeadingSlash = true;
// 如果尾字符是分隔符是否需要去除,默认true
private boolean stripTrailingSlash = true;
// 如果请求路径包含扩展名是否需要去除,默认true
private boolean stripExtension = true;
// 通过这个属性可以设置多种属性
private UrlPathHelper urlPathHelper = new UrlPathHelper();
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
// URL查找是否应始终使用当前路径中的完整路径
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
// 是否要对 URL 解码,默认 true。它会采用request 指定的编码或者 ISO-8859-1 编码对 URL 进行解码
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set if ";" (semicolon) content should be stripped from the request URI.
* @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
*/
// 是否删除 ; 内容
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
}
....
}
这里可以知道,我们日常使用的视图转换器就是 DefaultRequestToViewNameTranslator
。代码逻辑比较简单,和上面的类似,不赘述。
2.8 initViewResolvers(context)
在 Spring mvc 中。当 Controller
将请求处理结果放入到 ModelAndView
中以后,DispatcherServlet
会根据 ModelAndView
选择合适的视图进行渲染。在org.springframework.web.servlet.ViewResolver#resolveViewName
方法中,通过 viewName
创建合适类型的View。
代码逻辑相同,不再赘述。
2.9 initFlashMapManager(context)
Spring MVC Flash attributes 提供了一个请求存储属性,可供其他请求使用。在使用重定向的时候非常必要。 Flash attributes 在重定向之前暂存以便重定向之后还能使用,并立即删除。
Spring MVC 有两个主要的抽象类来支持 Flash attributes
。FlashMap 用于保存 Flash attributes
。而FlashMapManager
用于存储、检索、管理 FlashMap 实例。
Flash attributes 支持默认开启(“on”),并不需要显式启用,它永远不会导致Http Session 的创建,这两个方法都可以通过 静态方法 RequestContextUtils 从 Spring mvc 的任何位置访问。
初始化代码逻辑相同,不再赘述。
参考文章
Spring5源码注释github地址 Spring源码深度解析(第2版) spring源码解析 Spring源码深度解析笔记 Spring注解与源码分析 Spring注解驱动开发B站教程
转载自:https://juejin.cn/post/7139330939489878029