SpringMVC学习记录2

废话

最近在看SpringMVC...里面东西好多...反正东看一点西看一点吧...

分享一下最近的一些心得..是关于DispatcherServlet

 

DispatcherServlet与ContextLoaderListener

dispattcherServlet这个类大家肯定不陌生的...因为使用SpringMVC一定会在web.xml里配置它.

 1     <servlet>
 2         <servlet-name>mvc-dispatcher</servlet-name>
 3         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 4         <init-param>
 5             <!-- 默认/WEB-INF/[servlet名字]-servlet.xml加载上下文, 如果配置了contextConfigLocation参数, 
 6                 将使用classpath:/mvc-dispatcher-servlet.xml加载上下文 -->
 7             <param-name>contextConfigLocation</param-name>
 8             <param-value>classpath:/spring/mvc-dispatcher-servlet.xml</param-value>
 9         </init-param>
10         <load-on-startup>1</load-on-startup>
11     </servlet>

从配置中就可以看出这是1个servlet.

而使用SpringMVC需要使用Spring,Spring在web环境中时通过一个listener来配置的

 1     <listener>
 2         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 3     </listener>
 4 
 5     <!-- 配置spring的bean用 -->
 6     <context-param>
 7         <param-name>contextConfigLocation</param-name>
 8         <param-value>
 9             classpath*:/spring/application-context.xml
10         </param-value>
11     </context-param>
1 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {.....}

ContextLoaderListener实现了ServletContextListener.

 1 public interface ServletContextListener extends EventListener {
 2     /**
 3      ** Notification that the web application initialization
 4      ** process is starting.
 5      ** All ServletContextListeners are notified of context
 6      ** initialization before any filter or servlet in the web
 7      ** application is initialized.
 8      */
 9 
10     public void contextInitialized ( ServletContextEvent sce );
11 
12     /**
13      ** Notification that the servlet context is about to be shut down.
14      ** All servlets and filters have been destroy()ed before any
15      ** ServletContextListeners are notified of context
16      ** destruction.
17      */
18     public void contextDestroyed ( ServletContextEvent sce );
19 }

ServletContextListener的contextInitialized是在任何filter或者servlet之前调用的.

贴了这么多代码片段只为了说明一个问题:Spring的applicationContext先于SpringMVC的applicationContext加载.

 

HttpServletBean

DispatcherServlet继承自FrameworkServlet继承自HttpServletBean继承自HttpServlet

所以我觉得按时间顺序的话应该从HttpServletBean的init方法看起.

 1 @Override
 2     public final void init() throws ServletException {
 3         if (logger.isDebugEnabled()) {
 4             logger.debug("Initializing servlet '" + getServletName() + "'");
 5         }
 6 
 7         // Set bean properties from init parameters.
 8         try {
 9             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, getEnvironment()));
13             initBeanWrapper(bw);
14             bw.setPropertyValues(pvs, true);
15         }
16         catch (BeansException ex) {
17             logger.error("Failed to set bean 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     }

9-14行主要就是把当前类的对象(dispatcherServlet)包装成BeanWrapperImpl对象,然后设置属性.

PropertyValues可以来自ServletConfig,然后PropertyValues又会被设置到dispatcher的属性里去.

从中我们可以得到的信息是什么呢?

我们可以得到的信息就是:

1         <init-param>
2             <!-- 默认/WEB-INF/[servlet名字]-servlet.xml加载上下文, 如果配置了contextConfigLocation参数, 
3                 将使用classpath:/mvc-dispatcher-servlet.xml加载上下文 -->
4             <param-name>contextConfigLocation</param-name>
5             <param-value>classpath:/spring/mvc-dispatcher-servlet.xml</param-value>
6         </init-param>

dispatcherServlet里面的init-param我们可以配置dispatcherServlet里的所有属性名称和值

    /** ServletContext attribute to find the WebApplicationContext in */
    private String contextAttribute;

    /** WebApplicationContext implementation class to create */
    private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

    /** WebApplicationContext id to assign */
    private String contextId;

    /** Namespace for this servlet */
    private String namespace;

    /** Explicit context config location */
    private String contextConfigLocation;

...............

做完这些以后就转到22行,让FrameworkServlet来做剩下的事情

 

FrameworkServlet

 1 /**
 2      * Overridden method of {@link HttpServletBean}, invoked after any bean properties
 3      * have been set. Creates this servlet's WebApplicationContext.
 4      */
 5     @Override
 6     protected final void initServletBean() throws ServletException {
 7         getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
 8         if (this.logger.isInfoEnabled()) {
 9             this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
10         }
11         long startTime = System.currentTimeMillis();
12 
13         try {
14             this.webApplicationContext = initWebApplicationContext();
15             initFrameworkServlet();
16         }
17         catch (ServletException ex) {
18             this.logger.error("Context initialization failed", ex);
19             throw ex;
20         }
21         catch (RuntimeException ex) {
22             this.logger.error("Context initialization failed", ex);
23             throw ex;
24         }
25 
26         if (this.logger.isInfoEnabled()) {
27             long elapsedTime = System.currentTimeMillis() - startTime;
28             this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
29                     elapsedTime + " ms");
30         }
31     }

FrameworkServlet我觉得就做了一件事情.就是初始化了applicationContext(第14行).只是这个初始化非常复杂.....

然后留了1个扩展点:

第15行initFrameworkServlet()现在的实现是空的.它会在applicationContext初始化完成之后被调用.

所以我们可以通过继承dispatcherServlet来扩展.但是话又说回来.Spring的扩展点真的很多.可以通过继承很多接口来参与Spring bean的生命周期.也并不一定要通过这个方法来做.

 

然后再来看看initWebApplicationContext方法...这个方法里面嵌套了N层..

 1 /**
 2      * Initialize and publish the WebApplicationContext for this servlet.
 3      * <p>Delegates to {@link #createWebApplicationContext} for actual creation
 4      * of the context. Can be overridden in subclasses.
 5      * @return the WebApplicationContext instance
 6      * @see #FrameworkServlet(WebApplicationContext)
 7      * @see #setContextClass
 8      * @see #setContextConfigLocation
 9      */
10     protected WebApplicationContext initWebApplicationContext() {
11         WebApplicationContext rootContext =
12                 WebApplicationContextUtils.getWebApplicationContext(getServletContext());
13         WebApplicationContext wac = null;
14 
15         if (this.webApplicationContext != null) {
16             // A context instance was injected at construction time -> use it
17             wac = this.webApplicationContext;
18             if (wac instanceof ConfigurableWebApplicationContext) {
19                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
20                 if (!cwac.isActive()) {
21                     // The context has not yet been refreshed -> provide services such as
22                     // setting the parent context, setting the application context id, etc
23                     if (cwac.getParent() == null) {
24                         // The context instance was injected without an explicit parent -> set
25                         // the root application context (if any; may be null) as the parent
26                         cwac.setParent(rootContext);
27                     }
28                     configureAndRefreshWebApplicationContext(cwac);
29                 }
30             }
31         }
32         if (wac == null) {
33             // No context instance was injected at construction time -> see if one
34             // has been registered in the servlet context. If one exists, it is assumed
35             // that the parent context (if any) has already been set and that the
36             // user has performed any initialization such as setting the context id
37             wac = findWebApplicationContext();
38         }
39         if (wac == null) {
40             // No context instance is defined for this servlet -> create a local one
41             wac = createWebApplicationContext(rootContext);
42         }
43 
44         if (!this.refreshEventReceived) {
45             // Either the context is not a ConfigurableApplicationContext with refresh
46             // support or the context injected at construction time had already been
47             // refreshed -> trigger initial onRefresh manually here.
48             onRefresh(wac);
49         }
50 
51         if (this.publishContext) {
52             // Publish the context as a servlet context attribute.
53             String attrName = getServletContextAttributeName();
54             getServletContext().setAttribute(attrName, wac);
55             if (this.logger.isDebugEnabled()) {
56                 this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
57                         "' as ServletContext attribute with name [" + attrName + "]");
58             }
59         }
60 
61         return wac;
62     }

里面有很多种情况,很多if情况我都没遇到过..我只知道在我最一般的配置下.这个时候Spring的rootContext(11行)是已经完成了初始化.而SpringMVC的wac applicationContext还没有初始化.所以这个时候wac = createWebApplicationContext(rootContext);

 

 1 protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
 2         Class<?> contextClass = getContextClass();
 3         if (this.logger.isDebugEnabled()) {
 4             this.logger.debug("Servlet with name '" + getServletName() +
 5                     "' will try to create custom WebApplicationContext context of class '" +
 6                     contextClass.getName() + "'" + ", using parent context [" + parent + "]");
 7         }
 8         if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
 9             throw new ApplicationContextException(
10                     "Fatal initialization error in servlet with name '" + getServletName() +
11                     "': custom WebApplicationContext class [" + contextClass.getName() +
12                     "] is not of type ConfigurableWebApplicationContext");
13         }
14         ConfigurableWebApplicationContext wac =
15                 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
16 
17         wac.setEnvironment(getEnvironment());
18         wac.setParent(parent);
19         wac.setConfigLocation(getContextConfigLocation());
20 
21         configureAndRefreshWebApplicationContext(wac);
22 
23         return wac;
24     }

如果不通过init-param修改dispatcherServlet的属性的话.contextClass默认是XmlWebApplicationContext.class

所以通过BeanUtils通过反射来创建XmlWebApplicationContext(applicationContext).

然后各种setter方法以后调用configureAndRefreshWebApplicationContext方法

 

 1 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
 2         if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
 3             // The application context id is still set to its original default value
 4             // -> assign a more useful id based on available information
 5             if (this.contextId != null) {
 6                 wac.setId(this.contextId);
 7             }
 8             else {
 9                 // Generate default id...
10                 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
11                         ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
12             }
13         }
14 
15         wac.setServletContext(getServletContext());
16         wac.setServletConfig(getServletConfig());
17         wac.setNamespace(getNamespace());
18         wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
19 
20         // The wac environment's #initPropertySources will be called in any case when the context
21         // is refreshed; do it eagerly here to ensure servlet property sources are in place for
22         // use in any post-processing or initialization that occurs below prior to #refresh
23         ConfigurableEnvironment env = wac.getEnvironment();
24         if (env instanceof ConfigurableWebEnvironment) {
25             ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
26         }
27 
28         postProcessWebApplicationContext(wac);
29         applyInitializers(wac);
30         wac.refresh();
31     }

然后又是各种setter方法.

18行这里添加了一个eventListener,当xmlWebapplicationContext refresh(创建各种bean?)以后会通知这个listener.

1     private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
2 
3         @Override
4         public void onApplicationEvent(ContextRefreshedEvent event) {
5             FrameworkServlet.this.onApplicationEvent(event);
6         }
7     }

而这个listener会delegate另外1个linstener ContextRefreshListener来处理.

而ContextRefreshListener是FrameworkServlet的内部类.因为是内部类.他可以调用FrameworkServlet的方法(onApplicationEvent方法).

1     public void onApplicationEvent(ContextRefreshedEvent event) {
2         this.refreshEventReceived = true;
3         onRefresh(event.getApplicationContext());
4     }

onRefresh又是一个扩展接口.这个时间点是xmlWebapplicationContext finishRefresh的时候.

这个方法被dispatcherServlet用到了.后面会说.

 

25行 ConfigurableWebEnvironment的(实际上是StandardServletEnvironment)的initPropertySources方法具体做的事情是:

Replace Servlet-based stub property sources with actual instances populated with the given servletContext and servletConfig objects.

This method is idempotent with respect to the fact it may be called any number of times but will perform replacement of stub property sources with their corresponding actual property sources once and only once.

这我没太看懂...根据我的理解就是有些properties一开始在servletContext或者servletConfig还没有的时候会先用占位符properties去占位,然后现在通过这个方法替换成真正的包含servletContext或者servletConfig的properties.但是SpringMVC初始化的时候这些都是有了的...到底什么时候会存在这种情况我就不知道了..说不定Spring初始化xmlWebApplicationContext的时候会遇到吧.毕竟那个时候还是没有servletConfig的....

 

然后28行又是一个扩展点.

postProcessWebApplicationContext(wac);我们可以像先前那样,通过继承dispatcherServlet来实现.

这个时间点就是applicationContext还没有refresh(生成各种bean?)的时候.

 

然后又是1个扩展点:

applyInitializers(wac);

 1     protected void applyInitializers(ConfigurableApplicationContext wac) {
 2         String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
 3         if (globalClassNames != null) {
 4             for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
 5                 this.contextInitializers.add(loadInitializer(className, wac));
 6             }
 7         }
 8 
 9         if (this.contextInitializerClasses != null) {
10             for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
11                 this.contextInitializers.add(loadInitializer(className, wac));
12             }
13         }
14 
15         AnnotationAwareOrderComparator.sort(this.contextInitializers);
16         for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
17             initializer.initialize(wac);
18         }
19     }

可以通过在web.xml里配置globalInitializerClasses和contextInitializerClasses来初始化applicationContext

比如公司的做法:

1     <context-param>
2         <param-name>contextInitializerClasses</param-name>
3         <param-value>XXXX.spring.SpringApplicationContextInitializer</param-value>
4     </context-param>
 1 public class SpringApplicationContextInitializer implements
 2         ApplicationContextInitializer<ConfigurableApplicationContext> {
 3 
 4     /**
 5      * The Constant LOGGER.
 6      */
 7     private static final Logger LOGGER = LoggerFactory.getLogger(SpringApplicationContextInitializer.class);
 8 
 9     /* (non-Javadoc)
10      * @see org.springframework.context.ApplicationContextInitializer#initialize(org.springframework.context.ConfigurableApplicationContext)
11      */
12     @Override
13     public void initialize(ConfigurableApplicationContext applicationContext) {
14         try {
15             ResourcePropertySource rps = new ResourcePropertySource(
16                     applicationContext.getResource("classpath:application-cfg.properties"));
17             applicationContext.getEnvironment().getPropertySources().addFirst(rps);
18         } catch (IOException e) {
19             LOGGER.error("Fail to add properties in application-cfg.properties to environment", e);
20         }
21     }
22 }

通过编码方式强行加载application-cfg.properties

 

最后就是调用applicationContext的refresh方法了:

wac.refresh(); 容器的refresh里面也有很多很多东西.但是和SpringMVC好像没啥关系..和Spring有关.我就没细看了..

 

DispatcherServlet

前面提到在frameworkServlet初始化applicationContext finish的时候会触发监听器的事件,然后调用DispatcherServlet的onRefresh方法.

 1 /**
 2      * This implementation calls {@link #initStrategies}.
 3      */
 4     @Override
 5     protected void onRefresh(ApplicationContext context) {
 6         initStrategies(context);
 7     }
 8 
 9     /**
10      * Initialize the strategy objects that this servlet uses.
11      * <p>May be overridden in subclasses in order to initialize further strategy objects.
12      */
13     protected void initStrategies(ApplicationContext context) {
14         initMultipartResolver(context);
15         initLocaleResolver(context);
16         initThemeResolver(context);
17         initHandlerMappings(context);
18         initHandlerAdapters(context);
19         initHandlerExceptionResolvers(context);
20         initRequestToViewNameTranslator(context);
21         initViewResolvers(context);
22         initFlashMapManager(context);
23     }

 

这个方法要做的就是各种init....各种init方法里面都是差不多的.我们来看一个就可以了.

 1 /**
 2      * Initialize the HandlerMappings used by this class.
 3      * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 4      * we default to BeanNameUrlHandlerMapping.
 5      */
 6     private void initHandlerMappings(ApplicationContext context) {
 7         this.handlerMappings = null;
 8 
 9         if (this.detectAllHandlerMappings) {
10             // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
11             Map<String, HandlerMapping> matchingBeans =
12                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
13             if (!matchingBeans.isEmpty()) {
14                 this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
15                 // We keep HandlerMappings in sorted order.
16                 OrderComparator.sort(this.handlerMappings);
17             }
18         }
19         else {
20             try {
21                 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
22                 this.handlerMappings = Collections.singletonList(hm);
23             }
24             catch (NoSuchBeanDefinitionException ex) {
25                 // Ignore, we'll add a default HandlerMapping later.
26             }
27         }
28 
29         // Ensure we have at least one HandlerMapping, by registering
30         // a default HandlerMapping if no other mappings are found.
31         if (this.handlerMappings == null) {
32             this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
33             if (logger.isDebugEnabled()) {
34                 logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
35             }
36         }
37     }

detectAllHandlerMappings默认是true.所以会去applicationContext里找HandlerMapping.class类型的bean.因为配置过<mvc:annotation-driven>所以可以加载到3个bean..

把dispatcherServlet里的成员变量指向这个HandlerMapping的list.因为后面分发请求的时候要用到.弄成成员域就不需要每次都去applicationContext里找了.

3个bean到底使用哪个呢? 先来排个序... 这个3个bean都实现了Ordered接口...排序结果不用多说大家肯定知道.RequestMappingHanddlerMapping肯定是最优先的...

 

后续

然后...然后以要做的后续事情就不多了...回到FrameworkServlet.把applicationContext放到ServletContext.与Spring初始化applicationContext的一样..

然后貌似就真的没有然后了....

 

思考与收获

这次学习学到了什么:

1. DispatcherServlet里有很多扩展点:

DispatcherServlet提供的扩展方法至少有(可能还有些我没发现):

(1) initFrameworkServlet(),在xmlWebApplicationContext初始化完成后(refresh完成后)调用

(2) postProcessWebApplicationContext(wac);在xmlWebApplicationContext各种set以后但是xmlWebApplicationContext还没有refresh(生成bean?)的时候调用

(3) onRefresh(ApplicationContext context);在xmlWebApplicationContext内部被调用,时间点是finish refresh的时候(bean初始化完成?).

DispatcherServlet指明的通过接口实现的扩展点至少有(可能还有些我没发现):

(1) 配置globalInitializerClasses或contextInitializerClasses,自己实现 ApplicationContextInitializer<C extends ConfigurableApplicationContext>接口,时间点是xmlWebApplicationContext refresh方法调用之前,postProcessWebApplicationContext(wac);之后.

 

2.结合上一篇文章,我知道了DispatcherServlet其实也并不是很特殊的,SpringMVC的@Controller自动委派请求等功能还是通过配置Bean(比如mvc:annotation-driven标签),让Bean参与Spring Bean的声明周期来完成的.所以这些事情是在xmlWebApplicationContext的refresh的时候发生的.并不是在DispatcherServlet里实现的.所以要搞清楚这些奇特的bean需要研究这些bean到底参与了bean声明周期的哪些步骤,到底做了写什么.此外还需要自己研究下xmlWebApplicationContext的refresh到底干了些什么事情.DispatcherServlet的initStrategies里的各种resolver,每种resolver都是为request与response的某一个环节服务的.比如RequestMappingHandlerMapping是为了让请求委派给正确的Controller服务的.研究这些resolver可以了解从解析请求到生成响应的细节.

 

3.对SpringMVC初始化的步骤有了了解.

 

posted @ 2016-03-16 15:12  abcwt112  阅读(792)  评论(0编辑  收藏  举报