Novice web development simple summary (9)-ContextLoaderListener

table of Contents

一  ContextLoaderListener

二 ContextLoader#initWebApplicationContext

1. Read the previously saved ApplicationContext

2. Create a new ApplicationContext

(1) determineContextClass(sc): Get the class corresponding to ApplicationContext

(2)(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass)

3. Configure ConfigurableWebApplicationContext properties

4.保存ApplicationContext

三 ContextLoader#closeWebApplicationContext

Four summary


A brief summary of web development for novices in Xiaobai (2)-What is web.xml also mentioned that in a web application, ContextLoaderListener is usually configured in web.xml, so what is the role of ContextLoaderListener?

    <!--用来配置Spring的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:config/application-context.xml</param-value>
    </context-param>
    <!--用来创建Spring IoC容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

We know that a simple summary of novice web development in Xiaobai (6)-Spring's IoC container knows that Spring is the core function of the IoC container, which is to load and manage the life cycle of all JavaBeans. In this summary, it is also mentioned how to pass the code After instantiating an IoC container, how to retrieve the corresponding JavaBean object from the IoC container, and how does this ContextLoaderListener implement this process? Analyze this process from the perspective of source code.

一  ContextLoaderListener

It inherits ContextLoader and implements the ServletContextListener interface. Then when the web application is started, the corresponding contextInitialized() method is called to complete the initialization of the IoC container; when the web application is closed, resources are released, etc.

    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

 Simply naming from the source code is to create the WebApplicationContext object of the IoC container (a simple summary in the novice web development of Xiaobai (6)-the WebApplicationContext mentioned in the Spring IoC container ) and release resources, etc., and the specific Listener's The logic is completed in ContextLoader. Later, I will look at the effects of several key methods in detail.

二 ContextLoader#initWebApplicationContext

The following initWebApplicationContext() code is most of the code in the source code. In order to facilitate the description of the entire process, some non-critical codes are removed.

  public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//=== 1.读取之前保存的ApplicationContext
        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 {
//=== 2.否则就直接创建新的servletContext
                if (this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }
//=== 3.配置ROOT application和读取配置信息
                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }

  //=== 4.将创建的servletContext写入到key中 
  servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//===  5.将创建的servletContext写入到 Map<ClassLoader, WebApplicationContext>中
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = this.context;
                } else if (ccl != null) {
                    currentContextPerThread.put(ccl, this.context);
                }

                return this.context;
            } catch (Error | RuntimeException var8) {
                throw var8;
            }
        }
    }

It can be seen from the code that it is mainly divided into the following steps:

1. Read the previously saved ApplicationContext

The code will first read the previously saved ApplicationContext from WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUT at the beginning, and execute it down if it cannot be read.

Here is a supplementary knowledge of Tomcat and Spring Context:

Generally, Tomcat and Spring projects should include at least three configuration files: web.xml, applicationcontext.xml, and xxx-servlet.xml.

When Tomcat starts a web application, it creates a ServletContext object for each web application, and this ServletContext object can be understood as a Servlet container. Then Tomcat will first read the configuration content of the web.xml file to set the basic information of the web application; generally in the project, in order to be able to start the IoC container in Spring, the ContextLoaderListener will be configured in the web.xml, and the ContextLoaderListener will be in the web. Create an IoC container during the application startup process, then an ApplicationContext is created, to be precise, a WebApplicationContext. The ApplicationContext usually loads components of the middle layer and data layer of the backend other than the web layer, which can be integrated with any web framework .

The first WebApplicationContext created by ContextLoaderListener becomes the ROOT ApplicationContext (the configuration information of applicationcontext.xml is read, which can be specified by contextConfigLocation, if not specified, the read is /WEB-INF/applicationContext.xml), the ROOT ApplicationContext Will be stored in WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUT of ServletContext, and other Context will be associated as child nodes or descendant nodes. That is, a web application can create multiple ApplicationContexts. (The corresponding code logic is org.springframework.web.context.ContextLoader#createWebApplicationContext, you can see this logic in detail in the introduction below)

When Tomcat generates Servlet, it usually needs to configure DispatcherServlet in web.xml, then DispatcherServlet reads the xxx-servlet.xml (xxx is the configuration name of the Servlet) file, and at this time DispatcherServlet will be in this process It initializes a WebApplicationContext related to xxx, and also saves the WebApplicationContext to the ServletContext. The WebApplicationContext will be based on the above ROOT ApplicationContext, and set the above ROOT ApplicationContext to parent, and save it to the ServletContext. The ApplicationContext creates beans for web components, such as controllers, view resolvers, and processor mappings (the corresponding code logic is org.springframework.web.servlet.DispatcherServlet#initWebApplicationContext, which will be analyzed in detail later) (Left issues 1: DispatcherServlet)

Then we can see that the relationship between these three Contexts is briefly described as follows:

  • (1) ROOT ApplicationContext and xxx ApplicationContext and ServletContext related binding;
  • (2) ROOT ApplicationContext is the parent node of xxx ApplicationContext

2. Create a new ApplicationContext

If the first step fails, a new ServletContext will be created through createWebApplicationContext(servletContext). Enter the source code of createWebApplicationContext() to see the logic:

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

There are two important two lines of code in this: one is this.determineContextClass(sc); the other is return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);

(1) determineContextClass(sc): Get the class corresponding to ApplicationContext

The function of this method is to find the ApplicationContext (IoC container: used to instantiate and manage all JavaBeans) from the ServletContext (the Servlet container creates a unique global ServletContext object for each web application).

    protected Class<?> determineContextClass(ServletContext servletContext) {
//我们传入的这个ServletContext就是Tomcat为我们web应用创建的一个Servlet容器的全局对象
//1.首先会从配置中读取contextClass
        String contextClassName = servletContext.getInitParameter("contextClass");
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException var4) {
                throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
            }
        } else {
//2.否则就直接读取默认配置的
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }

The logic of this place is simple:

  • First, the class configured by "contextClass" in the configuration file will be read

That is, if the contextClass is configured in the web.xml file through the following code, then this returns the custom ApplicationContext class at this time;

    <context-param>
        <param-name>contextClass</param-name>
<!--自己定义的ApplicationContext对应的类名-->
        <param-value>com.wj.hsqldb.SpringApplicationContext</param-value>
    </context-param>

Usually this class will not be configured, and the default class will be used below.

  • Otherwise, the content of org.springframework.web.context.WebApplicationContext configured in ContextLoader.properties (the file is in the org\springframework\web\context\ContextLoader.properties directory) is read (the default value is org.springframework.web. context.support.XmlWebApplicationContext)

Here is a point about ApplicationContext:

When the Tomcat server starts a web application, it will create a ServletContext for each web application, so this ServletContext can be regarded as a reference to the Servlet container, and the ApplicationContext is the Spring IoC container, so the ApplicationContext can be regarded as an IoC container. Each web application can create multiple ApplicationContext.

Common ApplicationContext is divided into two sub-interfaces: ConfigurableApplicationContext (which can be set through a configuration file) and WebApplicationContext (specially used for Web development), and ConfigurableWebApplicationContext is a configurable WebApplicationContext that inherits the WebApplicationContext sub-interface.

Common ConfigurableApplicationContext implementation classes are:

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext

The use of these several methods can refer to the simple summary of Xiaobai novice web development (6)-Spring's IoC container .

Common ConfigurableWebApplicationContext implementation classes are:

  • XmlWebApplicationContext (this class reads the configuration content under /WEB-INF/applicationContext.xml by default, of course, you can also specify the configuration file through contextConfigLocation
  • AnnotationConfigWebApplicationContext: Read the configuration class of @Configuration

The relationship diagram is as follows:

Finally, the class name of the ApplicationContext obtained by determineContextClass(sc) is XmlWebApplicationContext, which is a WebApplicationContext that can be configured through a configuration file.

(2)(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass)

At the end of this code, it is obvious that the created WebApplicationContext object is forcibly converted into a ConfigurableWebApplicationContext object. From the relationship between the ApplicationContext interfaces we summarized, then we mean we are going to configure an ApplicationContext through "contextClass" At the time, it must be an implementation class of ConfigurableWebApplicationContext.

3. Configure ConfigurableWebApplicationContext properties

Since the second step has forced the WebApplicationContext to ConfigurableWebApplicationContext, this part of the code must be executed.

The logic of the code is basically to set the various properties of the ConfigurableWebApplicationContext, including reading the information configured by the contextConfigLocation to set the properties. The key method configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc). The code is not posted, you can view it according to the source code.

4.保存ApplicationContext

It can be seen from the code that the last created WebApplicationContext is saved to the WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUT key mentioned in the first step and the currentContextPerThread map of the WebApplicationContext saved by itself.

Through the above four steps, the creation process of WebApplicationContext has been completed, then we can use Spring's IoC container to manage JavaBeans. A simple summary of web development for novices in Xiaobai (6)-The annotation method mentioned in Spring's IoC container to manage JavaBeans should not be used, and can only be done through configuration files.

(Left question 2: And how did those @Component methods that I saw in the company's projects be implemented? )

Answer: It is possible to load JavaBeans through annotations. The following operations are required:

Add in the configuration file corresponding to contextConfigLocation

<beans> 
    <context:annotation-config/>
    <context:component-scan base-package="com.wj"/>
</beans> 

Note that this context: component-scan base-packag is the package to be scanned for annotations. "*" must not be used here, otherwise the following exception will be thrown:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.annotation.internalAsyncAnnotationProcessor' defined in org.springframework.scheduling.annotation.ProxyAsyncConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor]: Factory method 'asyncAdvisor' threw exception; nested exception is java.lang.IllegalArgumentException: @EnableAsync annotation metadata was not injected
		at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:656)
		at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:484)
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)

Be sure to use the corresponding path to be scanned, so that you can use Spring's annotation method to load JavaBean (refer to Xiaobai novice web development simple summary (6)-Spring's IoC container ).

But when the HttpServlet class is added to the project, another problem will be introduced here: you will find that when @Autowired is used to introduce a JavaBean instance in a subclass of HttpServlet, the JavaBean instance NullPointException will be thrown. The reason is Because HttpServlet is in the Servlet container, and these instantiated JavaBean objects are in the Spring IoC container, these JavaBeans cannot be obtained in the Servlet container, but spring-web also provides a solution, which is to override the init(config) of HttpServlet Add the following code to the method:

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        ServletContext application = this.getServletContext();     
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,application);
    }

For some specific code implementations, please refer to Xiaobai Novice Web Development Brief Summary (10)-Summary of Database HSQLDB Instance Problems

In summary, to be precise, ContextLoaderListener reads the xml file specified in the contextConfigLocation when the web application starts, automatically assembles the configuration information of the ApplicationContext, and creates a WebApplicationContext object, that is, the IoC container, because the Servlet is in the Servlet container (Tomcat), The JavaBean instantiated by Spring is in the IoC container. In order to make these JavaBeans available in the Servlet container, the created WebApplicationContext object is bound to the ServletContext, then the WebApplicationContext object can be accessed through the Servlet, and this object can be used To access the JavaBean.

 (Legacy question 3: Here I need to verify whether my conclusion is correct!!!)

It is possible to directly obtain the WebApplicationContext to access these JavaBeans, but it does not need to be so troublesome. After adding these JavaBean dependencies through annotations, you only need to override the init(config) method of HttpServlet and add SpringBeanAutowiringSupport.processInjectionBasedOnServletContext() to access the corresponding JavaBeans through annotations. For details, please refer to the simple summary of Xiaobai novice web development (10)-Summary of database HSQLDB instance problems

三 ContextLoader#closeWebApplicationContext

The code logic should be relatively clear, that is, to release resources: clear the contents stored in the ServletContext and the local Map.

public void closeWebApplicationContext(ServletContext servletContext) {
        servletContext.log("Closing Spring root WebApplicationContext");
        boolean var6 = false;

        try {
            var6 = true;
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ((ConfigurableWebApplicationContext)this.context).close();
                var6 = false;
            } else {
                var6 = false;
            }
        } finally {
            if (var6) {
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = null;
                } else if (ccl != null) {
                    currentContextPerThread.remove(ccl);
                }
  //清空Servlet里面的内容          
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            }
        }

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = null;
        } else if (ccl != null) {
 //清空Map里面的内容  
            currentContextPerThread.remove(ccl);
        }
  //清空Servlet里面的内容  servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }

Four summary

Through the interpretation of the source code of ContextLoaderListener, I finally understand that I have summarized the simple summary of Xiaobai novice web development before (2)-What is web.xml , why configure ContextLoaderListener in web.xml, and develop in Xiaobai novice web development Brief summary (10)-The reason why the bookManagerService instantiated in the HttpServlet using the annotation method is a null pointer encountered in the summary of the database HSQLDB instance problem .

1. ContextLoaderListener will create Root WebApplicationContext when the web application is started;

2. A WebApplicationContext is a Spring IoC container used to manage JavaBeans;

3. ContextLoaderListener will first read the WebApplicationContext previously saved in the ServletContext, and recreate it if there is none;

4. When ContextLoaderListener creates a new WebApplicationContext, it will set the returned WebApplicationContext according to whether "contextClass" is configured in web.xml; if it is configured, it will return the configured class; if it is not configured, it will read the default XmlWebApplicationContext;

5. The newly created WebApplicationContext of ContextLoaderListener will be converted into ConfigurableWebApplicationContext object, so if you want to configure "contextClass" through web.xml, this class must be the implementation class of ConfigurableWebApplicationContext;

6. An ApplicationContext interface is divided into ConfigurableApplicationContext and WebApplicationContext, and WebApplicationContext is dedicated to web applications, and in order to be configurable, a sub-interface of ConfigurableWebApplicationContext is deliberately generated for the configurable WebApplicationContext of web applications;

7. When the WebApplicationContext object is generated, it is to configure each attribute inside, that is, read the "contextConfigLocation" configuration file in web.xml to configure the attributes;

8. Save the WebApplicationContext to the ServletContext, so you can use the WebApplicationContext object in the ServletContext;

9. ContextLoaderListener will clear the content stored in the ServletContext when the web application is closed, and release the content in the local Map.

Of course there are several remaining issues:

1. Analysis of DispatcherServlet source code;

2. Why can you use @Controller and other objects to mark a JavaBean in Spring MVC, because what I understand now is that the ApplicationContext is an XmlWebApplicationContext, and JavaBeans can only be managed through configuration files;

3. Because of the loading of the ContextLoaderListener, the JavaBean object can be used in the Servlet, this needs to be verified

Guess you like

Origin blog.csdn.net/nihaomabmt/article/details/114259276