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的到来.