- 浏览: 51104 次
- 性别:
- 来自: 成都
文章分类
最新评论
SpringMVC源码剖析(三)- DispatcherServlet的初始化流程
转载:
在我们第一次学Servlet编程,学java web的时候,还没有那么多框架。我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转到我们定义好的jsp页面。Servlet类编写完之后在web.xml里注册这个Servlet类。
除此之外,没有其他了。我们启动web服务器,在浏览器中输入地址,就可以看到浏览器上输出我们写好的页面。为了更好的理解上面这个过程,你需要学习关于Servlet生命周期的三个阶段,就是所谓的“init-service-destroy”。
以上的知识,我觉得对于你理解SpringMVC的设计思想,已经足够了。SpringMVC当然可以称得上是一个复杂的框架,但是同时它又遵循Servlet世界里最简单的法则,那就是“init-service-destroy”。我们要分析SpringMVC的初始化流程,其实就是分析DispatcherServlet类的init()方法,让我们带着这种单纯的观点,打开DispatcherServlet的源码一窥究竟吧。
1.<init-param>配置元素读取
用Eclipse IDE打开DispatcherServlet类的源码,ctrl+T看一下。
DispatcherServlet类的初始化入口方法init()定义在HttpServletBean这个父类中,HttpServletBean类作为一个直接继承于HttpServlet类的类,覆写了HttpServlet类的init()方法,实现了自己的初始化行为。
01 @Override
02 public final void init() throws ServletException {
03 if (logger.isDebugEnabled()) {
04 logger.debug("Initializing servlet '" + getServletName() + "'");
05 }
06
07 // Set bean properties from init parameters.
08 try {
09 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
10 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
11 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
12 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
13 initBeanWrapper(bw);
14 bw.setPropertyValues(pvs, true);
15 }
16 catch (BeansException ex) {
17 logger.error("Failed to set bean<span></span> properties on servlet '" + getServletName() + "'", ex);
18 throw ex;
19 }
20
21 // Let subclasses do whatever initialization they like.
22 initServletBean();
23
24 if (logger.isDebugEnabled()) {
25 logger.debug("Servlet '" + getServletName() + "' configured successfully");
26 }
27 }
这里的initServletBean()方法在HttpServletBean类中是一个没有任何实现的空方法,它的目的就是留待子类实现自己的初始化逻辑,也就是我们常说的模板方法设计模式。SpringMVC在此生动的运用了这个模式,init()方法就是模版方法模式中的模板方法,SpringMVC真正的初始化过程,由子类FrameworkServlet中覆写的initServletBean()方法触发。
再看一下init()方法内被try,catch块包裹的代码,里面涉及到BeanWrapper,PropertyValues,ResourceEditor这些Spring内部非常底层的类。要深究具体代码实现上面的细节,需要对Spring框架源码具有相当深入的了解。我们这里先避繁就简,从代码效果和设计思想上面来分析这段try,catch块内的代码所做的事情:
注册一个字符串到资源文件的编辑器,让Servlet下面的<init-param>配置元素可以使用形如“classpath:”这种方式指定SpringMVC框架bean配置文件的来源。
将web.xml中在DispatcherServlet这个Servlet下面的<init-param>配置元素利用JavaBean的方式(即通过setter方法)读取到DispatcherServlet中来。
这两点,我想通过下面一个例子来说明一下。
我在web.xml中注册的DispatcherServlet配置如下:
01 <!-- springMVC配置开始 -->
02 <servlet>
03 <servlet-name>appServlet</servlet-name>
04 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
05 <init-param>
06 <param-name>contextConfigLocation</param-name>
07 <param-value>classpath:spring/spring-servlet.xml</param-value>
08 </init-param>
09 <load-on-startup>1</load-on-startup>
10 </servlet>
11 <servlet-mapping>
12 <servlet-name>appServlet</servlet-name>
13 <url-pattern>/</url-pattern>
14 </servlet-mapping>
15 <!-- springMVC配置结束 -->
可以看到,我注册了一个名为contextConfigLocation的<init-param>元素,其值为“classpath:spring/spring-servlet.xml”,这也是大家常常用来指定SpringMVC配置文件路径的方法。上面那段try,catch块包裹的代码发挥的作用,一个是将“classpath:spring/spring-servlet.xml”这段字符串转换成classpath路径下的一个资源文件,供框架初始化读取配置元素。在我的工程中是在spring文件夹下面的配置文件spring-servlet.xml。
另外一个作用,就是将contextConfigLocation的值读取出来,然后通过setContextConfigLocation()方法设置到DispatcherServlet中,这个setContextConfigLocation()方法是在FrameworkServlet类中定义的,也就是上面继承类图中DispatcherServlet的直接父类。
我们在setContextConfigLocation()方法上面打上一个断点,启动web工程,可以看到下面的调试结果。
HttpServletBean类的作者是大名鼎鼎的Spring之父Rod Johnson。作为POJO编程哲学的大师,他在HttpServletBean这个类的设计中,运用了依赖注入思想完成了<init-param>配置元素的读取。他抽离出HttpServletBean这个类的目的也在于此,就是“以依赖注入的方式来读取Servlet类的<init-param>配置信息”,而且这里很明显是一种setter注入。
明白了HttpServletBean类的设计思想,我们也就知道可以如何从中获益。具体来说,我们继承HttpServletBean类(就像DispatcherServlet做的那样),在类中定义一个属性,为这个属性加上setter方法后,我们就可以在<init-param>元素中为其定义值。在类被初始化后,值就会被注入进来,我们可以直接使用它,避免了样板式的getInitParameter()方法的使用,而且还免费享有Spring中资源编辑器的功能,可以在web.xml中,通过“classpath:”直接指定类路径下的资源文件。
注意,虽然SpringMVC本身为了后面初始化上下文的方便,使用了字符串来声明和设置contextConfigLocation参数,但是将其声明为Resource类型,同样能够成功获取。鼓励读者们自己继承HttpServletBean写一个测试用的Servlet类,并设置一个参数来调试一下,这样能够帮助你更好的理解获取配置参数的过程。
2.容器上下文的建立
上一篇文章中提到过,SpringMVC使用了Spring容器来容纳自己的配置元素,拥有自己的bean容器上下文。在SpringMVC初始化的过程中,非常关键的一步就是要建立起这个容器上下文,而这个建立上下文的过程,发生在FrameworkServlet类中,由上面init()方法中的initServletBean()方法触发。
01 @Override
02 protected final void initServletBean() throws ServletException {
03 getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
04 if (this.logger.isInfoEnabled()) {
05 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
06 }
07 long startTime = System.currentTimeMillis();
08
09 try {
10 this.webApplicationContext = initWebApplicationContext();
11 initFrameworkServlet();
12 }
13 catch (ServletException ex) {
14 this.logger.error("Context initialization failed", ex);
15 throw ex;
16 }
17 catch (RuntimeException ex) {
18 this.logger.error("Context initialization failed", ex);
19 throw ex;
20 }
21
22 if (this.logger.isInfoEnabled()) {
23 long elapsedTime = System.currentTimeMillis() - startTime;
24 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
25 elapsedTime + " ms");
26 }
27 }
initFrameworkServlet()方法是一个没有任何实现的空方法,除去一些样板式的代码,那么这个initServletBean()方法所做的事情已经非常明白:
1 this.webApplicationContext = initWebApplicationContext();
这一句简单直白的代码,道破了FrameworkServlet这个类,在SpringMVC类体系中的设计目的,它是 用来抽离出建立 WebApplicationContext 上下文这个过程的。
initWebApplicationContext()方法,封装了建立Spring容器上下文的整个过程,方法内的逻辑如下:
获取由ContextLoaderListener初始化并注册在ServletContext中的根上下文,记为rootContext
如果webApplicationContext已经不为空,表示这个Servlet类是通过编程式注册到容器中的(Servlet 3.0+中的ServletContext.addServlet() ),上下文也由编程式传入。若这个传入的上下文还没被初始化,将rootContext上下文设置为它的父上下文,然后将其初始化,否则直接使用。
通过wac变量的引用是否为null,判断第2步中是否已经完成上下文的设置(即上下文是否已经用编程式方式传入),如果wac==null成立,说明该Servlet不是由编程式注册到容器中的。此时以contextAttribute属性的值为键,在ServletContext中查找上下文,查找得到,说明上下文已经以别的方式初始化并注册在contextAttribute下,直接使用。
检查wac变量的引用是否为null,如果wac==null成立,说明2、3两步中的上下文初始化策略都没成功,此时调用createWebApplicationContext(rootContext),建立一个全新的以rootContext为父上下文的上下文,作为SpringMVC配置元素的容器上下文。大多数情况下我们所使用的上下文,就是这个新建的上下文。
以上三种初始化上下文的策略,都会回调onRefresh(ApplicationContext context)方法(回调的方式根据不同策略有不同),onRefresh方法在DispatcherServlet类中被覆写,以上面得到的上下文为依托,完成SpringMVC中默认实现类的初始化。
最后,将这个上下文发布到ServletContext中,也就是将上下文以一个和Servlet类在web.xml中注册名字有关的值为键,设置为ServletContext的一个属性。你可以通过改变publishContext的值来决定是否发布到ServletContext中,默认为true。
以上面6点跟踪FrameworkServlet类中的代码,可以比较清晰的了解到整个容器上下文的建立过程,也就能够领会到FrameworkServlet类的设计目的,它是用来建立一个和Servlet关联的Spring容器上下文,并将其注册到ServletContext中的。跳脱开SpringMVC体系,我们也能通过继承FrameworkServlet类,得到与Spring容器整合的好处,FrameworkServlet和HttpServletBean一样,是一个可以独立使用的类。整个SpringMVC设计中,处处体现开闭原则,这里显然也是其中一点。
3.初始化SpringMVC默认实现类
初始化流程在FrameworkServlet类中流转,建立了上下文后,通过onRefresh(ApplicationContext context)方法的回调,进入到DispatcherServlet类中。
1 @Override
2 protected void onRefresh(ApplicationContext context) {
3 initStrategies(context);
4 }
DispatcherServlet类覆写了父类FrameworkServlet中的onRefresh(ApplicationContext context)方法,提供了SpringMVC各种编程元素的初始化。当然这些编程元素,都是作为容器上下文中一个个bean而存在的。具体的初始化策略,在initStrategies()方法中封装。
01 protected void initStrategies(ApplicationContext context) {
02 initMultipartResolver(context);
03 initLocaleResolver(context);
04 initThemeResolver(context);
05 initHandlerMappings(context);
06 initHandlerAdapters(context);
07 initHandlerExceptionResolvers(context);
08 initRequestToViewNameTranslator(context);
09 initViewResolvers(context);
10 initFlashMapManager(context);
11 }
我们以其中initHandlerMappings(context)方法为例,分析一下这些SpringMVC编程元素的初始化策略,其他的方法,都是以类似的策略初始化的。
01 private void initHandlerMappings(ApplicationContext context) {
02 this.handlerMappings = null;
03
04 if (this.detectAllHandlerMappings) {
05 // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
06 Map<String, HandlerMapping> matchingBeans =
07 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
08 if (!matchingBeans.isEmpty()) {
09 this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
10 // We keep HandlerMappings in sorted order.
11 OrderComparator.sort(this.handlerMappings);
12 }
13 }
14 else {
15 try {
16 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
17 this.handlerMappings = Collections.singletonList(hm);
18 }
19 catch (NoSuchBeanDefinitionException ex) {
20 // Ignore, we'll add a default HandlerMapping later.
21 }
22 }
23
24 // Ensure we have at least one HandlerMapping, by registering
25 // a default HandlerMapping if no other mappings are found.
26 if (this.handlerMappings == null) {
27 this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
28 if (logger.isDebugEnabled()) {
29 logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
30 }
31 }
32 }
detectAllHandlerMappings变量默认为true,所以在初始化HandlerMapping接口默认实现类的时候,会把上下文中所有HandlerMapping类型的Bean都注册在handlerMappings这个List变量中。如果你手工将其设置为false,那么将尝试获取名为handlerMapping的Bean,新建一个只有一个元素的List,将其赋给handlerMappings。如果经过上面的过程,handlerMappings变量仍为空,那么说明你没有在上下文中提供自己HandlerMapping类型的Bean定义。此时,SpringMVC将采用默认初始化策略来初始化handlerMappings。
点进去getDefaultStrategies看一下。
01 @SuppressWarnings("unchecked")
02 protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
03 String key = strategyInterface.getName();
04 String value = defaultStrategies.getProperty(key);
05 if (value != null) {
06 String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
07 List<T> strategies = new ArrayList<T>(classNames.length);
08 for (String className : classNames) {
09 try {
10 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
11 Object strategy = createDefaultStrategy(context, clazz);
12 strategies.add((T) strategy);
13 }
14 catch (ClassNotFoundException ex) {
15 throw new BeanInitializationException(
16 "Could not find DispatcherServlet's default strategy class [" + className +
17 "] for interface [" + key + "]", ex);
18 }
19 catch (LinkageError err) {
20 throw new BeanInitializationException(
21 "Error loading DispatcherServlet's default strategy class [" + className +
22 "] for interface [" + key + "]: problem with class file or dependent class", err);
23 }
24 }
25 return strategies;
26 }
27 else {
28 return new LinkedList<T>();
29 }
30 }
它是一个范型的方法,承担所有SpringMVC编程元素的默认初始化策略。方法的内容比较直白,就是以传递类的名称为键,从defaultStrategies这个Properties变量中获取实现类,然后反射初始化。
需要说明一下的是defaultStrategies变量的初始化,它是在DispatcherServlet的静态初始化代码块中加载的。
01 private static final Properties defaultStrategies;
02
03 static {
04 // Load default strategy implementations from properties file.
05 // This is currently strictly internal and not meant to be customized
06 // by application developers.
07 try {
08 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
09 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
10 }
11 catch (IOException ex) {
12 throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
13 }
14 }
1 private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
这个DispatcherServlet.properties里面,以键值对的方式,记录了SpringMVC默认实现类,它在spring-webmvc-3.1.3.RELEASE.jar这个jar包内,在org.springframework.web.servlet包里面。
01 # Default implementation classes for DispatcherServlet's strategy interfaces.
02 # Used as fallback when no matching beans are found in the DispatcherServlet context.
03 # Not meant to be customized by application developers.
04
05 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
06
07 org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
08
09 org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
10 org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
11
12 org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
13 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
14 org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
15
16 org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
17 org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
18 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
19
20 org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
21
22 org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
23
24 org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
至此,我们分析完了initHandlerMappings(context)方法的执行过程,其他的初始化过程与这个方法非常类似。所有初始化方法执行完后,SpringMVC正式完成初始化,静静等待Web请求的到来。
4.总结
回顾整个SpringMVC的初始化流程,我们看到,通过HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类层次,SpringMVC的设计者将三种不同的职责分别抽象,运用模版方法设计模式分别固定在三个类层次中。其中HttpServletBean完成的是<init-param>配置元素的依赖注入,FrameworkServlet完成的是容器上下文的建立,DispatcherServlet完成的是SpringMVC具体编程元素的初始化策略。
在我们第一次学Servlet编程,学java web的时候,还没有那么多框架。我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转到我们定义好的jsp页面。Servlet类编写完之后在web.xml里注册这个Servlet类。
除此之外,没有其他了。我们启动web服务器,在浏览器中输入地址,就可以看到浏览器上输出我们写好的页面。为了更好的理解上面这个过程,你需要学习关于Servlet生命周期的三个阶段,就是所谓的“init-service-destroy”。
以上的知识,我觉得对于你理解SpringMVC的设计思想,已经足够了。SpringMVC当然可以称得上是一个复杂的框架,但是同时它又遵循Servlet世界里最简单的法则,那就是“init-service-destroy”。我们要分析SpringMVC的初始化流程,其实就是分析DispatcherServlet类的init()方法,让我们带着这种单纯的观点,打开DispatcherServlet的源码一窥究竟吧。
1.<init-param>配置元素读取
用Eclipse IDE打开DispatcherServlet类的源码,ctrl+T看一下。
DispatcherServlet类的初始化入口方法init()定义在HttpServletBean这个父类中,HttpServletBean类作为一个直接继承于HttpServlet类的类,覆写了HttpServlet类的init()方法,实现了自己的初始化行为。
01 @Override
02 public final void init() throws ServletException {
03 if (logger.isDebugEnabled()) {
04 logger.debug("Initializing servlet '" + getServletName() + "'");
05 }
06
07 // Set bean properties from init parameters.
08 try {
09 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
10 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
11 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
12 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
13 initBeanWrapper(bw);
14 bw.setPropertyValues(pvs, true);
15 }
16 catch (BeansException ex) {
17 logger.error("Failed to set bean<span></span> properties on servlet '" + getServletName() + "'", ex);
18 throw ex;
19 }
20
21 // Let subclasses do whatever initialization they like.
22 initServletBean();
23
24 if (logger.isDebugEnabled()) {
25 logger.debug("Servlet '" + getServletName() + "' configured successfully");
26 }
27 }
这里的initServletBean()方法在HttpServletBean类中是一个没有任何实现的空方法,它的目的就是留待子类实现自己的初始化逻辑,也就是我们常说的模板方法设计模式。SpringMVC在此生动的运用了这个模式,init()方法就是模版方法模式中的模板方法,SpringMVC真正的初始化过程,由子类FrameworkServlet中覆写的initServletBean()方法触发。
再看一下init()方法内被try,catch块包裹的代码,里面涉及到BeanWrapper,PropertyValues,ResourceEditor这些Spring内部非常底层的类。要深究具体代码实现上面的细节,需要对Spring框架源码具有相当深入的了解。我们这里先避繁就简,从代码效果和设计思想上面来分析这段try,catch块内的代码所做的事情:
注册一个字符串到资源文件的编辑器,让Servlet下面的<init-param>配置元素可以使用形如“classpath:”这种方式指定SpringMVC框架bean配置文件的来源。
将web.xml中在DispatcherServlet这个Servlet下面的<init-param>配置元素利用JavaBean的方式(即通过setter方法)读取到DispatcherServlet中来。
这两点,我想通过下面一个例子来说明一下。
我在web.xml中注册的DispatcherServlet配置如下:
01 <!-- springMVC配置开始 -->
02 <servlet>
03 <servlet-name>appServlet</servlet-name>
04 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
05 <init-param>
06 <param-name>contextConfigLocation</param-name>
07 <param-value>classpath:spring/spring-servlet.xml</param-value>
08 </init-param>
09 <load-on-startup>1</load-on-startup>
10 </servlet>
11 <servlet-mapping>
12 <servlet-name>appServlet</servlet-name>
13 <url-pattern>/</url-pattern>
14 </servlet-mapping>
15 <!-- springMVC配置结束 -->
可以看到,我注册了一个名为contextConfigLocation的<init-param>元素,其值为“classpath:spring/spring-servlet.xml”,这也是大家常常用来指定SpringMVC配置文件路径的方法。上面那段try,catch块包裹的代码发挥的作用,一个是将“classpath:spring/spring-servlet.xml”这段字符串转换成classpath路径下的一个资源文件,供框架初始化读取配置元素。在我的工程中是在spring文件夹下面的配置文件spring-servlet.xml。
另外一个作用,就是将contextConfigLocation的值读取出来,然后通过setContextConfigLocation()方法设置到DispatcherServlet中,这个setContextConfigLocation()方法是在FrameworkServlet类中定义的,也就是上面继承类图中DispatcherServlet的直接父类。
我们在setContextConfigLocation()方法上面打上一个断点,启动web工程,可以看到下面的调试结果。
HttpServletBean类的作者是大名鼎鼎的Spring之父Rod Johnson。作为POJO编程哲学的大师,他在HttpServletBean这个类的设计中,运用了依赖注入思想完成了<init-param>配置元素的读取。他抽离出HttpServletBean这个类的目的也在于此,就是“以依赖注入的方式来读取Servlet类的<init-param>配置信息”,而且这里很明显是一种setter注入。
明白了HttpServletBean类的设计思想,我们也就知道可以如何从中获益。具体来说,我们继承HttpServletBean类(就像DispatcherServlet做的那样),在类中定义一个属性,为这个属性加上setter方法后,我们就可以在<init-param>元素中为其定义值。在类被初始化后,值就会被注入进来,我们可以直接使用它,避免了样板式的getInitParameter()方法的使用,而且还免费享有Spring中资源编辑器的功能,可以在web.xml中,通过“classpath:”直接指定类路径下的资源文件。
注意,虽然SpringMVC本身为了后面初始化上下文的方便,使用了字符串来声明和设置contextConfigLocation参数,但是将其声明为Resource类型,同样能够成功获取。鼓励读者们自己继承HttpServletBean写一个测试用的Servlet类,并设置一个参数来调试一下,这样能够帮助你更好的理解获取配置参数的过程。
2.容器上下文的建立
上一篇文章中提到过,SpringMVC使用了Spring容器来容纳自己的配置元素,拥有自己的bean容器上下文。在SpringMVC初始化的过程中,非常关键的一步就是要建立起这个容器上下文,而这个建立上下文的过程,发生在FrameworkServlet类中,由上面init()方法中的initServletBean()方法触发。
01 @Override
02 protected final void initServletBean() throws ServletException {
03 getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
04 if (this.logger.isInfoEnabled()) {
05 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
06 }
07 long startTime = System.currentTimeMillis();
08
09 try {
10 this.webApplicationContext = initWebApplicationContext();
11 initFrameworkServlet();
12 }
13 catch (ServletException ex) {
14 this.logger.error("Context initialization failed", ex);
15 throw ex;
16 }
17 catch (RuntimeException ex) {
18 this.logger.error("Context initialization failed", ex);
19 throw ex;
20 }
21
22 if (this.logger.isInfoEnabled()) {
23 long elapsedTime = System.currentTimeMillis() - startTime;
24 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
25 elapsedTime + " ms");
26 }
27 }
initFrameworkServlet()方法是一个没有任何实现的空方法,除去一些样板式的代码,那么这个initServletBean()方法所做的事情已经非常明白:
1 this.webApplicationContext = initWebApplicationContext();
这一句简单直白的代码,道破了FrameworkServlet这个类,在SpringMVC类体系中的设计目的,它是 用来抽离出建立 WebApplicationContext 上下文这个过程的。
initWebApplicationContext()方法,封装了建立Spring容器上下文的整个过程,方法内的逻辑如下:
获取由ContextLoaderListener初始化并注册在ServletContext中的根上下文,记为rootContext
如果webApplicationContext已经不为空,表示这个Servlet类是通过编程式注册到容器中的(Servlet 3.0+中的ServletContext.addServlet() ),上下文也由编程式传入。若这个传入的上下文还没被初始化,将rootContext上下文设置为它的父上下文,然后将其初始化,否则直接使用。
通过wac变量的引用是否为null,判断第2步中是否已经完成上下文的设置(即上下文是否已经用编程式方式传入),如果wac==null成立,说明该Servlet不是由编程式注册到容器中的。此时以contextAttribute属性的值为键,在ServletContext中查找上下文,查找得到,说明上下文已经以别的方式初始化并注册在contextAttribute下,直接使用。
检查wac变量的引用是否为null,如果wac==null成立,说明2、3两步中的上下文初始化策略都没成功,此时调用createWebApplicationContext(rootContext),建立一个全新的以rootContext为父上下文的上下文,作为SpringMVC配置元素的容器上下文。大多数情况下我们所使用的上下文,就是这个新建的上下文。
以上三种初始化上下文的策略,都会回调onRefresh(ApplicationContext context)方法(回调的方式根据不同策略有不同),onRefresh方法在DispatcherServlet类中被覆写,以上面得到的上下文为依托,完成SpringMVC中默认实现类的初始化。
最后,将这个上下文发布到ServletContext中,也就是将上下文以一个和Servlet类在web.xml中注册名字有关的值为键,设置为ServletContext的一个属性。你可以通过改变publishContext的值来决定是否发布到ServletContext中,默认为true。
以上面6点跟踪FrameworkServlet类中的代码,可以比较清晰的了解到整个容器上下文的建立过程,也就能够领会到FrameworkServlet类的设计目的,它是用来建立一个和Servlet关联的Spring容器上下文,并将其注册到ServletContext中的。跳脱开SpringMVC体系,我们也能通过继承FrameworkServlet类,得到与Spring容器整合的好处,FrameworkServlet和HttpServletBean一样,是一个可以独立使用的类。整个SpringMVC设计中,处处体现开闭原则,这里显然也是其中一点。
3.初始化SpringMVC默认实现类
初始化流程在FrameworkServlet类中流转,建立了上下文后,通过onRefresh(ApplicationContext context)方法的回调,进入到DispatcherServlet类中。
1 @Override
2 protected void onRefresh(ApplicationContext context) {
3 initStrategies(context);
4 }
DispatcherServlet类覆写了父类FrameworkServlet中的onRefresh(ApplicationContext context)方法,提供了SpringMVC各种编程元素的初始化。当然这些编程元素,都是作为容器上下文中一个个bean而存在的。具体的初始化策略,在initStrategies()方法中封装。
01 protected void initStrategies(ApplicationContext context) {
02 initMultipartResolver(context);
03 initLocaleResolver(context);
04 initThemeResolver(context);
05 initHandlerMappings(context);
06 initHandlerAdapters(context);
07 initHandlerExceptionResolvers(context);
08 initRequestToViewNameTranslator(context);
09 initViewResolvers(context);
10 initFlashMapManager(context);
11 }
我们以其中initHandlerMappings(context)方法为例,分析一下这些SpringMVC编程元素的初始化策略,其他的方法,都是以类似的策略初始化的。
01 private void initHandlerMappings(ApplicationContext context) {
02 this.handlerMappings = null;
03
04 if (this.detectAllHandlerMappings) {
05 // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
06 Map<String, HandlerMapping> matchingBeans =
07 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
08 if (!matchingBeans.isEmpty()) {
09 this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
10 // We keep HandlerMappings in sorted order.
11 OrderComparator.sort(this.handlerMappings);
12 }
13 }
14 else {
15 try {
16 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
17 this.handlerMappings = Collections.singletonList(hm);
18 }
19 catch (NoSuchBeanDefinitionException ex) {
20 // Ignore, we'll add a default HandlerMapping later.
21 }
22 }
23
24 // Ensure we have at least one HandlerMapping, by registering
25 // a default HandlerMapping if no other mappings are found.
26 if (this.handlerMappings == null) {
27 this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
28 if (logger.isDebugEnabled()) {
29 logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
30 }
31 }
32 }
detectAllHandlerMappings变量默认为true,所以在初始化HandlerMapping接口默认实现类的时候,会把上下文中所有HandlerMapping类型的Bean都注册在handlerMappings这个List变量中。如果你手工将其设置为false,那么将尝试获取名为handlerMapping的Bean,新建一个只有一个元素的List,将其赋给handlerMappings。如果经过上面的过程,handlerMappings变量仍为空,那么说明你没有在上下文中提供自己HandlerMapping类型的Bean定义。此时,SpringMVC将采用默认初始化策略来初始化handlerMappings。
点进去getDefaultStrategies看一下。
01 @SuppressWarnings("unchecked")
02 protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
03 String key = strategyInterface.getName();
04 String value = defaultStrategies.getProperty(key);
05 if (value != null) {
06 String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
07 List<T> strategies = new ArrayList<T>(classNames.length);
08 for (String className : classNames) {
09 try {
10 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
11 Object strategy = createDefaultStrategy(context, clazz);
12 strategies.add((T) strategy);
13 }
14 catch (ClassNotFoundException ex) {
15 throw new BeanInitializationException(
16 "Could not find DispatcherServlet's default strategy class [" + className +
17 "] for interface [" + key + "]", ex);
18 }
19 catch (LinkageError err) {
20 throw new BeanInitializationException(
21 "Error loading DispatcherServlet's default strategy class [" + className +
22 "] for interface [" + key + "]: problem with class file or dependent class", err);
23 }
24 }
25 return strategies;
26 }
27 else {
28 return new LinkedList<T>();
29 }
30 }
它是一个范型的方法,承担所有SpringMVC编程元素的默认初始化策略。方法的内容比较直白,就是以传递类的名称为键,从defaultStrategies这个Properties变量中获取实现类,然后反射初始化。
需要说明一下的是defaultStrategies变量的初始化,它是在DispatcherServlet的静态初始化代码块中加载的。
01 private static final Properties defaultStrategies;
02
03 static {
04 // Load default strategy implementations from properties file.
05 // This is currently strictly internal and not meant to be customized
06 // by application developers.
07 try {
08 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
09 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
10 }
11 catch (IOException ex) {
12 throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
13 }
14 }
1 private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
这个DispatcherServlet.properties里面,以键值对的方式,记录了SpringMVC默认实现类,它在spring-webmvc-3.1.3.RELEASE.jar这个jar包内,在org.springframework.web.servlet包里面。
01 # Default implementation classes for DispatcherServlet's strategy interfaces.
02 # Used as fallback when no matching beans are found in the DispatcherServlet context.
03 # Not meant to be customized by application developers.
04
05 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
06
07 org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
08
09 org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
10 org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
11
12 org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
13 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
14 org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
15
16 org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
17 org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
18 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
19
20 org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
21
22 org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
23
24 org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
至此,我们分析完了initHandlerMappings(context)方法的执行过程,其他的初始化过程与这个方法非常类似。所有初始化方法执行完后,SpringMVC正式完成初始化,静静等待Web请求的到来。
4.总结
回顾整个SpringMVC的初始化流程,我们看到,通过HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类层次,SpringMVC的设计者将三种不同的职责分别抽象,运用模版方法设计模式分别固定在三个类层次中。其中HttpServletBean完成的是<init-param>配置元素的依赖注入,FrameworkServlet完成的是容器上下文的建立,DispatcherServlet完成的是SpringMVC具体编程元素的初始化策略。
相关推荐
在本文中,我们将深入探讨`DispatcherServlet`的初始化流程,这是SpringMVC的核心组件。`DispatcherServlet`扮演着中央调度者的角色,负责接收请求、解析请求信息,并调用合适的控制器进行业务逻辑处理。 首先,让...
【SpringMVC源码剖析(二)- DispatcherServlet的前世今生1】 在Spring MVC框架中,DispatcherServlet扮演着至关重要的角色,它是整个框架的核心。本文将深入探讨DispatcherServlet的两个核心设计原则及其在Spring ...
2. 设置当前请求的`LocaleResolver`和`LocaleContext`,以处理国际化问题。 3. 替换当前线程的`RequestAttributes`,创建一个新的`ServletRequestAttributes`对象,它包含请求相关的属性。 接下来,...
DispatcherServlet 首先是一个 Servlet,Servlet 有自己的生命周期的方法(init、destory 等),那么我们在看 DispatcherServlet 初始化时首先需要看源码中 DispatcherServlet 的类结构设计。 DispatcherServlet 的...
《SpringMVC-Activiti5.16-Shiro-EasyUI项目整合详解》 在现代企业级应用开发中,高效、安全、易用是至关重要的考量因素。SpringMVC、Activiti、Shiro和EasyUI这四个技术组件的整合,正是为了实现这样的目标。这篇...
这个压缩包文件“SpringMVC-Activiti5.16-Shiro-EasyUI.zip”显然包含了使用这些技术构建的一个完整或部分的应用系统。让我们详细探讨一下每个组件及其在实际开发中的应用。 **1. SpringMVC** SpringMVC是Spring...
DispatcherServlet是SpringMVC的核心分发器,它实现了请求分发,是处理请求的入口,本篇将深入源码分析它的初始化过程。 首先,从DispatcherServlet的名称上可以看出它是一个Servlet,通过一张图来看一下它的实现...
《SpringMVC-Mybatis-Shiro-Redis:构建安全高效的Web应用》 在现代Web开发中,构建一个高效且安全的后端系统是至关重要的。本文将深入探讨一个基于SpringMVC、Mybatis、Shiro和Redis的Web应用架构,这四个组件共同...
SpringMVC的工作流程包括:用户发送请求到DispatcherServlet,DispatcherServlet根据请求信息分发到相应的HandlerMapping,然后HandlerMapping找到对应的Controller处理请求,Controller处理完业务逻辑后,返回...
- SessionFactory:创建 Hibernate 的会话工厂,负责配置和初始化。 - Session:与数据库交互的主要接口,执行 CRUD(创建、读取、更新、删除)操作。 - Transaction:事务管理,保证数据一致性。 - Hibernate ...
Vans项目整合了SpringMVC,意味着它利用了SpringMVC的功能,如模型-视图-控制器(MVC)架构模式,实现了请求处理、数据绑定、验证、国际化等特性。 1. **SpringMVC**: - MVC架构:SpringMVC通过DispatcherServlet...
在SpringMVC中,核心组件包括DispatcherServlet、Controller、Model、View和ViewResolver等。DispatcherServlet作为入口点,负责接收HTTP请求,并根据请求信息分发到相应的处理器。Controller是处理业务逻辑的组件,...
它是一个模型-视图-控制器(MVC)架构的实现,提供了强大的数据绑定、模型验证、本地化和国际化等功能,极大地简化了Java Web开发。在“SpringMVC精品资源--基于springMVC实现的解决方案系统.zip”这个压缩包中,...
SpringMVC源码剖析(五)- 消息转换器HttpMessageConverter1 在SpringMVC中,有一个非常重要的机制,即消息转换器HttpMessageConverter,它负责将HTTP请求和响应报文转换为Java对象和反之。为了更好地理解这个机制...
SpringMVC视频教程--李守红,视频,随堂练习,课件,项目源码,需要的来拿
SpringMVC提供了很多高级特性,如RESTful支持、上传下载、数据校验、本地化、主题装饰等。通过学习和实践,我们可以充分利用这些功能来提升应用的质量和用户体验。 7. **实战项目应用** 在实际项目中,SpringMVC...
【描述】:这个资源包聚焦于Java开发中的Web应用程序集成,通过使用javaconfig而非传统的XML配置来整合SpringMVC、Mybatis和SpringSecurity三大框架。这种方式使得配置更加简洁且易于维护,同时体现了Spring框架的...
【标题】"SpringMVC精品资源--SSM(spring+springmvc+mybatis)框架 Demo.zip" 提供的是一份关于使用Spring、SpringMVC和MyBatis这三大开源框架集成开发的示例项目。这个压缩包可能包含了完整的源代码、配置文件、...
学习如何配置DispatcherServlet、Controller、ModelAndView、视图解析器等是SpringMVC的重点。 4. **SpringBoot**:SpringBoot简化了Spring应用的初始搭建和运行过程,通过预配置和自动配置特性,使得开发者可以...
学习SpringMVC,你需要熟悉DispatcherServlet、Controller、ModelAndView、ViewResolver等关键组件,以及使用注解驱动的开发方式。 最后,Spring框架是Java企业级应用的核心,它提供了一种依赖注入(DI)和面向切面...