目录
- DispatcherServlet类图
- HttpServletBean#init
- new ServletConfigPropertyValues()
- FrameworkServlet#initServletBean
- initWebApplicationContext
- createWebApplicationContext
- configureAndRefreshWebApplicationContext
- DispatcherServlet内部9大组件初始化
- 初识9大组件
- DispatcherServlet#onRefresh();
- DispatcherServlet.properties文件 (默认策略)
- 获取默认策略解析器
- initMultipartResolver
- initLocaleResolver
- initThemeResolver
- initHandlerMappings
- initHandlerAdapters
- initHandlerExceptionResolvers
- initRequestToViewNameTranslator
- initViewResolvers
- initFlashMapManager
DispatcherServlet类图
Web应用
启动的最后一个步骤就是创建和初始化相关Servlet
,我们配置了DispatcherServlet类
前端控制器,前端控制器作为中央控制器是整个Web应用
的核心,用于获取分发用户请求并返回响应。
通过类图可以看出DispatcherServlet类
的间接父类实现了Servlet接口
,因此其本质上依旧是一个Servlet
HttpServletBean#init
DispatcherServelt
类的本质是Servlet
,所以在Web应用
部署到容器后进行Servlet
初始化时会调用相关的init(ServletConfig)
方法,因此,DispatchServlet类
的初始化过程也由该方法开始:
(注意:DispatcherServelt
没有init方法,会走到父类HttpServletBean
的init方法)
/*** DispatcherServlet 初始化入口* Map config parameters onto bean properties of this servlet, and* invoke subclass initialization.* @throws ServletException if bean properties are invalid (or required* properties are missing), or if subclass initialization fails.*/@Overridepublic final void init() throws ServletException {// Set bean properties from init parameters./** 1.加载初始化参数,如:* <servlet>* <servlet-name>DispatcherServlet</servlet-name>* <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>* <init-param>* <param-name>name</param-name>* <param-value>zimu</param-value>* </init-param>* <load-on-startup>1</load-on-startup>* </servlet>* 这里会解析init-param列表。*/PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {// 2.将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());// 3.注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));// 4.空实现,留给子类覆盖initBeanWrapper(bw);// 5.属性注入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.// 【重点】6.子类负责实现,主要来初始化ApplicationContext及一些ResolveinitServletBean();}
new 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, ", "));}}
该方法最主要的作用就是初始化init-param
,如果我们没有配置任何init-param
,那么该方法不会执行任何操作。从这里我们没有拿到有用的信息,但是在该方法结尾有initServletBean()
,这是一个模板方法,可以由子类来实现,那么接下来我们就去看其子类FrameworkServlet中
的initServletBean
FrameworkServlet#initServletBean
继续查看 initServletBean()。父类 FrameworkServlet 覆盖了 HttpServletBean 中的 initServletBean 函数,如下:
protected void initServletBean() throws ServletException {
}
@Overrideprotected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");if (logger.isInfoEnabled()) {logger.info("Initializing Servlet '" + getServletName() + "'");}long startTime = System.currentTimeMillis();try {//【重点】,用于初始化子ApplicationContext对象,// 主要是用来加载<servlet/>对应的servletName-servlet.xml文件如:springMVC.xmlthis.webApplicationContext = initWebApplicationContext();// 空的模板方法initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}if (logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :"masked to prevent unsafe logging of potentially sensitive data";logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +"': request parameters and headers will be " + value);}//当我们看到这句日志,就能知道dispatcherServlet已经初始化完成,web子容器也就初始化完成了if (logger.isInfoEnabled()) {logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}}
其中我们可以只有一个核心方法:initWebApplicationContext()
,主要用来初始化springmvc子容器,其余都是一些日志相关的,关于 initWebApplicationContext
的实现是在 FrameworkServlet#initWebApplicationContext
中完成,下面我们就来看看其实现过程。
initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {/*获取由ContextLoaderListener创建的根IoC容器获取根IoC容器有两种方法,还可通过key直接获取*/WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;// 如果当前webApplicationContext不为null(context 实例在构造函数中被注入),则为其设置父容器if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = 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, etcif (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/*如果当前Servelt存在一个WebApplicationContext即子IoC容器并且上文获取的根IoC容器存在,则将根IoC容器作为子IoC容器的父容器*/cwac.setParent(rootContext);}//配置并刷新当前的子IoC容器,用于构建相关BeanconfigureAndRefreshWebApplicationContext(cwac);}}}// 未能通过构造函数注入,则尝试去ServletContext容器中查找有无WebApplicationContextif (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//如果当前Servlet不存在一个子IoC容器则去查找一下wac = findWebApplicationContext();}// 以上均无WebApplicationContext,则创建一个新的WebApplicationContextif (wac == null) {// No context instance is defined for this servlet -> create a local onewac = 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.synchronized (this.onRefreshMonitor) {onRefresh(wac);}}//我们是否需要吧我们的容器发布出去,作为ServletContext的一个属性值呢?默认值为true哦if (this.publishContext) {// Publish the context as a servlet context attribute.// 这个attr的key的默认值,就是FrameworkServlet.SERVLET_CONTEXT_PREFIX,保证了全局唯一性// 这么一来,我们的根容器、web子容器其实就都放进ServletContext上下文里了,拿取都非常的方便了。 只是我们一般拿这个容器的情况较少,一般都是拿跟容器,比如那个工具类就是获取根容器的~~~~~~String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;}
通过函数名不难发现,该方法的主要作用同样是创建一个WebApplicationContext对象,即Ioc容器,不过前面讲过每个Web应用最多只能存在一个根IoC容器,这里创建的则是特定Servlet拥有的子IoC容器
上述逻辑如下:
- 如果当前存在一个IOC容器(通过构造注入的),则为其绑定父子关系,并配置刷新当前子容器
- 如果当前没有则先去ServletContext找一下(目前没啥用)
- 之后如果当前不存在并且也没找到则去通过反射创建一个新的WebApplicationContext并配置刷新
为什么需要多个IOC容器呢?
答:父子容器类似于类的继承关系,子类可以访问父类中的成员变量,而父类不可访问子类的成员变量,同样的,子容器可以访问父容器中定义的Bean,但父容器无法访问子容器定义的Bean。
根IoC容器做为全局共享的IoC容器放入Web应用需要共享的Bean,而子IoC容器根据需求的不同,放入不同的Bean,这样能够做到隔离,保证系统的安全性。
createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {return createWebApplicationContext((ApplicationContext) parent);}protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {// 获取 servlet 的初始化参数 contextClass,如果没有配置默认为 XMLWebApplicationContext.ClassClass<?> contextClass = getContextClass();// 校验必须是ConfigurableWebApplicationContext的子类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");}// 通过反射方式实例化 contextClassConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);// 设置好父容器、Enviroment等等wac.setEnvironment(getEnvironment());wac.setParent(parent);// 获取 contextConfigLocation 属性,配置在 servlet 初始化参数中String configLocation = getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}// 这个是重点,如完善、初始化、刷新容器configureAndRefreshWebApplicationContext(wac);return wac;}
configureAndRefreshWebApplicationContext
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 informationif (this.contextId != null) {wac.setId(this.contextId);}else {// Generate default id...// 默认的id 这里面和contextpath有关了wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}// 关联到了Namespace/servlet等等wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());//添加了一个容器监听器 此监听器SourceFilteringListener在后面还会碰到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 #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}//留给子类,可以复写此方法,做一些初始化时候自己的实行postProcessWebApplicationContext(wac);//同样的 执行一些初始化类们 一般也是用不着applyInitializers(wac);// 加载配置文件及整合 parent 到 wacwac.refresh();}
该方法用于创建一个子IoC容器并将根IoC容器做为其父容器,接着进行配置和刷新操作用于构造相关的Bean。至此,根IoC容器以及相关Servlet的子IoC容器已经配置完成,子容器中管理的Bean一般只被该Servlet使用,因此,其中管理的Bean一般是“局部”的,如SpringMVC中需要的各种重要组件,包括Controller、Interceptor、Converter、ExceptionResolver等。相关关系如下图所示:
DispatcherServlet内部9大组件初始化
了解DispatcherServlet之前,先回顾一下DispatcherServlet的内置组件及其作用。
初识9大组件
组件名 | 作用 |
---|---|
HandlerMapping | 是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。 |
HandlerAdapter | 从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情. (Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。) |
HandlerExceptionResolver | 其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。 |
ViewResolver | ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。 |
RequestToViewNameTranslator | ViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。 |
LocaleResolver | 解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。 |
ThemeResolver | 用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。 |
MultipartResolver | 用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。 |
FlashMapManager | FlashMap管理器,它能够存储并取回两次请求之间的FlashMap对象。FlashMap用于在请求重定向的情况下传递数据** |
DispatcherServlet#onRefresh();
当IoC子容器构造完成后调用了onRefresh()方法,该方法的调用与initServletBean()方法的调用相同,由父类调用但具体实现由子类覆盖,调用onRefresh()方法时将前文创建的IoC子容器作为参数传入,查看DispatcherServletBean类的onRefresh()方法源码如下:
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";// 默认策略配置文件private static final Properties defaultStrategies;static {// Load default strategy implementations from properties file.// This is currently strictly internal and not meant to be customized// by application developers.try {// 初始化资源配置文件ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());}}@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}protected void initStrategies(ApplicationContext context) {// 1.初始化 MultipartResolver(文件上传解析器)initMultipartResolver(context);// 2.初始化 LocaleResolver(区域信息解析器,与国际化相关)initLocaleResolver(context);// 3.初始化 ThemeResolver(主题解析器)initThemeResolver(context);// 4.初始化 HandlerMappings(处理器映射器)initHandlerMappings(context);// 5.初始化 HandlerAdapters(处理器适配器)initHandlerAdapters(context);// 6.初始化 HandlerExceptionResolver(异常解析器)initHandlerExceptionResolvers(context);// 7.初始化 RequestToViewNameTranslator(视图名转换器)initRequestToViewNameTranslator(context);// 8.初始化 ViewResolvers(视图解析器 )initViewResolvers(context);// 9.初始化 FlashMapManager(flashMap管理器)initFlashMapManager(context);}
DispatcherServlet.properties文件 (默认策略)
# 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.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
DispatcherServlet.properties
里面记录了各种默认的加载策略。在下面的代码讲解中会涉及。
获取默认策略解析器
这里先介绍下默认策略解析器的解析创建流程,下面9大组件基本都会用到
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) {// 策略接口长名称作为 keyString key = strategyInterface.getName();// 这里 defaultStrategies 是一个类静态属性,指向classpath resource 文件 DispatcherServlet.properties// 该行获取策略接口对应的实现类,是','分割的实现类的长名称String value = defaultStrategies.getProperty(key);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 new LinkedList<>();}}
逻辑也比较简单,传入了相应解析器接口,根据接口长路径作为key去文件中寻找,找到就反射创建即可
initMultipartResolver
这一步顾名思义,就是配置多文件解析器。在 Spring 中,MultipartResolver
主要用来处理文件上传。默认情况下,Spring是没有 Multipart
处理的。如果想使用 Spring 的 Multipart
,则需要手动注入 Multipart
解析器,即MultipartResolver
。这样Spring 会检查每个请求是否包含 Multipart
,如果包含,那么 MultipartResolver
就会解析它。一般我们可以注入 CommonsMultipartResolver
。
代码比较简单,从容器中获取,有就赋下值,没有就算了,没有就说明没有上传文件场景
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";private void initMultipartResolver(ApplicationContext context) {try {//从容器中获取beanName为multipartResolver的解析器,并保存到当前的成员变量multipartResolver上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.// 默认是没有配置multipartResolver的.this.multipartResolver = null;if (logger.isTraceEnabled()) {logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");}}}
配置CommonsMultipartResolver
<!-- 配置CommonsMultipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 设置上传文件大小上限 --><property name="maxUploadSize" value="10485760" />
</bean>
initLocaleResolver
这里是初始化国际化解析器。逻辑和上面基本相同,不过如果容器中没有会根据默认配置文件创建默认解析器
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";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.// 使用默认策略,利用反射创建对象this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);if (logger.isTraceEnabled()) {logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");}}}
一般情况下, localeResolver 有三种注入实例
AcceptHeaderLocaleResolver
: 基于URL 参数的配置 。他会根据请求的URL后缀来判断国际化场景。比如 : “http://xxx?local=zh_CN”。local参数也可以是en_US。CookieLocaleResolver
: 基于Cookie的国际化配置。他是通过浏览器的 Cookies 设置取得Local对象。SessionLocaleResolver
:基于 Session 的配置,他通过验证用户会话中预置的属性来解析区域。常用的是根据用户本次会话过程中语言来决定语言种类。如果该会话属性不存在,则会根据 http的 accept-language 请求头确认国际化场景。
initThemeResolver
在Web开发中经常会遇到通过主题Theme 来控制网页风格,这将进一步改善用户体验。简单的说,一个主题就是一组静态资源(比如样式表和图片),他们可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常类似。这里就不再过多赘述。
简单说一下三个常用的主题解析器
FixedThemeResolver
:用于选择一个固定的主题SessionThemeResolver
:用于主题保存在用户的http session中CookieThemeResolver
:实现用户所选的主题,以cookie的形式存放在客户端的机器上
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";private void initThemeResolver(ApplicationContext context) {try {this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.themeResolver);}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);if (logger.isTraceEnabled()) {logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +"': using default [" + this.themeResolver.getClass().getSimpleName() + "]");}}}
initHandlerMappings
当客户端发出 Request 时, DispatcherServlet 会根据请求地址URL来获取指定的处理器方法来处理请求,URL->处理器方法的映射就维护在HandlerMapping中
在基于Spring mvc 的web应用程序中,我们可以为DispatcherServlet 提供多个 HandlerMapping 供其使用。DispatcherServlet 在选用 HandlerMapping 的过程中,将会根据我们所指定的一系列Handler 的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前HandlerMapping能够返回可用的Handler,DispatcherServlet 则是使用当前返回的Handler 进行Web请求的处理,而不再询问其他HandlerMapping,否则DispatcherServlet将按照各个HandlerMapping 的优先级进行询问,知道获取到一个可用的Handler 为止。
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";private void initHandlerMappings(ApplicationContext context) {// 初始化记录 HandlerMapping 对象的属性变量为nullthis.handlerMappings = null;// 1.根据属性detectAllHandlerMappings(默认为true)决定是启用所有的 HandlerMapping 对象,还是// 使用指定名称的 HandlerMapping 对象if (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.// 从容器及其祖先容器查找所有类型为 HandlerMapping 的 HandlerMapping 对象,记录到 handlerMappings 并排序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.// We keep HandlerMappings in sorted order.// 排序,关于这里的排序,可以参考 WebMvcConfigurationSupport 类中对各种 HandlerMapping bean进行定义时所使用的 order 属性AnnotationAwareOrderComparator.sort(this.handlerMappings);}}// 2.否则只获取当前上下文自定义配置的handlerMappingelse {try {// 获取名称为 handlerMapping 的 HandlerMapping bean 并记录到 handlerMappingsHandlerMapping 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.// 3.上述两步都未能获取到handlerMapping,则使用默认的handlerMapping,if (this.handlerMappings == null) {// 如果上面步骤从容器获取 HandlerMapping 失败,则使用缺省策略创建 HandlerMapping 对象记录到// handlerMappingsthis.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
detectAllHandlerMappings
参数用来 判断是否启用所有的HandlerMapping。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的HandlerMapping。如果检索所有的话,那么会对获取到的HandlerMapping进行优先级排序,不允许检索所有就获取上下文定义的,不允许检索所有以及上下文没有定义的话,那就走默认的
关于几种HandlerMapping,我们这里来简单看看。
-
RequestMappingHandlerMapping
:完成@Controller和@RequestMapping 的解析,并将解析保存。请求发送时与请求路径进行匹配对应找到合适的Handler。RequestMappingHandlerMapping 实现了 InitializingBean 接口,会在afterPropertiesSet 方法中调用解析。匹配调用则是在 DispatcherServlet doDispatch方法中的getHandler中调用了HandlerMapper中的getHandler中的getHandlerInternal方法。 -
BeanNameUrlHandlerMapping
:以beanName 作为key值@Component("/mappingTest3") public class MappingTest3 implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().write("BeanNameUrlHandlerMapping test!");return null;} }
-
SimpleUrlHandlerMapping
:基本逻辑是通过注入SimpleurlHandlerMapping 的mapping属性,mapping key为url, value为handler(beanName)。这里需要注意Controller必须要实现Controller接口。@Configuration public class MyConfig extends SimpleUrlHandlerMapping{@Beanpublic SimpleUrlHandlerMapping simpleUrlHandlerMapping(){SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();Properties properties = new Properties();properties.setProperty("simpleUrl","mappingTest2");simpleUrlHandlerMapping.setMappings(properties);//设置该handlermapping的优先级为1,否则会被默认的覆盖,导致访问无效simpleUrlHandlerMapping.setOrder(1);return simpleUrlHandlerMapping;} }
@Component("mappingTest2") public class MappingTest2 implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().write("SimpleUrlHandlerMapping test!");return null;} }
在这个例子中,我们访问localhost/simpleUrl
就会直接进入容器中名称为mappingTest2
的bean
的handleRequest
方法。
initHandlerAdapters
初始化处理器适配器。这里使用了适配器模式来设计。
private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;// 如果启用所有的HandlerAdapter。可以通过这个参数来控制是使用指定的HandlerAdapter,还是检索所有的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 {try {//没有启用则从Spring 容器中获取 beanName = handlerAdapter 并且类型是 HandlerAdapter 类型的beanHandlerAdapter 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。
initHandlerExceptionResolvers
基于 HandlerExceptionResolver
接口的异常处理,使用这种方式只需要实现 org.springframework.web.servlet.HandlerExceptionResolver
中的resolveException` 方法,该方法返回一个 ModelAndView 对象,在方法内部对异常的类型进行判断,然后尝试生成对应的 ModelAndView对象,如果该方法返回了null。则Spring会继续寻找其他的实现了HandlerExceptionResolver 接口的bean,直至找到一个可以返回ModelAndView 的 HandlerExceptionResolver 。
代码逻辑相同,不再赘述。
private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// We keep HandlerExceptionResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}else {try {HandlerExceptionResolver her =context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {// Ignore, no HandlerExceptionResolver is fine too.}}// Ensure we have at least some HandlerExceptionResolvers, by registering// default HandlerExceptionResolvers if no other resolvers are found.if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
initRequestToViewNameTranslator
当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;private boolean stripLeadingSlash = true;private boolean stripTrailingSlash = true;private boolean stripExtension = true;private UrlPathHelper urlPathHelper = new UrlPathHelper();...@Overridepublic String getViewName(HttpServletRequest request) {String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);return (this.prefix + transformPath(lookupPath) + this.suffix);}@Nullableprotected String transformPath(String lookupPath) {String path = lookupPath;if (this.stripLeadingSlash && path.startsWith(SLASH)) {path = path.substring(1);}if (this.stripTrailingSlash && path.endsWith(SLASH)) {path = path.substring(0, path.length() - 1);}if (this.stripExtension) {path = StringUtils.stripFilenameExtension(path);}if (!SLASH.equals(this.separator)) {path = StringUtils.replace(path, SLASH, this.separator);}return path;}...
}
private void initRequestToViewNameTranslator(ApplicationContext context) {try {this.viewNameTranslator =context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.viewNameTranslator);}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +"': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");}}}
initViewResolvers
在 Spring mvc 中。当 Controller 将请求处理结果放入到 ModelAndView 中以后,DispatcherServlet 会根据 ModelAndView 选择合适的视图进行渲染。在org.springframework.web.servlet.ViewResolver#resolveViewName
方法中,通过 viewName 创建合适类型的View。
代码逻辑相同,不再赘述。
private void initViewResolvers(ApplicationContext context) {this.viewResolvers = null;if (this.detectAllViewResolvers) {// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.Map<String, ViewResolver> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.viewResolvers = new ArrayList<>(matchingBeans.values());// We keep ViewResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.viewResolvers);}}else {try {ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);this.viewResolvers = Collections.singletonList(vr);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default ViewResolver later.}}// Ensure we have at least one ViewResolver, by registering// a default ViewResolver if no other resolvers are found.if (this.viewResolvers == null) {this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);if (logger.isTraceEnabled()) {logger.trace("No ViewResolvers declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
initFlashMapManager
FlashMapManager是用于检索和保存FlashMap实例的策略接口。
Spring3.1之后引入了一个叫做Flash Attribute的功能,主要就是为了解决表单重复提交数据的问题,应用POST/Redirect/GET(PRG)模式来防止重复提交数据(表单通过HTTP POST请求提交之后,用户在服务器端返回之前刷新了响应的页面,会导致原始的表单内容重复提交,可能会导致一些难以预料的结果)。所以采用重定向请求到成功页面,这样用户进行刷新不会进行提交表单,而是加载新的GET请求。但是重定向会引入无法传递请求参数和属性的问题,所以Spring的Flash Attribute就是为了请求重定向之前,解决临时存储的问题。
FlashMap为一个请求提供方法用于存储在另一个请求中使用的数据属性。
private void initFlashMapManager(ApplicationContext context) {try {this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.flashMapManager);}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");}}}
以上:内容部分参考:《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎各位大佬指正