我打算先看Mvc,目前毕竟需要先研究源码好调试情况。
Spring框架提供了构建Web应用程序的全功能MVC模块,通过策略接口,Spring框架时高度可配置的,而且支持多种视图技术。
SpringMVC分离了控制器,模型对象,分派器以及处理程序对象的角色。此分离让它们更容易进行定制。
Servlet
- MVC是基于Servlet功能实现的,通过实现Servlet接口,通过实现Servlet接口的DispatcherServlet来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射,视图解析,本地语言,主题解析以及上载文件支持。
- 默认的处程序是非常简单的Controller接口,只有一个方法ModelAndView handleRequest(request,response)。Spring提供了一个控制器层次结构,可以派生子类。如果应用程序需要处理输入表单。那么可以基础AbstractFormController如果需要多页输入处理到一个表单,那么可以基础AbstractWizardFormController。
- 其MVC解决了:将Web页面的请求传给服务器。根据不同的请求处理不同的逻辑单元。返回处理结果数据并跳转至响应的页面
这里是基于XML配置的不是基于SpringBoot ApplicationProperties
运行
- 配置web.xml
其使用web.xml初始化配置信息,如index页面,servlet,servlet-mapping,filter,listener,启动加载级别等。但是其实现原理是通过servlet拦截所有URL来达到控制的目的。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--添加过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置spring-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 使用ContextLoaderListener配置时,需要告诉它Spring配置文件的位置 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--自定义首页-->
<!-- <welcome-file-list>
<welcome-file>/first/page</welcome-file>
</welcome-file-list>-->
<!--配置404错误页面-->
<error-page>
<error-code>404</error-code>
<location>/error_pages/404.jsp</location>
</error-page>
<!--读取静态文件-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
</web-app>
MVC最关键的是要配置两个地方。
contextConfigLocation:Spring的核心就是配置文件,这个参数就是使Web与Spring的配置文件相结合的一个关键配置
DispatcherServlet:包含了SpringMVC的请求逻辑,Spring使用此类拦截Web请求并进行相应的逻辑处理
- 创建Spring的配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.concretepage.controller" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
InternalResourceViewResp;ver是一个辅助bean,会在ModelAndView返回的视图名前面加上prefix指定的前缀,再在最后加上suffix指定的后缀,列如:由于XXController返回的ModelAndView中的视图名是testview,故在视图解析器在/WEB-INF/jsp/testview.jsp处查找视图。
- 创建model
用于承载数据 - 创建controller
控制器用于处理Web请求。
ModelAndView handleRequestInternal(HttpServletRequest arg0,HttpServletResponse arg1) throws Exception{
......
return new ModelAndView();
}
控制器执行方法都必须返回一个ModelAndView。
-
创建视图文件
视图文件用于展现请求处理结果 -
创建Servlet配置文件Spring-servlet
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="simpleUrlMapping"
class="org.springframework.web.servlet.handler.SompleUrlHadnlerMapping">
<property name="mappings">
<props>
<prop key="/userlist.htm">userController</prop>
</props>
</property>
</bean>
<bean id ="userController" class="test.contrlloer.UserController" />
</beans>
因为SpringMVC是基于Servlet的实现,所以在Web启动时,服务器会实现尝试加载对应于Servlet的配置文件,而为了让项目更加模块化,通常将Web部分配置存放于此配置文件中。
一个SpringMVC应用完成
ContextLoaderListener
从功能实现的分析,先从web.xml开始,在web.xml首先配置的就是ContextLoaderListener。
当使用编程方式的时候,可以将Spring配置信息作为参数传入Spring容器中。如
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
但是在Web下,我们需要更多的时与Web环境互相结合,通常就是将路径以context-param的方式注册并使用ContextLoaderListener进行监听读取
ContextLoaderListener作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为其实现了ServletContextListener此接口。在web.xml配置这个监听器,启动容器时会默认执行它实现的方法,使用ServletContextListener接口。开发者能够在客户端请求提供服务之前向ServletContext中添加任意的对象,此对对象在ServletContext启动的时候被初始化。然后再ServletContext整个运行期间都是可见的。
每个Web应用都有一个ServletContext与之相关,ServletContext对象再应用启动时被创建,在应用关闭的时候被销毁,ServletContext在全局范围内有效,类型于应用中的一个全局变量。
在ServletContextListenrer中的核心逻辑便是初始化WebApplicationContext实例并存放至ServletContext中。
ServletContextListener的使用
- 创建自定义ServletContextListener
实现在系统启动时添加自定义的属性,以便在全局范围内可以随时调用,系统启动的时候会调用ServletContextListener实现类的contextInitialized方法。所以在其方法实现初始化逻辑
public class MyDataContextListener implements ServletContextListener{
private ServletContext context =null;
public MyDataContextListener(){
}
public void contextInitialized(ServletContextEvent event){
this.context = event.getServletContext();
context =setAttribute("MyData","this data");
}
public void contextDestroyed(ServletContextEvent even){
this.context =null;
}
}
- 注册监听器
在web.xml注册自定义的监听器
<listener>
com.test.MyDataContextListener
</listener>
- 测试
Web应用启动时,可以在任何的Servlet or JSP获取初始化的参数
String myData = (String)getServletContext.getAttribute("myData");
Spring中的ContextLoaderListener
分析了ServletContextListener 后再分析ContextLoaderListener的实现。
ServletContext启动后会调用ServletContextListener的contextInitialized方法。
ContextLoaderListener.class
@Override
public void contextInitialized(ServletContextEvent event) {
//初始化WevApplicationContext
initWebApplicationContext(event.getServletContext());
}
这里的WebApplicationContext:再web应用中。我们会用到它,它继承自ApplicationContext。
在ApplicationContext的基础之上又追加了一些特定于Web操作及属性。类似通过编程方式使用Spring时使用的ClassPathXmlApplicationContext类提供的功能。
ContextLoader.class
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
//如果web.xml存在多次ContextLoader定义
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 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()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//记录在servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
initWebApplicationContex函数主要时体现了创建WebApplicationContext实例的一个功能架构,步骤如下
- WebApplicationContext存在性的验证
配置只允许声明一次ServletContextListener,多次声明会扰乱Spring执行逻辑,所以首先对此验证。
如果创建WebApplicationContext实例会记录在ServletContext中以方便全局调用,而使用key就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,所以验证方式就是查看ServletContext实例中是否有对应key的属性。 - 创建WebApplicationContext实例
如果通过验证,则创建WebApplicationContext实例的工作委托给了createWebApplicationContext函数。
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
其中,在ContextLoader类中有这样的静态代码块
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, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
根据以上静态diamagnetic块内容,可得当前类ContextLoader同样目录下存在属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个实现类通过反射方式进行实例的创建
- 将实例记录在servletContext中
- 映射当前的类加载器于创建的实例到全局变量currentContextPerThread中
DispatcherServlet
Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例。而真正的逻辑实现其实实在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。
servlet
servlet是java编写的程序,其基于HTTP协议,在服务端运行的(如,Tomcat)是按照servlet规范编写的一个java类,主要是处理客户端的请求并将其结果发送到客户端。
servlet生命周期是由servlet的容器控制的。分为三个阶段:初始化,运行和销毁。
-
初始化阶段
servlet容器加载servlet类,把servlet类的.class文件中数据读到内存中。
servlet容器创建一个ServletConfig对象。ServletConfig对象包含了servlet的初始化配置信息。
servlet容器创建一个servlet对象。
servlet容器调用servlet对象的init方法进行初始化。 -
运行阶段
当servlet容器接收到一个请求时servlet容器会针对这个请求创建servletRequest和ServletResponse对象,然后调用service方法,并把这两个参数传递给service方法。
service方法通过servletRequest对象获得请求的信息,并处理该请求,再通过servletResponse对象生成这个请求的相应结果,然后销毁servletRequest和servletResponse对象。不管是post or get 最终都由service方法处理。 -
销毁阶段
当Web应用被终止时,servlet容器会调用servlet对象的destroy方法,然后再销毁servlet对象。同时也会销毁与servlet对象相关联的servletConfig对象。我们可以再destroy方法的实现中释放servlet所占资源,如关闭数据库连接,关闭文件输入输出流等。
servlet框架由两个java包组成,javax.servlet 和 javax.servlet.http。
javax.servlet :定义了所有的servlet类都必须实现或扩展的通用接口和类。
avax.servlet.http:定义了采用HTTP通信协议的HttpServlet类。
servlet被设计成请求驱动,其请求可能包含多个数据项。当Web容器接收到某个servlet请求时,servlet把请求封装成一个HttpServletRequest对象,然后把对象传给servlet的对象的服务方法。
HTTP请求方式包括delete,get,options,post,put,trace。
HttpServlet类中分别提供了相应方法。doDelete(),doGet()…
DispatcherServlet的初始化
上面可得servlet初始化会调用其init方法。那么先查看在DispatcherServlet中是否重写了init方法。
在其父类 HttpServletBean中找到该方法。
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
//解析init-param并封装至pvs中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//将当前这个servlet类转换为BeanWrapper,使得能以Spring方式对init-param的值进进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册自定义属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEdit进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//空实现,留给子类覆盖
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.留给子类扩展
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
DIspatcherServlet初始化主要通过当前servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入,如contextAttribute,contextClass,nameSpace,contextConfigLocation等,都可以在web.xml中以初始化参数的方式配置在servlet声明中。
DispatcherServlet继承自FrameworkServlet,其包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。属性注入包含以下:
- 封装及验证初始化参数
ServletConfigPropertyValues 除了封装属性外还有对属性验证的功能
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
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中配置中配置的封装,当然用户可以通过对requiredProperties参数的初始化来强制验证某些属性的必要性,这样在属性封装过程中一旦监测到requiredProperties中的属性没有指定初始值,就会抛出异常
- 将当前servlet实例转化成BeanWrapper实例
PropertyAccessFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要用于指定实例转化为Spring中可以除了的BeanWrapper实例。
- 注册相对于Resource的属性编辑器
属性编辑器,容器的基本实现讲解过,这里使用属性编辑器的目的实在对当前实例(DispatcherServlet)属性注入过程中一旦遇到Resource类型的属性就会使用ResourceEditor去解析
-
属性注入
BeanWrapper为Spring中的方法,支持SPring的自动注入,最常用的属性无非就是contextAttribute,contextClass,nameSpace,contextConfigLocation等 -
servletBean的初始化
在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例。而在这个函数中最重要的就是对这个实例进行进一步的补充初始化。
继续查看initServletBean(),父类FrameworkServlet覆盖了HttpServletBean中的initServletBean函数
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
//设计为子类覆盖
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
上述函数设计了计时器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServlet()用于子类的覆盖操作。而作为关键的初始化逻辑委托给了initWebApplicationContext()
WebApplicationContext初始化
创建or刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
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
//根据contextAttribute属性加载WebApplicationContext
wac = findWebApplicationContext();
}
if (wac == null) {
// 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.
synchronized (this.onRefreshMonitor) {
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;
}
- 创建web ApplicationContext实例
- 通过构造函数的注入进行初始化
进入initWebApplicationContext函数判断this.WebApplicationContext!=null便可确定this.webApplicationContext是否通过构造函数来初始化的。因为在Web种包含SpringWeb的核心逻辑的DispatcherServlet只可以被声明一次,在Spring中已经存在验证。 - 通过contextAttribute进行初始化
通过在web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为WebApplicationContext.class.getName()+".ROOT",也就是在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+".ROOT"为key放入ServletContext中
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;
}
- 重新创建WebApplicationContext实例
如果上面两种都没有找到任何突破,则重新创建新的实例
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//获取servlet的初始化参数contextClass,如果没有配置默认为XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//通过反射方式实例化contextClass
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//parent为在ContextLoaderListener中创建的实例
//在ContextLoaderListener加载的时候初始化的WebApplicationContext类型实例
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
//获取contextConfigLocation属性,配置在servlet初始化参数中
wac.setConfigLocation(configLocation);
}
//初始化Spring环境包括加载配置文件
configureAndRefreshWebApplicationContext(wac);
return wac;
}
configureAndRefreshWebApplicationContext
无论时通过构造函数注入还是单独创建,都会调用configureAndRefreshWebApplicationContext方法对已经创建的WebApplicationContext实例进行配置及刷新。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 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);
//加载配置文件及整合parent到wac
wac.refresh();
}
无论调用方式如何编号,只有是提供ApplicableContext所提供的功能最后都免不了使用公共父类AbstractApplicationContext提供的refresh()进行配置文件的加载
刷新
onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写,主要用于刷新Spring在Web功能实现中所必须使用的全局变量
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
其初始化的有9个参数
-
MultipartResolver
用于处理文件上传,xml配置。
MultipartResolver就是在 initMultipartResolver(context) 中被加入到DIspatcherServlet中的。因为之前的步骤已经完成了Spring中配置文件的解析,所以在此只要在配置文件注册过都可以通过ApplicationContext 提供的getBean方法来直接获取对应bean,进而初始化MutipartResolver中multipartResolver变量。 -
初始化LocaleResolver
Spring国际化配置有3种方式
- 基于url参数的配置
- 基于session的配置
- 基于cookie的国际化配置
- 初始化ThemeResolver
主题,就是一组静态资源
- 初始化HandlerMappings
当客户端发出Request时,DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller。
在基于SpringMVC的Web应用程序种,我们可为DispatcherServlet提供多个HandlerMapping的过程中,根据我们指定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,不再继续询问其他的HandlerMapping。否则DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
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 {
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");
}
}
}
xml配置
<inti-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value>fasle</param-value>
</init-param>
如果期望MVC加载指定的handlermapping时,可修改,如上设置值为fasle
- 初始化HandlerAdapters
适配器,可以使接口不兼容而无法一起工作的类协同工作。做法就是将类自己包裹在一个已存在的类中。
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
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 {
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.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}
同样在初始化过程中涉及了一个变量detectAllHandlerAdapters,其作用和detectAllHandlerMappings类型,只不过作用对象为handlerAdapter,可同上一样配置强制系统只加载beanname为handlerAdapter的handlerAdapter
<inti-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value>fasle</param-value>
</init-param>
如果找不到对应的bean,那么系统会尝试加载默认的适配器
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
defaultStrategies,在DispatcherServlet中有一段static代码
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());
}
}
在系统加载的时候,defaultStrategies根据当前路径DispatcherServlet.properties来初始化本身,查看DispatcherServlet.properties中对应于HandlerAdapter的属性。
如果开发人员没有在配置文件中定义自己的适配器,那么Spring默认加载配置文件中的3个适配器。
总控制的派遣器servlet通过处理器映射到处理器后,会轮询处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,eg简单的控制器类型,注解控制器类型or远程调用处理器类型,来选择某个适当的处理器适配器的实现,从而适配当前的HTTP请求。
- HTTP请求处理器适配器(HttpRequestHandlerAdapter)
- 简单控制器处理器适配器(SimpleControllerHandlerAdapter)
- 注解方式处理器适配器(AnnotationMethodHandlerAdapter)
具体的这3个适配器大意可以网上查询,这里不多言了。
- 初始化HandlerExceptionResolver
基于HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现resolveException方法,该方法返回一个ModelAndView对象,在方法内部对异常类型进行判断,然后尝试生成对应的ModelAndView对象,if(null) 则逐个执行直到返回一个ModelAndView对象
此类必须声明到Spring中去,让Spring管理他。在applicationContext.xml中增加其内容
- 初始化RequestToViewNameTranslator
当Controller处理器方法没有返回一个View对象or逻辑视图名称,并且该方法中没有直接网response的输出流里, 写数据时,Spring就会采用约定好的方式提供一个逻辑视图名称
通过RequestToViewNameTranslator的getViewName方法实现,其支持的用户定义的属性
prefix
suffix
separator
stripLeadingSlash
stripTrailingSlash
stripExtension
urlDecode
当我们没有手动定义名为viewNameTranslator的Bean的时候,Spring会给我们提供默认的viewNameTranslator。
- 初始化ViewResolvers
SpringMVC中当Controller将请求处理结果放入到ModelAndView中后,DIspatcherServlet会根据ModelAndVIew选择合适的视图进行渲染。其解决了选择合适的View,创建View对象。
<bean class="org.Springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/">
<property name="suffix" value=".jsp"/>
</bean>
- 初始化FlashMapManager
Flashattributes提供给了一个请求存储属性,可供其他请求使用,在使用重定向时候非常必要,post/redirect/get模式。flash attributes在重定向之前暂存(就像存在session中)以便重定向后还有使用,并立即删除。
整体小结
- web.xml先配置ContextLoaderListener-----获取param----->ServletContextListener
- ServletContextListener 初始化WebApplicationContext实例并存至ServletContext中
- ContextLoaderListener为辅助功能,用于创建WebApplicationContext类型实例,真正逻辑实现在DispatcherServlet
- ContextLoader 静态代码块,读取属性文件,提取将要实现的WebApplicationContext接口实现类
- 接下来
- DispatcherServlet ->FrameworkServlet ->HttpServletBean,ApplicationContextAware
HttpServletBean -> Httpservlet ->重写其init方法 - inti方法->servletBean初始化->FrameworkServlet->覆盖initServletBean(对WebApplicationContext进一步初始化)—关键初始化–>initWebApplicationContext()
- initWebApplicationContext()-----寻找or创建对应的WebApplicationContext实例 ------调用onRefresh()进行配置文件加载
- onRefresh刷新Spring在Web功能实现的全局变量,如果没有手动配置bean--------------------- DispatcherServlet静态代码块初始化本身对应的init**属性