Spring容器及其初始化

1.Spring中的几个概念

  • 在阅读Spring源码或相关文献时,会经常遇到这几个名词:WebApplicationContext---ApplicationContext---ServletContext---ServletConfig.这些名词很相近但适用范围有所不同,容易造成spring内部实现的理解混淆,所以首先大致解释这几个名词.

    • ServletContext:这个是来自Servlet规范里的概念,它是Servlet用来与容器间进行交互的接口的组合.也就是,这个接口定义了一系列的方法,Servlet通过这些方法可以很方便地与所在的容器进行一些交互.从它的定义中也可以看出在一个应用中(一个JVM)只有一个ServletContext.也就是说,容器中所有的Servlet都共享一个ServletContext.
    • ServletConfig:它与ServletContext的区别在于,ServletConfig是针对servlet而言的,每个servlet都有它独特的ServletConfig信息,相互之间不共享.
    • ApplicationContext:这个类是Spring容器功能的核心接口,它是Spring实现IOC功能最重要的接口.从它的名字可以看出,它维护了整个程序运行期所需要的上下文信息,注意这里的应用程序并不一定是web程序.在Spring中允许存在多个ApplicationContext,这些ApplicationContext相互之间形成父子,继承与被继承的关系,这也是通常我们所说的:在Spring中存在两个context,一个Root Application Context,一个是Servlet Application Context,这一点在后续会详细阐述.
    • WebApplicaitonContext:这个接口只是ApplicationContext接口的一个子接口,只不过它的应用形式是web,它在ApplicaitonContext的基础上,添加了对ServletContext的引用.

2.Spring容器的初始化

SpringMVC配置文件中,我们通常会配置一个前端控制器 DispatcherServlet和监听器 ContextLoaderListener来进行 Spring应用上下文的初始化

  • 在之前的阐述中可知,ServletContext是容器中所有Servlet共享的配置,它是应用于全局的.根据Servlet规范的规定,根据以上监听器的配置,其中context-param指定了配置文件的未知.在容器启动后初始化ServletContext时,监听器会自动加载配置文件,来初始化Spring的根容器Root Application Context.
  • 同样ServletConfig是针对每个Servlet进步配置的,因此它的配置是在servlet的配置中,根据以上DispatcherServlet的配置,配置中init-param同样指定了在Servlet初始化调用#init方法时加载配置信息的xml文件,并初始化Spring应用容器Servlet Application Context.

接下来我们具体分析Spring容器初始化:

  • 关于ApplicationContext的配置,首先,在ServletContext中配置context-param参数.通过监听器会生成所谓的Root Application Context,而每个DispatcherServlet中指定的init-param参数会生成Servlet Application Context.而且它的parent就是ServletContext中生成的Root Application Context.因此在ServletContext中定义的所有配置都会继承到DispatcherServlet中,这在之后代码中会有直观的提现.

1. Root Application Context

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    //...................
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

}

--------------------------------------------------------------------------------------------------------------------------------------------------------

public class ContextLoader {
    //...................
 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!");
        } else {
          //...................
            try {
                if(this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }
                //...................
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                return this.context;
            } 
        }
    }

 protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class contextClass = this.determineContextClass(sc);
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }


 protected Class determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter("contextClass");
        if(contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
        } else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } 
        }
    }


    static {
        try {
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
        }

        currentContextPerThread = new ConcurrentHashMap(1);
    }
}

  • 为了方便阅读,这里把一些不是核心的代码过滤

    1. 根据配置文件,首先我们通过ContextLoaderListener对象来监听ServletContext初始化,在初始化方法中会调用父类ContextLoader#initWebApplicationContext方法来进行
    2. initWebApplication中首先判断是否存在Root Application Context,如果存在则抛出异常.之后通过#createWebApplicationContext方法来创建容器对象,并会将容器放入ServletContext中.所以对于ApplicationContextServletContext的区别就是ApplicationContext其实就是ServletContext中的一个属性值而已.这个属性中存有程序运行的所有上下文信息,由于这个ApplicationContext是全局的应用上下文,所以在Spring中称它为"Root Application Context".
    3. 接下来我们具体看一下容器是如何创建的:我们进入到#createWebApplicationContext方法中可以看到它是通过实例化容器的class类来创建容器的,而在#determineContextClass方法中首先通过初始化参数来获取全路径类名,若不存在则通过配置类#defaultStrategies来获取容器名称.
    4. 我们可以从当前类的静态代码找到此配置类,它通过读取ContextLoader.properties配置文件来获取当前容器全路径类名称

2. Servlet Application Context

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    public final void init() throws ServletException {
        //遍历获取servletConfig的所有参数
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        //初始化servlet applicationContext
        this.initServletBean();
    }
}

--------------------------------------------------------------------------------------------------------------------------------------------------------

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    protected final void initServletBean() throws ServletException {
        //...................
        try {
            this.webApplicationContext = this.initWebApplicationContext();
        } 
    }

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;

        if(wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }

        if(this.publishContext) {
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
        return this.createWebApplicationContext((ApplicationContext)parent);
    }

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class contextClass = this.getContextClass();
       
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            wac.setConfigLocation(this.getContextConfigLocation());
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }
}
  • 接下来我们再看DispatcherServlet

    1. 作为Servlet,根据规范它的配置信息应该是在#init方法中完成,我们首先进入到DispatcherServlet#init,其方法是继承自父类HttpServletBean中.在父类的#init方法中,首先通过遍历获取ServletConfig的所有参数,然后进行Servlet Application Context的初始化
    2. 容器初始化方法#initServletBean位于父类FrameworkServlet中,在#initServletBean方法中调用#initWebApplicationContext方法
    3. initWebApplicationContext方法中首先通过ServletContext获取Root Application Context,然后开始初始化Servlet Application Context,在创建容器的过程会传入Root Application Context作为它的Parent,也就是在这里两者建立父子关系,形成之前所说的继承关系,最后同样将新创建的容器放入ContextServlet中.

3. 总结

以上就是关于在Spring中容器的大致分析,我们会在项目启动时将不同的组件实例注入到Spring容器中,从而实现Spring IOC机制.

  1. 若想要了解如何通过Spring注解方式自定义DispatcherServlet相关内容可以参考:Spring中关于的WebApplicationInitializer及其实现的分析
  2. 若想要了解关于SpringMVC自定义配置化相关知识可以参考:

你可能感兴趣的:(spring,java)