首页 > 编程语言 > 详细

Spring 源码分析(十)--SpringMVC(上篇)

时间:2018-02-19 22:44:39      阅读:448      评论:0      收藏:0      [点我收藏+]

    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 = ",; \t\n";

    /**
     * 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;

}

 

Spring 源码分析(十)--SpringMVC(上篇)

原文:https://www.cnblogs.com/fdzfd/p/8453110.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!