Spring已经有点历史了,不过从来没有认真的看过。
最近比较闲,便瞄了两眼Spring,用一个东西工作,自然是要先看看这东西内部是怎么构成的,否则用的时候,踩在坑里,也不知道如何跳出来。
了解Spring最快的方法,当然是从启动开始。
Spring和Struts一样,是通过一个Servlet来接管web服务的,首先看一下web.xml的定义
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- Spring The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>SetCharacterEncoding</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>false</param-value> </init-param> </filter> <filter-mapping> <filter-name>SetCharacterEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
很简单,用一个上下文参数指定了根容器的配置文件路径,定义了一个监听器,用来初始化根容器,
然后定义了一个servlet,用来接管所有的请求,也通过参数形式指定了配置文件路径。
最后通过一个过滤器来指定请求的编码(这个不是必须的,并且也可以指定其他的过滤器类)
根容器的定义是为了给所有servlets和filters来提供服务,如果服务只有一个servlet的话,其实定义在servlet容器里也没啥问题。
其实根据wizard创建的spring mvc project里,默认root-context.xml就是空的。
另外,我们先看一下servlet容器的定义文件,先记住两个关键定义。
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd 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"> <!-- Enables the Spring MVC @Controller programming model --> <annotation-driven /> <context:component-scan base-package="jp.co.wqf" /> </beans:beans>
其中的意义,后面再解释。
我们先看看根容器的初始化,打开类[org.springframework.web.context.ContextLoaderListener],根据以下调用途径,看看根上下文是怎么创建的。
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);
}
}
}
如果web.xml里没有用参数[contextClass]对上下文类进行定义的话,那么会用默认的上下文类,其实这个默认的类是定义在一个properties文件的,
看一下defaultStrategies的创建过程
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.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, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
再看看[ContextLoader.properties]定义文件的内容
# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
使用的是上下文类[XmlWebApplicationContext]
然后在方法[protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)]里设定了根据参数[contextConfigLocation]设定的配置文件路径,对根上下文进行了初始化处理
wac.refresh();
最后,把这个跟上下文保存在了ServletContext的属性里
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
监听器执行完,就该初始化servlet了,打开[],根据一下调用,看一下servlet的初始化
org.springframework.web.servlet.HttpServletBean.init()->
org.springframework.web.servlet.FrameworkServlet.initServletBean()->
org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext()
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
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.
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;
}
先取出保存在ServletContext里的根上下文,目的是把它作为servlet上下文的父上下文。
根据source追踪,最终发现servlet默认使用的上下文类和根上下文使用的是一样的,只不过这次是直接定义在source里的。
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
onRefresh方法里,会对一些后续需要的类进行扫描分类,存在类的属性变量里,如果想改变这些类的话,可以从这个地方入手
/** MultipartResolver used by this servlet */
private MultipartResolver multipartResolver;
/** LocaleResolver used by this servlet */
private LocaleResolver localeResolver;
/** ThemeResolver used by this servlet */
private ThemeResolver themeResolver;
/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;
/** List of HandlerAdapters used by this servlet */
private List<HandlerAdapter> handlerAdapters;
/** List of HandlerExceptionResolvers used by this servlet */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet */
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMapManager used by this servlet */
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet */
private List<ViewResolver> viewResolvers;
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
大致的初始化过程就是这样的,初始化的过程,也同样是读取配置文件[root-context.xml,servlet-context.xml]的过程。
这个过程以后再描述。