springMVC系列源码之初始化过程——10
摘要: 本文以结合源码的方式来说明springMVC启动做了哪些初始化工作, 具体是怎么做的,通过哪些具体的步骤来完成初始化.以图文的形式来尽力说的清楚点,有理解不到位的地方望不吝指正.
前言:
写在前面: 接触springMVC的时间也不是多长,开始的时候是尽量求入门, 怎么样使用springMVC来搭建一个项目,以及与spring,hibernate的结合.好在有struts2,spring的基础,没有太大的困难,到现在虽然还没有使用springMVC来具体的做项目,但是还是想看看源码,深入了解一点东西,在看之前也有点忐忑,水平不够,理解不到位,看不懂,,,多多问题,想起一本书上面说的,看优秀的框架的源码是一种锻炼,看不懂没有关系,掉下来起码是个半仙.嗯,半仙!看!开始的时候困难是很多,英语水平也不够.但是坚持,还是会有进步的.现在大致弄懂了一些,先记录下来,与其写在一个有可能会被遗忘的角落里,何不写在这里? 觉得有用的可以看一眼,有错误的地方也希望指正.
1、 简介
解析过程的一个标准:先理清一条线、延伸的地方可以适当的深入一点、但是不要忘了自己初始的目的。先脉络、再细节。
在看源码之前的一点关于springMVC的东西. 所有的框架都是为了让我们的开发更加高效,省去繁杂重复的底层工作,同时要保证安全性,springMVC的特点就不再啰嗦。对springMVC的理解、可以建立在他的几个重要的类、或者接口上、开发人员这些接口和类的定义决定了设计的整体脉络、通过高度的抽象让springMVC骨感的展现在人面前、但是如果没有具体的实现、则如空中楼阁一般、那么springMVC是如何初始化一些bean供项目运行的时候使用,如果这些bean散落在项目中的多个角落,一旦项目运行之后,这些东西就会被编译成class文件,而此时我们想修改它显然是不现实的.解决的办法就是通过配置文件来动态的改变他们,达到我们想要的效果.这样做引来的问题就是我们需要有一个容器来存放他们 ,那么我们是用什么来存放springMVC的bean的呢.作为spring的一个延伸,我们不难想到spring的IOC容器.既然springMVC与spring的整合是无缝的.那么spring的IOC容器也可以作为springMVC的容器!同时又两者的容器又有点不同, springMVC的容器(WebApplicationContext、具体的说是XMLWebApplicationContext)是作为spring的一个子容器存放在Servlet中的.并且可以有多个.spring的容器只有一个!
对于struts2我们知道,他是将Servlet与框架完全分离开来,用xwork容器来存放数据和bean.而springMVC却不然,它本身就是一个标准的Servlet,这与springMVC的设计原则是相符的.框架本来就是为Servlet服务的,不应该脱离Servlet! springMVC既然是Servlet实现.那么就可以配置多个Servlet,我们可以根据自己的需求,配置多个拦截不同请求的DispatcherServlet. 前面也有提到过springMVC的容器可以有多个. 官方的图:
2、 初始化过程
2.1 起始:
对于一个java web项目,作为启动第一个加载的web.xml是信息的源泉.无论是我们想看是使用了什么框架,或者是使用了什么配置.在这里都可以有最直观的印象.就像我们在这里看到struts2的过滤器,springMVC的Servlet一样.当然如果使用spring上下文的话,少不了了spring的Listener----ContextLoaderListener:
2.2 spring容器的加载
2.2.1 对于spring的加载我们并不陌生——web.xml中:
当容器启动时加载到web.xml中关于spring的配置文件的位置的属性和spring的Listener的时候、就会初始化好spring的WebApplicationContext、并且所有在spring配置文件中的bean(当然有时候也包括数据库的配置也交给spring去管理)已经被实例化好就 ok。这里不是我们的重点、不再深入源码看如何去加载。下面代码中的<context-param>会第一个被加载、然后是Listener。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:config/springAnnotation-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2.3springMVC的初始化
2.3.1 DispatcherServlet、作为springMVC 的前置控制器、是一个标准的Servlet、所以springMVC的初始化工作应该从DispatcherServlet的init()方法开始、但是我们发现DispatcherServlet中根本就没有init()方法、此时第一个想法就是这个方法在其父类中、这里不妨先看一下DispatcherServlet的结构层次。当然使用IDE工具的也可以鼠标放在DispatcherServlet、按F4、观察一下DispatcherServlet中所有的属性、方法、在里面寻找自己想查找的属性或者方法。
2.3.2 在HttpServletBean这个抽象类中可以看到init()方法(为节省篇幅、这里我将关于log的内容删掉了、看着简洁直接一点):
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 通过初始化参数来设置bean的属性。
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
// Let subclasses do whatever initialization they like.
initServletBean();
}
这个方法分成两个部分
a) initServletBean()之前——加载springMVC的配置文件、对我们配置的标签进行解析和将系统默认的bean的各种属性设置到对应的bean属性
b) initServletBean()是为子类提供模版、让子类根据自己的需求实现不同的ServletBean的初始化。
c) 从这里可以看出HttpServletBean的作用:(1)是一个Servlet的基类、对web.xml中配置的关于指定Servlet的value所代表的值或者资源文件解析、将得到的属性设置到对应的bean的属性中、当然前提是bean中这个属性有正确的setXXX()方法、他会自动的转换属性的类型、如果没有对应的setXXX()方法、则这个属性会被忽略、他并不对request做任何的处理只是继承HttpServlet的方法、没有具体的实现其作用、(2)为子类提供初始化模版、具体功能由子类实现、如果我们想要有自己的DispatcherServlet、那么我们完全可以继承HttpServletBean、重写他的initServletBean()方法来完成我们自己的MyDispatcherServlet!这是springMVC超强的扩展性的一个体现、也是springMVC设计原则的一个体现——A key design principle in Spring Web MVC and in Spring ingeneral is the "Openfor extension, closed for modification" principle.
2.3.3 回头看看DispatcherServlet中的关于具有自己特色的initServletBean()方法的实现。同样我们在DispatcherServlet中仍然没有找到方法initServletBean()方法、不难想像、必然又是在父类中实现的、通过跟踪可以发现此方法是在FrameworkServlet方法中实现的:
protected final void initServletBean() throws ServletException {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
省去关键的代码后发现此方法是如此的简洁、调用两个方法:
a) 一个是初始化WebApplicationContext、
b) 一个是初始化FramesServlet、而后面一个方法在DispatcherServlet和他自身中并没有具体的实现、
c) 所以关键来了——initWebApplicationContext():
在这里先提取一个方法:configureAndRefreshWebApplicationContext(cwac)、顾名思义:是配置和refresh我们的springMVC的WebApplicationContext的配置。他会回调DispatcherServlet中的onRefresh()方法、依据创建、或者找到的上下文来初始化默认加载的类、此方法是FrameworkServlet中的方法。
i. 我们跟踪到方法内部、简化后代码如下:
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);
}
return wac;
}
ii. 获取spring的WebApplicationContext作为根上下文(spring上下文在服务器刚启动中最先加载、所以这里可以通过springMVC的一个工具类来获取它。web.xml中配置的ContextLoaderListener、load-on-startup 值为1)
iii. 如果WebApplicationContext是我们手动注入的、直接拿来赋值给FrameworkServlet中定义的webApplicationContext、但是此时的webApplicationContext并不是我们这个DispatcherServlet特有的、所以要对其进行处理、比如设置父上下文、id等等。最后调用configureAndRefreshWebApplicationContext(cwac)方法。
iv. 如果不是手动注入的、那么就在servletContext中寻找一个已经注册的WebApplicationContext作为其上下文。contextAttribute、findWebApplicationContext()并且这个已经是初始化好的、具有父上下文、id等等属性。最后调用configureAndRefreshWebApplicationContext(cwac)方法。
v. 如果上述步骤没有找到、创建一个新的createWebApplicationContext(rootContext)、最后调用configureAndRefreshWebApplicationContext(cwac)方法。
vi. 当前的WebApplicationContext是否已经被refresh、如果没有则refresh。
vii. 当前的WebApplicationContext是否作为ServletContext的一个属性?默认是true、即将其放在为ServletContext中。key为:org.springframework.web.servlet.FrameworkServlet.CONTEXT.getServletName()。eg:org.springframework.web.servlet.FrameworkServlet.CONTEXT.springMVC (web.xml中配置的ServletName、从这里也可以看出、不同的请求对应的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中)
viii. 从debug跟踪可以看到、在初始化之前、springMVC的配置文件就已经加载好了、但是当springMVC的上下文创建的时候会refresh。
从这里可以看出、FrameworkServlet是springMVC框架的一个基类、初始化springMVC的WebApplicationContext、并且将其作为spring的ApplicationContext的一个子类整合在一起。具体作用
ix. 生成并且管理为每个Servlet制定的WebApplicationContext、
x. Publishes events on requestprocessing, whether or not a request is successfully handled
xi. 他可以单独作为一个类来使用、我们可以继承他来实现自己特色的DispatcherServlet、是springMVC处处体现open for extention的一个体现。
上面的创建过程中不断的强调一个回调函数onRefresh();WebApplicationContext创建的最后都会调用onRefresh(Application context)、此方法是在DispatcherServlet中。他会根据传入的WebApplicationContext初始化一系列的策略。onRefresh(Application context):
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);
}
这里仅以初始化List<HandlerMapping>来说明过程、其他的类似。
1. 在前面文章中提到过、HandlerMapping是根据request来检测包含处理这个request的handler和handlerinteceptor的HandlerExcutorChain。下面具体看initHandlerMapping(Applicationcontext)方法:
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<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.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);
}
}
2. 先将DispatcherServlet中的List<HandlerMapping> handlerMapping清空.判断是否检测所有HandlerMapping, 如果是则会把上下文中所有HandlerMapping类型的Bean都注册在handlerMappings这个List变量中,非空的情况下根据order进行排序。
3. 不开启:则只获取当前WebApplicationContext中名称为”handlerMapping”的一个bean的实例放入List<HandlerMapping>中.则将尝试获取名为handlerMapping的Bean,新建一个只有一个元素的List,将其赋给handlerMappings。
4. 如果此时List<HandlerMapping>仍然为空, 则使用默认的加载策略:getDefaultStrategies(context, HandlerMapping.class)
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<T>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<T>();
}
}
此方法是根据传入的context和具体的某个接口类型去寻找默认的加载策略.它是一个范型的方法,承担所有SpringMVC编程元素的默认初始化策略。以传递类的名称为键,从defaultStrategies这个Properties变量中获取实现类,然后反射初始化。比如上面传入的HandlerMapping.class, 则是在context中查找HandlerMapping的一个实例:BeanNameUrlHandlerMapping作为Defaultstrategy.defaultStrategies是有静态块加载的:
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 'DispatcherServlet.properties': " + ex.getMessage());
}
}
而默认配置项都是通过一个资源文件:DispatcherServlet.properties 来配置的、关于这个变量的引用和具体的配置项:
/**
* Name of the class path resource (relative to the DispatcherServlet class)
* that defines DispatcherServlet's default strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
位置:org.springframework.web.servlet包下。具体配置项:
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
上面的context没有特别注明都是当前的WebApplicationContext,准确的说是XMLWepApplicationContext.
.到此, springMVC的初始化就完成的, 静静的等待request的到来.