spring之我见--从spring的启动到ioc容器的创建

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lovejj1994/article/details/79164317

spring是JAVA人可能用的最多的框架之一,我也很遗憾在面试时不止一次被问到spring原理问题时而语塞,也下定决心开了新的一个专题,spring之我见,用“之我见”三个字是为了严谨,因为读源码对于我来说不是简单的活儿,搞不好就是理解错误,所以自我勉励吧。

从spring启动谈起

spring 和 spring boot 在启动上还是有区别的,这篇文章我是以spring4.3为准绳。

我先用idea生成了一个springMVC 项目,web.xml 是首要的配置文件

//这个listener 是 spring ioc 的启动核心,如果你不用spring的ioc,你可以不配置
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    //DispatcherServlet 是 springmvc的前端控制总线,如果你不用spring的controller,你可以不配置,这部分不在我这篇文章的讨论范围
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>

1.1配置ContextLoaderListener的原因

ContextLoaderListener(spring中的类)继承ContextLoader(spring中的类),并实现ServletContextListener(servlet中的接口),ServletContextListener是容器里的类,我用的是tomcat,所以这个类在tomcat的lib里

我们知道,当我们启动tomcat的时候,spring也随之启动,为什么呢?因为通过ContextLoaderListener监听,ContextLoaderListener是作为启动spring的入口。可能这样说还是有点懵,我们先介绍下ServletContext。

1.1.1ServletContext

ServletContext,Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。可以把ServletContext看作一个Web应用的服务器端组件的共享内存。在ServletContext中可以存放共享数据。

web.xml新增下面servlet配置

<servlet>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>TestServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/testServlet</url-pattern>
</servlet-mapping>
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends HttpServlet {


    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("初始化 TestServlet,在第一次访问的时候初始化");
        super.init(config);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {

        // 获得ServletContext的引用
        ServletContext context = getServletContext();

        // 从ServletContext读取count属性
        Integer count = (Integer) context.getAttribute("count");

        String contextConfigLocation = context.getInitParameter("contextConfigLocation");

        System.out.println("web.xml 中配的context-param 属性contextConfigLocation : " + contextConfigLocation);

        // 如果没有读到count属性,那么创建count属性,并设置初始值为0
        if (count == null) {
            System.out.println("context中还没有count属性呢");
            count = new Integer(0);
            context.setAttribute("count", count);
        }
        count = count + 1;
        // count增加之后还要写回去,引用为什么还要重新存回去
        context.setAttribute("count", count);
        System.out.println("您是第" + count + "个访问的!");

    }

    @Override
    public void destroy() {
        super.destroy();
    }

}

这里写图片描述

从上图看出,该系统的ServletContext是一个ApplicationContextFacade实例,位于tomcat包下。

再从上述代码中可见通过getServletContext()方法可以直接获得ServletContext的引用。好了,到此我们知道了ServletContext是每个web应用必须的,随着tomcat启动而有一个ServletContext实例,而该对象又有一个ServletContextListener的接口,监视ServletContext的创建,这样就可以调用这个接口的回调方法来启动Spring容器了。

1.1.2ServletContextListener

从一开始的web.xml中我们配置了一个listener和 上节阐述了ServletContext和spring的关系,我们有必要看看ContextLoaderListener是帮助spring启动的,但是在此之前我们得看看ContextLoaderListener的接口,ServletContextListener,了解它方法的定义。

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

先看接口定义

package javax.servlet;

import java.util.EventListener;

/**
 * Implementations of this interface receive notifications about changes to the
 * servlet context of the web application they are part of. To receive
 * notification events, the implementation class must be configured in the
 * deployment descriptor for the web application.
 */

public interface ServletContextListener extends EventListener {

    /**
    * 通知在web程序初始化的时候开始,所有的ServletContextListeners都会在
    * web应用中任何的filter和servlet初始化之前接收到context初始化的时候通知
     ** Notification that the web application initialization process is starting.
     * All ServletContextListeners are notified of context initialization before
     * any filter or servlet in the web application is initialized.
     * @param sce Information about the ServletContext that was initialized
     */
    public void contextInitialized(ServletContextEvent sce);

    /**
     ** Notification that the servlet context is about to be shut down. All
     * servlets and filters have been destroy()ed before any
     * ServletContextListeners are notified of context destruction.
     */
    public void contextDestroyed(ServletContextEvent sce);
}

我们看注释知道 web程序初始化的时候,contextInitialized()方法是一定会调用的,也就是为什么spring会随着web系统一起启动啦,那我们再看一下ContextLoaderListener的contextInitialized方法做了些什么

    /**
     * Initialize the root web application context.
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

里面调用了initWebApplicationContext方法,我们进去继续往里看

/**
    为给定的ServletContext初始化Spring容器
     * 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);
                }
            }
            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;
        }
    }

initWebApplicationContext方法是在为spring创建一个上下文,也就是spring核心组件,ApplicationContext,它贯穿整个spring生命周期, 包括IOC容器,说到IOC容器,它存储了所有我们要托管给他们的bean实例,但是这里存的又不是实体,而是元数据BeanDefinition(描述该类一切相关信息的实体),需要的时候就可以通过getBean()去反射创建出它的实体,这也就是为什么标题是从启动到 容器的创建,因为这个过程是无缝连接的。

1.1.3ServletContext 和 ApplicationContext的区别

ApplicationContext是spring的核心,Context通常解释为上下文环境,用“容器”来表述更容易理解一些,ApplicationContext则是“应用的容器了”了。

ServletContext 是Servlet与Servlet容器之间直接通信的接口,Servlet容器在启动一个web应用时,会为它创建一个ServletContext对 象,每个web应用有唯一的ServletContext对象,同一个web应用的所有Servlet对象共享一个 ServletContext,Servlet对象可以通过它来访问容器中的各种资源

IOC容器的创建

到了这一章,其实我不自己写了,因为有一篇文章已经是从源码角度 仔细剖析了IOC容器从定位,载入、解析和依赖注入已经全部分析完毕。是难得的好文,看他的文章已经可以足够理解IOC容器相关的知识:

https://www.cnblogs.com/ITtangtang/p/3978349.html

参考文章

https://www.cnblogs.com/tuhooo/p/6491903.html

猜你喜欢

转载自blog.csdn.net/lovejj1994/article/details/79164317
今日推荐