springMVC系列源码之初始化过程——10

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的容器可以有多个. 官方的图:


                               springMVC系列源码之初始化过程——10  



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


补充:

        了解了整体过程之后、我们对DispatcherServlet的体系会有个很层次分明的认识、HttpServletBean完成的是<init-param>配置元素的依赖注入,FrameworkServlet完成的是容器上下文的建立,DispatcherServlet完成的是SpringMVC具体编程元素的初始化策略。




springMVC系列源码之初始化过程——10

上一篇:RDIFramework.NET 答客户问(2014-02-23)


下一篇:编程习惯-enum的用法