zoukankan      html  css  js  c++  java
  • Spring 源码分析(十)--SpringMVC(上篇)

        Spring框架提供了构建Web应用程序的全功能MVC模块。通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术。Spring MVC分离了控制器,模型对象,分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。

        Spring的MVC是基于Servlet功能实现的,通过实现Servlet接口的DispatcherServlet来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射,视图解析,本地语言,主题解析以及上载文件支持。默认的处理程序是非常简单的Controller接口,只有一个方法ModelAndView  handleRequest(request, response)。Spring提供了一个控制器层次结构,可以派生子类。如果应用程序需要处理用户输入表单,那么可以继承AbstractFormController。如果需要把多页输入处理到一个表单,那么可以继承AbstractWizardFormController。

    一:SpringMVC 快速体验

    (1)配置web.xml

        一个Web中可以没有web.xml文件,也就是说,web.xml文件并不是Web工程必须的。web.xml文件用来初始化配置信息:比如Welcome页面,servlet,servlet-mapping,filter,listener,启动加载级别等。但是,SpringMVC的实现原理是通过Servlet拦截所有URL来达到控制的目的,所以web.xml的配置是必须的

      Spring的MVC之所以必须要配置web.xml,其实最关键的是要配置两个地方。

    • contextConfigLocation:Spring的核心就是配置文件,可以说配置文件是Spring中必不可少的东西(java配置流行前),而这个参数就是使Web与Spring的配置文件相结合的一个关键配置。
    • DispatcherServlet:包含了SpringMVC的请求逻辑,Spring使用此类拦截Web请求并进行相应的逻辑处理。

    (2)创建Spring配置文件applicationContext.xml

    (3)创建model

    (4)创建controller

        在请求的最后返回了ModelAndView类型的实例。ModelAndView类在SpringMVC中占用很重要的地位,控制器执行方法都必须返回一个ModelAndView,ModelAndView对象保存了视图以及视图显示的模型数据,例如其中的参数如下。

    • 第一个参数userList:视图组件的逻辑名称。这里视图的逻辑名称就是userlist,视图解析器会使用该名称查找实际的View对象。
    • 第二个参数users:传递给视图的,模型对象的名称。
    • 第三个参数userList:传递给视图的,模型对象的值。

    (5)创建视图文件userlist.jsp

    (6)创建Servlet配置文件Spring-servlet.xml

        因为SpringMVC是基于Servlet的实现,所以在Web启动的时候,服务器会首先尝试加载对应于Servlet的配置文件,而为了让项目更加模块化,通常我们将Web部分的配置都存于此配置文件中

        此时,启动服务器,输入网址:http://localhost:8080/Springmvc/userlist.htm.看到服务器返回界面:

    二:ContextLoaderListener

        在Web下,我们需要更多的是与Web环境相互结合,通常的办法是将路径以context--param的方式注册并使用ContextLoaderListener进行监听读取。

        ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法,使用ServletContextListener接口,开发者能够在为客户端请求提供服务之前向ServletContext中添加任意的对象。这个对象在ServletContext启动的时候被初始化,然后再ServletContextt整个运行期间都是可见的。

        每一个Web应用都要一个ServletContext与之相关联。ServletContext对象在应用启动时被创建,在应用关闭的时候被销毁。ServletContext在全局范围内有效,类似于应用中的一个全局变量。

        在ServletContextListener中的核心逻辑便是初始化WebApplicationContext实例并存放至ServletContext中

    (2.1)Spring中的ContextLoaderListener

        ServletContext启动之后会调用ServletContextListener的contextInitialized方法,那么,我们就从这个函数开始进行分析。

    /** 
     * Interface for receiving notification events about ServletContext
     * lifecycle changes.
     *
     * <p>In order to receive these notification events, the implementation
     * class must be either declared in the deployment descriptor of the web
     * application, annotated with {@link javax.servlet.annotation.WebListener},
     * or registered via one of the addListener methods defined on
     * {@link ServletContext}.
     *
     * <p>Implementations of this interface are invoked at their
     * {@link #contextInitialized} method in the order in which they have been
     * declared, and at their {@link #contextDestroyed} method in reverse
     * order.
     *
     * @see ServletContextEvent
     *
     * @since Servlet 2.3
     */
    public interface ServletContextListener extends EventListener {
    
        /**
         * Receives notification that the web application initialization
         * process is starting.
         *
         * <p>All ServletContextListeners are notified of context
         * initialization before any filters or servlets in the web
         * application are initialized.
         *
         * @param sce the ServletContextEvent containing the ServletContext
         * that is being initialized
         */
        public void contextInitialized(ServletContextEvent sce);
    
        /**
         * Receives notification that the ServletContext is about to be
         * shut down.
         *
         * <p>All servlets and filters will have been destroyed before any
         * ServletContextListeners are notified of context
         * destruction.
         *
         * @param sce the ServletContextEvent containing the ServletContext
         * that is being destroyed
         */
        public void contextDestroyed(ServletContextEvent sce);
    }
    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    
        /**
         * Create a new {@code ContextLoaderListener} that will create a web application
         * context based on the "contextClass" and "contextConfigLocation" servlet
         * context-params. See {@link ContextLoader} superclass documentation for details on
         * default values for each.
         * <p>This constructor is typically used when declaring {@code ContextLoaderListener}
         * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
         * required.
         * <p>The created application context will be registered into the ServletContext under
         * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
         * and the Spring application context will be closed when the {@link #contextDestroyed}
         * lifecycle method is invoked on this listener.
         * @see ContextLoader
         * @see #ContextLoaderListener(WebApplicationContext)
         * @see #contextInitialized(ServletContextEvent)
         * @see #contextDestroyed(ServletContextEvent)
         */
        public ContextLoaderListener() {
        }
    
        /**
         * Create a new {@code ContextLoaderListener} with the given application context. This
         * constructor is useful in Servlet 3.0+ environments where instance-based
         * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
         * API.
         * <p>The context may or may not yet be {@linkplain
         * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
         * (a) is an implementation of {@link ConfigurableWebApplicationContext} and
         * (b) has <strong>not</strong> already been refreshed (the recommended approach),
         * then the following will occur:
         * <ul>
         * <li>If the given context has not already been assigned an {@linkplain
         * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
         * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
         * the application context</li>
         * <li>{@link #customizeContext} will be called</li>
         * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
         * specified through the "contextInitializerClasses" init-param will be applied.</li>
         * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
         * </ul>
         * If the context has already been refreshed or does not implement
         * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
         * assumption that the user has performed these actions (or not) per his or her
         * specific needs.
         * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
         * <p>In any case, the given application context will be registered into the
         * ServletContext under the attribute name {@link
         * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
         * application context will be closed when the {@link #contextDestroyed} lifecycle
         * method is invoked on this listener.
         * @param context the application context to manage
         * @see #contextInitialized(ServletContextEvent)
         * @see #contextDestroyed(ServletContextEvent)
         */
        public ContextLoaderListener(WebApplicationContext context) {
            super(context);
        }
    
    
        /**
         * Initialize the root web application context.
         */
        @Override
        public void contextInitialized(ServletContextEvent event) {
            initWebApplicationContext(event.getServletContext());
        }
    
    
        /**
         * Close the root web application context.
         */
        @Override
        public void contextDestroyed(ServletContextEvent event) {
            closeWebApplicationContext(event.getServletContext());
            ContextCleanupListener.cleanupAttributes(event.getServletContext());
        }
    
    }

        这里涉及一个常用类WebApplicationContext:在Web应用中,我们会用到WebApplicationContext,WebApplicationContext继承自ApplicationContext,在ApplicationContext的基础上又追加了一些特定于Web的操作及属性,非常类似于我们通过编程方式使用Spring时使用的ClassPathXmlApplicationContext类提供的功能。

    public class ContextLoader {
    
        /**
         * Config param for the root WebApplicationContext id,
         * to be used as serialization id for the underlying BeanFactory: {@value}
         */
        public static final String CONTEXT_ID_PARAM = "contextId";
    
        /**
         * Name of servlet context parameter (i.e., {@value}) that can specify the
         * config location for the root context, falling back to the implementation's
         * default otherwise.
         * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
         */
        public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
    
        /**
         * Config param for the root WebApplicationContext implementation class to use: {@value}
         * @see #determineContextClass(ServletContext)
         */
        public static final String CONTEXT_CLASS_PARAM = "contextClass";
    
        /**
         * Config param for {@link ApplicationContextInitializer} classes to use
         * for initializing the root web application context: {@value}
         * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
         */
        public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
    
        /**
         * Config param for global {@link ApplicationContextInitializer} classes to use
         * for initializing all web application contexts in the current application: {@value}
         * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
         */
        public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
    
        /**
         * Optional servlet context parameter (i.e., "{@code locatorFactorySelector}")
         * used only when obtaining a parent context using the default implementation
         * of {@link #loadParentContext(ServletContext servletContext)}.
         * Specifies the 'selector' used in the
         * {@link ContextSingletonBeanFactoryLocator#getInstance(String selector)}
         * method call, which is used to obtain the BeanFactoryLocator instance from
         * which the parent context is obtained.
         * <p>The default is {@code classpath*:beanRefContext.xml},
         * matching the default applied for the
         * {@link ContextSingletonBeanFactoryLocator#getInstance()} method.
         * Supplying the "parentContextKey" parameter is sufficient in this case.
         */
        public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
    
        /**
         * Optional servlet context parameter (i.e., "{@code parentContextKey}")
         * used only when obtaining a parent context using the default implementation
         * of {@link #loadParentContext(ServletContext servletContext)}.
         * Specifies the 'factoryKey' used in the
         * {@link BeanFactoryLocator#useBeanFactory(String factoryKey)} method call,
         * obtaining the parent application context from the BeanFactoryLocator instance.
         * <p>Supplying this "parentContextKey" parameter is sufficient when relying
         * on the default {@code classpath*:beanRefContext.xml} selector for
         * candidate factory references.
         */
        public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
    
        /**
         * Any number of these characters are considered delimiters between
         * multiple values in a single init-param String value.
         */
        private static final String INIT_PARAM_DELIMITERS = ",; 	
    ";
    
        /**
         * Name of the class path resource (relative to the ContextLoader class)
         * that defines ContextLoader's default strategy names.
         */
        private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
    
    
        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, ContextLoader.class);
                defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
            }
            catch (IOException ex) {
                throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
            }
        }
    
    
        /**
         * Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext.
         */
        private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
                new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);
    
        /**
         * The 'current' WebApplicationContext, if the ContextLoader class is
         * deployed in the web app ClassLoader itself.
         */
        private static volatile WebApplicationContext currentContext;
    
    
        /**
         * The root WebApplicationContext instance that this loader manages.
         */
        private WebApplicationContext context;
    
        /**
         * Holds BeanFactoryReference when loading parent factory via
         * ContextSingletonBeanFactoryLocator.
         */
        private BeanFactoryReference parentContextRef;
    
        /** Actual ApplicationContextInitializer instances to apply to the context */
        private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
                new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
    
    
        /**
         * Create a new {@code ContextLoader} that will create a web application context
         * based on the "contextClass" and "contextConfigLocation" servlet context-params.
         * See class-level documentation for details on default values for each.
         * <p>This constructor is typically used when declaring the {@code
         * ContextLoaderListener} subclass as a {@code <listener>} within {@code web.xml}, as
         * a no-arg constructor is required.
         * <p>The created application context will be registered into the ServletContext under
         * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
         * and subclasses are free to call the {@link #closeWebApplicationContext} method on
         * container shutdown to close the application context.
         * @see #ContextLoader(WebApplicationContext)
         * @see #initWebApplicationContext(ServletContext)
         * @see #closeWebApplicationContext(ServletContext)
         */
        public ContextLoader() {
        }
    
        /**
         * Create a new {@code ContextLoader} with the given application context. This
         * constructor is useful in Servlet 3.0+ environments where instance-based
         * registration of listeners is possible through the {@link ServletContext#addListener}
         * API.
         * <p>The context may or may not yet be {@linkplain
         * ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation
         * of {@link ConfigurableWebApplicationContext} and (b) has <strong>not</strong>
         * already been refreshed (the recommended approach), then the following will occur:
         * <ul>
         * <li>If the given context has not already been assigned an {@linkplain
         * ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
         * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
         * the application context</li>
         * <li>{@link #customizeContext} will be called</li>
         * <li>Any {@link ApplicationContextInitializer}s specified through the
         * "contextInitializerClasses" init-param will be applied.</li>
         * <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called</li>
         * </ul>
         * If the context has already been refreshed or does not implement
         * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
         * assumption that the user has performed these actions (or not) per his or her
         * specific needs.
         * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
         * <p>In any case, the given application context will be registered into the
         * ServletContext under the attribute name {@link
         * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and subclasses are
         * free to call the {@link #closeWebApplicationContext} method on container shutdown
         * to close the application context.
         * @param context the application context to manage
         * @see #initWebApplicationContext(ServletContext)
         * @see #closeWebApplicationContext(ServletContext)
         */
        public ContextLoader(WebApplicationContext context) {
            this.context = context;
        }
    
    
        /**
         * Specify which {@link ApplicationContextInitializer} instances should be used
         * to initialize the application context used by this {@code ContextLoader}.
         * @since 4.2
         * @see #configureAndRefreshWebApplicationContext
         * @see #customizeContext
         */
        @SuppressWarnings("unchecked")
        public void setContextInitializers(ApplicationContextInitializer<?>... initializers) {
            if (initializers != null) {
                for (ApplicationContextInitializer<?> initializer : initializers) {
                    this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
                }
            }
        }
    
    
        /**
         * Initialize Spring's web application context for the given servlet context,
         * using the application context provided at construction time, or creating a new one
         * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
         * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
         * @param servletContext current servlet context
         * @return the new WebApplicationContext
         * @see #ContextLoader(WebApplicationContext)
         * @see #CONTEXT_CLASS_PARAM
         * @see #CONFIG_LOCATION_PARAM
         */
        public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
            if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
                throw new IllegalStateException(
                        "Cannot initialize context because there is already a root application context present - " +
                        "check whether you have multiple ContextLoader* definitions in your web.xml!");
            }
    
            Log logger = LogFactory.getLog(ContextLoader.class);
            servletContext.log("Initializing Spring root WebApplicationContext");
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }
            long startTime = System.currentTimeMillis();
    
            try {
                // Store context in local instance variable, to guarantee that
                // it is available on ServletContext shutdown.
                if (this.context == null) {
                    this.context = createWebApplicationContext(servletContext);
                }
                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                    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 ->
                            // determine parent for root web application context, if any.
                            ApplicationContext parent = loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
    //ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
    this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } } /** * Instantiate the root WebApplicationContext for this loader, either the * default context class or a custom context class if specified. * <p>This implementation expects custom contexts to implement the * {@link ConfigurableWebApplicationContext} interface. * Can be overridden in subclasses. * <p>In addition, {@link #customizeContext} gets called prior to refreshing the * context, allowing subclasses to perform custom modifications to the context. * @param sc current servlet context * @return the root WebApplicationContext * @see ConfigurableWebApplicationContext */ protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } /** * Return the WebApplicationContext implementation class to use, either the * default XmlWebApplicationContext or a custom context class if specified. * @param servletContext current servlet context * @return the WebApplicationContext implementation class to use * @see #CONTEXT_CLASS_PARAM * @see org.springframework.web.context.support.XmlWebApplicationContext */ protected Class<?> determineContextClass(ServletContext servletContext) {
    //CONTEXT_CLASS_PARAM = "contextClass" String contextClassName
    = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]", ex); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]", ex); } } } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } wac.setServletContext(sc); String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } customizeContext(sc, wac); wac.refresh(); } /** * Customize the {@link ConfigurableWebApplicationContext} created by this * ContextLoader after config locations have been supplied to the context * but before the context is <em>refreshed</em>. * <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext) * determines} what (if any) context initializer classes have been specified through * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and * {@linkplain ApplicationContextInitializer#initialize invokes each} with the * given web application context. * <p>Any {@code ApplicationContextInitializers} implementing * {@link org.springframework.core.Ordered Ordered} or marked with @{@link * org.springframework.core.annotation.Order Order} will be sorted appropriately. * @param sc the current servlet context * @param wac the newly created application context * @see #CONTEXT_INITIALIZER_CLASSES_PARAM * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) */ protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { throw new ApplicationContextException(String.format( "Could not apply context initializer [%s] since its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(), wac.getClass().getName())); } this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); } AnnotationAwareOrderComparator.sort(this.contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { initializer.initialize(wac); } } /** * Return the {@link ApplicationContextInitializer} implementation classes to use * if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}. * @param servletContext current servlet context * @see #CONTEXT_INITIALIZER_CLASSES_PARAM */ protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> determineContextInitializerClasses(ServletContext servletContext) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>(); String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM); if (globalClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM); if (localClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } return classes; } @SuppressWarnings("unchecked") private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) { try { Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader()); if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) { throw new ApplicationContextException( "Initializer class does not implement ApplicationContextInitializer interface: " + clazz); } return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz; } catch (ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex); } } /** * Template method with default implementation (which may be overridden by a * subclass), to load or obtain an ApplicationContext instance which will be * used as the parent context of the root WebApplicationContext. If the * return value from the method is null, no parent context is set. * <p>The main reason to load a parent context here is to allow multiple root * web application contexts to all be children of a shared EAR context, or * alternately to also share the same parent context that is visible to * EJBs. For pure web applications, there is usually no need to worry about * having a parent context to the root web application context. * <p>The default implementation uses * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}, * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context * which will be shared by all other users of ContextsingletonBeanFactoryLocator * which also use the same configuration parameters. * @param servletContext current servlet context * @return the parent application context, or {@code null} if none * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator */ protected ApplicationContext loadParentContext(ServletContext servletContext) { ApplicationContext parentContext = null; String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); if (parentContextKey != null) { // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml" BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); Log logger = LogFactory.getLog(ContextLoader.class); if (logger.isDebugEnabled()) { logger.debug("Getting parent context definition: using parent context key of '" + parentContextKey + "' with BeanFactoryLocator"); } this.parentContextRef = locator.useBeanFactory(parentContextKey); parentContext = (ApplicationContext) this.parentContextRef.getFactory(); } return parentContext; } /** * Close Spring's web application context for the given servlet context. If * the default {@link #loadParentContext(ServletContext)} implementation, * which uses ContextSingletonBeanFactoryLocator, has loaded any shared * parent context, release one reference to that shared parent context. * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have * to override this method as well. * @param servletContext the ServletContext that the WebApplicationContext runs in */ public void closeWebApplicationContext(ServletContext servletContext) { servletContext.log("Closing Spring root WebApplicationContext"); try { if (this.context instanceof ConfigurableWebApplicationContext) { ((ConfigurableWebApplicationContext) this.context).close(); } } finally { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = null; } else if (ccl != null) { currentContextPerThread.remove(ccl); } servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (this.parentContextRef != null) { this.parentContextRef.release(); } } } /** * Obtain the Spring root web application context for the current thread * (i.e. for the current thread's context ClassLoader, which needs to be * the web application's ClassLoader). * @return the current root web application context, or {@code null} * if none found * @see org.springframework.web.context.support.SpringBeanAutowiringSupport */ public static WebApplicationContext getCurrentWebApplicationContext() { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl != null) { WebApplicationContext ccpt = currentContextPerThread.get(ccl); if (ccpt != null) { return ccpt; } } return currentContext; } }

        initWebApplicationContext函数主要是体现了创建WebApplicationContext实例的一个功能架构,从函数中我们看到了初始化的大致步骤。

      (1)WebApplicationContext存在性的验证。

        在配置中只允许声明一次ServletContextListener,多次声明会扰乱Spring的执行逻辑,所以这里首先做的就是对此验证,在Spring中如果创建WebApplicationContext实例会记录在ServletContext中以便全局调用,而使用的key就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

     所以验证的方式就是查看ServletContext实例中是否有对应key的属性

      (2)创建WebApplicationContext实例。

      如果通过验证,则Spring将创建WebApplicationContext实例的工作委托给了createWebApplicationContext函数。

      其中,在ContextLoader类中有这样的静态代码块:

    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, ContextLoader.class);
                defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
            }
            catch (IOException ex) {
                throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
            }
        }

        根据以上静态代码块的内容,我们推断在当前类ContextLoader通用目录下必定会存在属性文件ContextLoader.properties,查看后果然存在,内容如下:

    # Default WebApplicationContext implementation class for ContextLoader.
    # Used as fallback when no explicit context implementation has been specified as context-param.
    # Not meant to be customized by application developers.
    
    org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

        综合以上代码分析,在初始化的过程中,在没有配置contextClass情况下,程序首先会读取ContextLoader类的同目录下的属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个实现类通过反射的方式进行实例的创建

    (3)将实例记录在servletContext中。

    (4)映射当前的类加载器与创建的实例到全局变量currentContextPerThread中。

    三:DispatcherServlet

     (3.1)介绍

        在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。

        servlet是一个java编写的程序,此程序是基于HTTP协议的,在服务器端运行的(如Tomcat),是按照servlet规范编写的一个Java类。主要是处理客户端的请求并将其结果发送到客户端。servlet的生命周期是由servlet的容器来控制的,它可以分为3个阶段:初始化,运行和销毁。

    (1)初始化阶段

    •   servlet容器加载servlet类,把servlet类的.class文件中的数据读到内存中。
    • servlet容器创建一个ServletConfig对象。ServletConfig对象包含了servlet的初始化配置信息。
    • servlet容器创建一个servlet对象
    • servlet容器调用servlet对象的init方法进行初始化

    (2)运行阶段。

        当servlet容器接收到一个请求时,servlet容器会针对这个请求创建servletRequest和servletResponse对象,然后调用service方法。并把这两个参数传递给service方法。service方法通过servletRequest对象获得请求的信息。并处理该请求。再通过servletResponse对象生成这个请求的响应结果。然后销毁servletRequest和servletResponse对象。我们不管这个请求是post提交的还是get提交的,最终这个请求都会由service方法来处理。

    (3)销毁阶段。

        当Web应用被终止时,servlet容器会先调用servlet对象的destory方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的servletConfig对象。我们可以在destory方法的实现张红,释放servlet所占用的资源,如关闭数据库连接,关闭文件输入输出流等。

        servlet的框架是由两个Java包组成:javax.servlet和javax.servlet.http。在javax.servlet包中定义了所有的servlet类都必须实现或扩展的通用接口和类,在javax.servlet.http包中定义了采用HTTP通信协议的HttpServlet类。

        servlet被设计成请求驱动,servlet的请求可能包含多个数据项,当Web容器接收到某个servlet请求时,servlet把请求封装成一个HttpServletRequest对象,然后把对象传给servlet的对应的服务方法

    (3.2)DispatcherServlet的初始化

        在servlet初始化阶段会调用其init方法,所以我们首先要查看在DispatcherServlet中是否重写了init方法。我们在其父类HttpServletBean中找到了该方法。

    /**
         * Map config parameters onto bean properties of this servlet, and
         * invoke subclass initialization.
         * @throws ServletException if bean properties are invalid (or required
         * properties are missing), or if subclass initialization fails.
         */
        @Override
        public final void init() throws ServletException {
            if (logger.isDebugEnabled()) {
                logger.debug("Initializing servlet '" + getServletName() + "'");
            }
    
            // Set bean properties from init parameters.
            try {
    //解析init-param并封装至pvs中 PropertyValues pvs
    = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); //将当前的Servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入
    BeanWrapper bw
    = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); //空实现,留给子类覆盖
    initBeanWrapper(bw); bw.setPropertyValues(pvs,
    true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } // Let subclasses do whatever initialization they like. initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }

        DispatcherServlet的初始化过程主要是通过将当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入。这些属性如contextAttribute,contextClass,nameSpace,contextConfigLocation等,都可以在web.xml文件中以初始化参数的方式配置在servlet的声明中。DispatcherServlet继承自FrameworkServlet,FrameworkServlet类上包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。属性注入主要包含以下几个步骤:

    (1)封装及验证初始化参数

      ServletConfigPropertyValues除了封装属性外还有对属性验证的功能。

    HttpServletBean中静态类:

    /**
         * PropertyValues implementation created from ServletConfig init parameters.
         */
        private static class ServletConfigPropertyValues extends MutablePropertyValues {
    
            /**
             * Create new ServletConfigPropertyValues.
             * @param config ServletConfig we'll use to take PropertyValues from
             * @param requiredProperties set of property names we need, where
             * we can't accept default values
             * @throws ServletException if any required properties are missing
             */
            public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
                throws ServletException {
    
                Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty() ?
                        new HashSet<String>(requiredProperties) : null);
    
                Enumeration<String> paramNames = config.getInitParameterNames();
                while (paramNames.hasMoreElements()) {
                    String property = paramNames.nextElement();
                    Object value = config.getInitParameter(property);
                    addPropertyValue(new PropertyValue(property, value));
                    if (missingProps != null) {
                        missingProps.remove(property);
                    }
                }
    
                // Fail if we are still missing properties.
                if (!CollectionUtils.isEmpty(missingProps)) {
                    throw new ServletException(
                        "Initialization from ServletConfig for servlet '" + config.getServletName() +
                        "' failed; the following required properties were missing: " +
                        StringUtils.collectionToDelimitedString(missingProps, ", "));
                }
            }
        }

      从代码中得知,封装属性主要是对初始化的参数进行封装,也就是servlet中配置的<init-param>中配置的封装。

    (2)将当前servlet实例转化成BeanWrapper实例

        PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例。

    (3)注册相对于Resource的属性编辑器

        这里使用属性编辑器的目的是在对当前实例(DispatcherServlet)属性注入过程中一旦遇到Resource类型的属性就会使用ResourceEditor去解析。

    (4)属性注入

        BeanWrapper为Spring中的方法,支持Spring的自动注入。其实我们最常用的属性组人无非是contextAttribute,contextClass,nameSpace,contextConfigLocation等属性。

    (5)servletBean的初始化

        在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,而在这个函数中最重要的就是对这个实例进行进一步的补充初始化。

        继续查看initServletBean()。父类FrameworkServlet覆盖了HttpServletBean中的initServletBean函数,如下:

    /**
         * Overridden method of {@link HttpServletBean}, invoked after any bean properties
         * have been set. Creates this servlet's WebApplicationContext.
         */
        @Override
        protected final void initServletBean() throws ServletException {
            getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
            if (this.logger.isInfoEnabled()) {
                this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
            }
            long startTime = System.currentTimeMillis();
    
            try {
                this.webApplicationContext = initWebApplicationContext();
    //设计为子类覆盖 initFrameworkServlet(); }
    catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }

        上面的函数设计了计时器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServlet()用于子类的覆盖操作,而作为关键的初始化逻辑实现委托给了initWebApplicationContext()。

    (3.3)WebApplicationContext的初始化

        initWebApplicationContext函数的主要工作就是创建或刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化

      FrameworkServlet类中 initWebApplicationContext 方法

    /**
         * Initialize and publish the WebApplicationContext for this servlet.
         * <p>Delegates to {@link #createWebApplicationContext} for actual creation
         * of the context. Can be overridden in subclasses.
         * @return the WebApplicationContext instance
         * @see #FrameworkServlet(WebApplicationContext)
         * @see #setContextClass
         * @see #setContextConfigLocation
         */
        protected WebApplicationContext initWebApplicationContext() {
    //通过属性WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性在servletContext中获取
    //一般是ContextLoaderListener的初始化函数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); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }

        本函数中的初始化主要包含几个部分。

      1. 寻找或创建对应的WebApplicationContext实例

      WebApplicationContext的寻找及创建包括以下几个步骤。

    (1)通过构造函数的注入进行初始化。

        当进入initWebApplicationContext函数后通过判断this.webApplicationContext != null 后,便可以确定this.webApplicationContext是否是通过构造函数来初始化的。在Web中包含SpringWeb的核心逻辑的DispatcherServlet只可能被声明为一次,在Spring中已经存在验证,所以这就确保了如果this.webApplicationContext != null,则可以直接判定this.webApplicationContext已经通过构造函数初始化。

    (2)通过contextAttribute进行初始化

        如上findWebApplicationContext()函数,通过在web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为WebApplicationContext.class.getName() + ".ROOT",也就是在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName() + ".ROOT"为key放入ServletContext中,当然也可以重写初始化逻辑使用自己创建的WebApplicationContext,并在servlet的配置中通过初始化参数contextAttribute指定key。

    (3)重新创建WebApplicationContext实例

        如果通过以上两种方式并没有找到任何突破,那就没有办法了,只能在这里重新创建新的实例了。

        FrameworkServlet类中

    /**
         * Instantiate the WebApplicationContext for this servlet, either a default
         * {@link org.springframework.web.context.support.XmlWebApplicationContext}
         * or a {@link #setContextClass custom context class}, if set.
         * Delegates to #createWebApplicationContext(ApplicationContext).
         * @param parent the parent WebApplicationContext to use, or {@code null} if none
         * @return the WebApplicationContext for this servlet
         * @see org.springframework.web.context.support.XmlWebApplicationContext
         * @see #createWebApplicationContext(ApplicationContext)
         */
        protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
            return createWebApplicationContext((ApplicationContext) parent);
        }
    
    /**
         * Instantiate the WebApplicationContext for this servlet, either a default
         * {@link org.springframework.web.context.support.XmlWebApplicationContext}
         * or a {@link #setContextClass custom context class}, if set.
         * <p>This implementation expects custom contexts to implement the
         * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
         * interface. Can be overridden in subclasses.
         * <p>Do not forget to register this servlet instance as application listener on the
         * created context (for triggering its {@link #onRefresh callback}, and to call
         * {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
         * before returning the context instance.
         * @param parent the parent ApplicationContext to use, or {@code null} if none
         * @return the WebApplicationContext for this servlet
         * @see org.springframework.web.context.support.XmlWebApplicationContext
         */
        protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
            //获取servlet的初始化参数contextClass,如果没有配置默认为XmlWebApplicationContext.class
    Class
    <?> contextClass = getContextClass(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet with name '" + getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "'" + ", using parent context [" + parent + "]"); } if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); }
    //通过反射方式实例化contextClass ConfigurableWebApplicationContext wac
    = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment());
    //parent为在ContextLoaderListener中创建的实例
    //在ContextLoaderListener加载的时候初始化的WebApplicationContext类型实例 wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation());
    //初始化Spring环境包括加载配置文件等 configureAndRefreshWebApplicationContext(wac);
    return wac; } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information if (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac);
    //加载配置文件及整合parent到wac wac.refresh(); }
    /**
         * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
         * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
         */
        private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    
            @Override
            public void onApplicationEvent(ContextRefreshedEvent event) {
                FrameworkServlet.this.onApplicationEvent(event);
            }
        }
    /**
         * Callback that receives refresh events from this servlet's WebApplicationContext.
         * <p>The default implementation calls {@link #onRefresh},
         * triggering a refresh of this servlet's context-dependent state.
         * @param event the incoming ApplicationContext event
         */
        public void onApplicationEvent(ContextRefreshedEvent event) {
            this.refreshEventReceived = true;
            onRefresh(event.getApplicationContext());
        }

    DispatcherServlet类中重写onRefresh(ApplicationContext context)方法

    /**
         * This implementation calls {@link #initStrategies}.
         */
        @Override
        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);
        }

    2. configureAndRefreshWebApplicationContext 

        无论是通过构造函数注入还是单独创建,都免不了会调用configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext实例进行配置及刷新。

        无论调用方式如何变化,只有是使用ApplicationContext所提供的功能到最后都免不了使用公共父类AbstractApplicationContext提供的refresh()进行配置文件加载。

    3.刷新

        onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写,主要用于刷新Spring在Web功能实现中所必须使用的全局变量。这里从DispatcherServlet类源码中选择几个进行讲解。

    (1)初始化MultipartResolver

        在Spring中,MultipartResolver主要用来处理文件上传。默认情况下,Spring是没有multipart处理的。常用配置如下:

    /**
         * Initialize the MultipartResolver used by this class.
         * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
         * no multipart handling is provided.
         */
        private void initMultipartResolver(ApplicationContext context) {
            try {
    //MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"
    this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); if (logger.isDebugEnabled()) { logger.debug("Using MultipartResolver [" + this.multipartResolver + "]"); } } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; if (logger.isDebugEnabled()) { logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME + "': no multipart request handling provided"); } } }

    (2)初始化HandlerMappings

        当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet相应的Controller.

        在基于SpringMVC的Web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当期的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。初始化配置如下:

    /**
         * Initialize the HandlerMappings used by this class.
         * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
         * we default to BeanNameUrlHandlerMapping.
         */
        private void initHandlerMappings(ApplicationContext context) {
            this.handlerMappings = null;
    
            if (this.detectAllHandlerMappings) { //默认为true
                // 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.
                    AnnotationAwareOrderComparator.sort(this.handlerMappings);
                }
            }
            else {
                try {
    //HANDLER_MAPPING_BEAN_NAME = "handlerMapping" 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); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }

        在最后一步中,如果还是没有定义handlermapping的化,则SpringMVC将按照org.Springframework.web.servlet.DispatcherServlet所在目录下的DispatcherServlet.properties中所定义的org.Springframework.web.servlet.HandlerMapping的内容来加载默认的handlerMapping(用户没有自定义Strategies的情况下)。

    (3)初始化HandlerAdapters。

        从名字也能联想到这是一个典型的适配器模式的使用,适配器模式将一个类的接口适配成用户所期待的。

    /**
         * Initialize the HandlerAdapters used by this class.
         * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
         * we default to SimpleControllerHandlerAdapter.
         */
        private void initHandlerAdapters(ApplicationContext context) {
            this.handlerAdapters = null;
    
            if (this.detectAllHandlerAdapters) {
                // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
                Map<String, HandlerAdapter> matchingBeans =
                        BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
                if (!matchingBeans.isEmpty()) {
                    this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
                    // We keep HandlerAdapters in sorted order.
                    AnnotationAwareOrderComparator.sort(this.handlerAdapters);
                }
            }
            else {
                try {
                    HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
                    this.handlerAdapters = Collections.singletonList(ha);
                }
                catch (NoSuchBeanDefinitionException ex) {
                    // Ignore, we'll add a default HandlerAdapter later.
                }
            }
    
            // Ensure we have at least some HandlerAdapters, by registering
            // default HandlerAdapters if no other adapters are found.
            if (this.handlerAdapters == null) {
                this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
                if (logger.isDebugEnabled()) {
                    logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
                }
            }
        }

        如果无法找到对应的bean,那么系统会尝试加载默认的适配器。

    /**
         * Create a List of default strategy objects for the given strategy interface.
         * <p>The default implementation uses the "DispatcherServlet.properties" file (in the same
         * package as the DispatcherServlet class) to determine the class names. It instantiates
         * the strategy objects through the context's BeanFactory.
         * @param context the current WebApplicationContext
         * @param strategyInterface the strategy interface
         * @return the List of corresponding strategy objects
         */
        @SuppressWarnings("unchecked")
        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>();
            }
        }

        在该函数中,Spring会尝试从defaultStrategies中加载对应的HandlerAdapter的属性,那么defaultStrategies是如何初始化的呢?

        在当前类DispatcherServlet中存在这样一段初始化代码块:

    static {
            // Load default strategy implementations from properties file.
            // This is currently strictly internal and not meant to be customized
            // by application developers.
            try {
    //DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties" 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()); } }

        在系统加载的时候,defaultStrategies根据当前路径DispatcherServlet.properties来初始化本身。DispatcherServlet.properties内容如下:

    # 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

    (4)初始化HandlerExceptionResolvers

        基于HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现resolveException方法,该方法返回一个ModelAndView对象,在方法内部对异常的类型进行判断,然后尝试生成对应的ModelAndView对象,如果该方法返回了null,则Spring会继续寻找其它的实现了HandlerExceptionResolver接口的bean。

    /**
         * Initialize the HandlerExceptionResolver used by this class.
         * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
         * we default to no exception resolver.
         */
        private void initHandlerExceptionResolvers(ApplicationContext context) {
            this.handlerExceptionResolvers = null;
    
            if (this.detectAllHandlerExceptionResolvers) {
                // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
                Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                        .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
                if (!matchingBeans.isEmpty()) {
                    this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
                    // We keep HandlerExceptionResolvers in sorted order.
                    AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
                }
            }
            else {
                try {
                    HandlerExceptionResolver her =
                            context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
                    this.handlerExceptionResolvers = Collections.singletonList(her);
                }
                catch (NoSuchBeanDefinitionException ex) {
                    // Ignore, no HandlerExceptionResolver is fine too.
                }
            }
    
            // Ensure we have at least some HandlerExceptionResolvers, by registering
            // default HandlerExceptionResolvers if no other resolvers are found.
            if (this.handlerExceptionResolvers == null) {
                this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
                if (logger.isDebugEnabled()) {
                    logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
                }
            }
        }

    (5)初始化ViewResolvers

        在SpringMVC中,当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。那么在SpringMVC中是如何选择合适的View呢?View对象是如何创建的呢?答案就在ViewResolver中。ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。

    /**
         * Initialize the ViewResolvers used by this class.
         * <p>If no ViewResolver beans are defined in the BeanFactory for this
         * namespace, we default to InternalResourceViewResolver.
         */
        private void initViewResolvers(ApplicationContext context) {
            this.viewResolvers = null;
    
            if (this.detectAllViewResolvers) {
                // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
                Map<String, ViewResolver> matchingBeans =
                        BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
                if (!matchingBeans.isEmpty()) {
                    this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
                    // We keep ViewResolvers in sorted order.
                    AnnotationAwareOrderComparator.sort(this.viewResolvers);
                }
            }
            else {
                try {
    //VIEW_RESOLVER_BEAN_NAME = "viewResolver" ViewResolver vr
    = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); this.viewResolvers = Collections.singletonList(vr); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default ViewResolver later. } } // Ensure we have at least one ViewResolver, by registering // a default ViewResolver if no other resolvers are found. if (this.viewResolvers == null) { this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); if (logger.isDebugEnabled()) { logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default"); } } }

     ViewResolver接口

    public interface ViewResolver {
    
        /**
         * Resolve the given view by name.
         * <p>Note: To allow for ViewResolver chaining, a ViewResolver should
         * return {@code null} if a view with the given name is not defined in it.
         * However, this is not required: Some ViewResolvers will always attempt
         * to build View objects with the given name, unable to return {@code null}
         * (rather throwing an exception when View creation failed).
         * @param viewName name of the view to resolve
         * @param locale Locale in which to resolve the view.
         * ViewResolvers that support internationalization should respect this.
         * @return the View object, or {@code null} if not found
         * (optional, to allow for ViewResolver chaining)
         * @throws Exception if the view cannot be resolved
         * (typically in case of problems creating an actual View object)
         */
        View resolveViewName(String viewName, Locale locale) throws Exception;
    
    }

     AbstractCachingViewResolver类

    public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
    
        /** Default maximum number of entries for the view cache: 1024 */
        public static final int DEFAULT_CACHE_LIMIT = 1024;
    
        /** Dummy marker object for unresolved views in the cache Maps */
        private static final View UNRESOLVED_VIEW = new View() {
            @Override
            public String getContentType() {
                return null;
            }
            @Override
            public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
            }
        };
    
    
        /** The maximum number of entries in the cache */
        private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
    
        /** Whether we should refrain from resolving views again if unresolved once */
        private boolean cacheUnresolved = true;
    
        /** Fast access cache for Views, returning already cached instances without a global lock */
        private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);
    
        /** Map from view key to View instance, synchronized for View creation */
        @SuppressWarnings("serial")
        private final Map<Object, View> viewCreationCache =
                new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
                    @Override
                    protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
                        if (size() > getCacheLimit()) {
                            viewAccessCache.remove(eldest.getKey());
                            return true;
                        }
                        else {
                            return false;
                        }
                    }
                };
    
    
        /**
         * Specify the maximum number of entries for the view cache.
         * Default is 1024.
         */
        public void setCacheLimit(int cacheLimit) {
            this.cacheLimit = cacheLimit;
        }
    
        /**
         * Return the maximum number of entries for the view cache.
         */
        public int getCacheLimit() {
            return this.cacheLimit;
        }
    
        /**
         * Enable or disable caching.
         * <p>This is equivalent to setting the {@link #setCacheLimit "cacheLimit"}
         * property to the default limit (1024) or to 0, respectively.
         * <p>Default is "true": caching is enabled.
         * Disable this only for debugging and development.
         */
        public void setCache(boolean cache) {
            this.cacheLimit = (cache ? DEFAULT_CACHE_LIMIT : 0);
        }
    
        /**
         * Return if caching is enabled.
         */
        public boolean isCache() {
            return (this.cacheLimit > 0);
        }
    
        /**
         * Whether a view name once resolved to {@code null} should be cached and
         * automatically resolved to {@code null} subsequently.
         * <p>Default is "true": unresolved view names are being cached, as of Spring 3.1.
         * Note that this flag only applies if the general {@link #setCache "cache"}
         * flag is kept at its default of "true" as well.
         * <p>Of specific interest is the ability for some AbstractUrlBasedView
         * implementations (FreeMarker, Velocity, Tiles) to check if an underlying
         * resource exists via {@link AbstractUrlBasedView#checkResource(Locale)}.
         * With this flag set to "false", an underlying resource that re-appears
         * is noticed and used. With the flag set to "true", one check is made only.
         */
        public void setCacheUnresolved(boolean cacheUnresolved) {
            this.cacheUnresolved = cacheUnresolved;
        }
    
        /**
         * Return if caching of unresolved views is enabled.
         */
        public boolean isCacheUnresolved() {
            return this.cacheUnresolved;
        }
    
    
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            if (!isCache()) {
                return createView(viewName, locale);
            }
            else {
                Object cacheKey = getCacheKey(viewName, locale);
                View view = this.viewAccessCache.get(cacheKey);
                if (view == null) {
                    synchronized (this.viewCreationCache) {
                        view = this.viewCreationCache.get(cacheKey);
                        if (view == null) {
                            // Ask the subclass to create the View object.
                            view = createView(viewName, locale);
                            if (view == null && this.cacheUnresolved) {
                                view = UNRESOLVED_VIEW;
                            }
                            if (view != null) {
                                this.viewAccessCache.put(cacheKey, view);
                                this.viewCreationCache.put(cacheKey, view);
                                if (logger.isTraceEnabled()) {
                                    logger.trace("Cached view [" + cacheKey + "]");
                                }
                            }
                        }
                    }
                }
                return (view != UNRESOLVED_VIEW ? view : null);
            }
        }
    
        /**
         * Return the cache key for the given view name and the given locale.
         * <p>Default is a String consisting of view name and locale suffix.
         * Can be overridden in subclasses.
         * <p>Needs to respect the locale in general, as a different locale can
         * lead to a different view resource.
         */
        protected Object getCacheKey(String viewName, Locale locale) {
            return viewName + '_' + locale;
        }
    
        /**
         * Provides functionality to clear the cache for a certain view.
         * <p>This can be handy in case developer are able to modify views
         * (e.g. Velocity templates) at runtime after which you'd need to
         * clear the cache for the specified view.
         * @param viewName the view name for which the cached view object
         * (if any) needs to be removed
         * @param locale the locale for which the view object should be removed
         */
        public void removeFromCache(String viewName, Locale locale) {
            if (!isCache()) {
                logger.warn("View caching is SWITCHED OFF -- removal not necessary");
            }
            else {
                Object cacheKey = getCacheKey(viewName, locale);
                Object cachedView;
                synchronized (this.viewCreationCache) {
                    this.viewAccessCache.remove(cacheKey);
                    cachedView = this.viewCreationCache.remove(cacheKey);
                }
                if (logger.isDebugEnabled()) {
                    // Some debug output might be useful...
                    if (cachedView == null) {
                        logger.debug("No cached instance for view '" + cacheKey + "' was found");
                    }
                    else {
                        logger.debug("Cache for view " + cacheKey + " has been cleared");
                    }
                }
            }
        }
    
        /**
         * Clear the entire view cache, removing all cached view objects.
         * Subsequent resolve calls will lead to recreation of demanded view objects.
         */
        public void clearCache() {
            logger.debug("Clearing entire view cache");
            synchronized (this.viewCreationCache) {
                this.viewAccessCache.clear();
                this.viewCreationCache.clear();
            }
        }
    
    
        /**
         * Create the actual View object.
         * <p>The default implementation delegates to {@link #loadView}.
         * This can be overridden to resolve certain view names in a special fashion,
         * before delegating to the actual {@code loadView} implementation
         * provided by the subclass.
         * @param viewName the name of the view to retrieve
         * @param locale the Locale to retrieve the view for
         * @return the View instance, or {@code null} if not found
         * (optional, to allow for ViewResolver chaining)
         * @throws Exception if the view couldn't be resolved
         * @see #loadView
         */
        protected View createView(String viewName, Locale locale) throws Exception {
            return loadView(viewName, locale);
        }
    
        /**
         * Subclasses must implement this method, building a View object
         * for the specified view. The returned View objects will be
         * cached by this ViewResolver base class.
         * <p>Subclasses are not forced to support internationalization:
         * A subclass that does not may simply ignore the locale parameter.
         * @param viewName the name of the view to retrieve
         * @param locale the Locale to retrieve the view for
         * @return the View instance, or {@code null} if not found
         * (optional, to allow for ViewResolver chaining)
         * @throws Exception if the view couldn't be resolved
         * @see #resolveViewName
         */
        protected abstract View loadView(String viewName, Locale locale) throws Exception;
    
    }
  • 相关阅读:
    [Java] JDBC 06 批Transaction处理 -- conn.setAutoCommit(false); // 不让其自动提交 (很重要的知识点)
    [Java] JDBC 05 TestBatch.java 批处理 Batch
    [Java] JDBC 04 TestProc.java (对存储过程进行调用 CallableStatement)
    [Java] JDBC 03 TestPrepStmt.java
    美化复选框
    美化单选框
    canvas
    html5新增标签
    旋转、水平翻转、垂直翻转
    dede让channelartlist标签支持currentstyle属性 完美解决
  • 原文地址:https://www.cnblogs.com/fdzfd/p/8453110.html
Copyright © 2011-2022 走看看