Spring容器及其初始化

Spring的核心在于其IOC和AOP机制,而IOC机制的关键在于Spring中的容器,而Spring中的容器是并非单单指一个独立的对象.根据Spring中组件的职能,做用的不一样,咱们将不一样的组件对象分别注入到两类Context容器中,而且这两类Context容器存在着继承与被继承的关系.前端

1.Spring中的几个概念

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

    • 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应用上下文的初始化

image.png

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

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

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

1. Root Application Contextspring

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);
    }
}

image.png

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

    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 Contextapp

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 {
    public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    public FrameworkServlet() {
        this.contextClass = DEFAULT_CONTEXT_CLASS;
        this.contextInitializers = new ArrayList();
        ...
    }

    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;
        }
    }
    
    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
        return this.createWebApplicationContext((ApplicationContext)parent);
    }
  
    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + this.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 '" + 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;
        }
    }
}
  • 接下来咱们再看DispatcherServletui

    1. 做为Servlet,根据规范它的配置信息应该是在#init方法中完成,咱们首先进入到DispatcherServlet#init,其方法是继承自父类HttpServletBean中.在父类的#init方法中,首先经过遍历获取ServletConfig的全部参数,而后进行Servlet Application Context的初始化
    2. 容器初始化方法#initServletBean位于父类FrameworkServlet中,在#initServletBean方法中调用#initWebApplicationContext方法
    3. initWebApplicationContext方法中首先经过ServletContext获取Root Application Context,而后经过#createWebApplicationContext开始初始化Servlet Application Context,其中获取的#createWebApplicationContext中获取的class对象在构造方法中进行初始化的.在建立容器的过程会传入Root Application Context做为它的Parent,也就是在这里二者创建父子关系,造成以前所说的继承关系,最后一样将新建立的容器放入ContextServlet中.

QQ截图20191101143626.png

3. 总结

因此在Spring中存在两类context,一类Root Application Context,一类是Servlet Application Context,前者根容器是惟一的,是其余全部Servlet应用容器的父类.
以上就是关于在Spring中容器的大体分析,咱们会在项目启动时将不一样的组件实例注入到Spring容器中,从而实现Spring IOC机制.this

  1. 若想要了解如何经过Spring注解方式自定义DispatcherServlet相关内容能够参考:Spring中关于的WebApplicationInitializer及其实现的分析
  2. 若想要了解关于SpringMVC自定义配置化相关知识能够参考:
相关文章
相关标签/搜索