Scope and life cycle of bean s in Spring

Posted by Adthegreat on Tue, 07 Dec 2021 01:42:59 +0100

In Spring, the main bodies that make up the application and the objects managed by the Spring IoC container are called beans. In short, beans are objects initialized, assembled and managed by the IoC container. In addition, beans are no different from other objects in the application. The definition of beans and the dependencies between beans will be described by configuration metadata.

All beans in Spring are singleton by default. For Web applications, the Web container creates a separate servlet thread for each user request to process the request. After the Spring framework is introduced, each Action is singleton. For the singleton Service Bean managed by Spring, the singleton of Spring is based on BeanFactory, that is, the Spring container, There is only one singleton bean in this container. Java singleton is based on JVM, and there is only one instance in each JVM.

Scope of the bean

The essence of creating a bean definition is to use the corresponding class of the bean definition to create the "recipe" of the real instance. It is meaningful to regard the bean definition as a recipe. It is very similar to class. Multiple instances can be created according to only one "recipe". You can control not only the various dependencies and configuration values injected into the object, but also the scope of the object. In this way, you can flexibly select the scope of the created object without defining the scope at the Java Class level. The Spring Framework supports five scopes, which are described in the following table.

Among the five scopes, request, session and global session are only used in web-based applications (don't care what web application framework you use), and can only be used in Web-based Spring ApplicationContext environment.

(1) When the scope of a bean is singleton, there will only be one shared bean instance in the Spring IoC container, and all requests for a bean will only return the same instance of the bean as long as the id matches the bean definition. Singleton is a singleton type, which automatically creates a bean object when creating a container. It exists whether you use it or not, and the object obtained each time is the same object. Note that the singleton scope is the default scope in Spring. To define a bean as a singleton in XML, you can configure it as follows:

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

(2) When the scope of a bean is prototype, it means that a bean definition corresponds to multiple object instances. A prototype scoped bean will cause a new bean instance to be created each time a request is made to the bean (inject it into another bean, or call the container's getBean() method programmatically). Prototype is a prototype type. It is not instantiated when we create a container. Instead, we create an object when we obtain a bean, and the object we obtain each time is not the same object. As a rule of thumb, you should use the prototype scope for stateful beans and the singleton scope for stateless beans. Define a bean as a prototype in XML, which can be configured as follows:

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
 <!--perhaps-->
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/> 

(3) When the scope of a bean is Request, it means that in an HTTP Request, a bean definition corresponds to an instance; That is, each HTTP Request will have its own bean instance, which is created according to a bean definition. This scope is only valid in the case of web-based Spring ApplicationContext. Consider the following bean definitions:

<bean id="loginAction" class=com.foo.LoginAction" scope="request"/>

For each HTTP request, the Spring container will create a new loginaction bean instance according to the loginaction bean definition, and the loginaction bean instance is only valid in the current HTTP request. Therefore, you can safely change the internal state of the created instance according to the needs, while the instances created according to the loginaction bean definition in other requests, You will not see these state changes specific to a request. When the processing of the request ends, the bean instance of the request scope will be destroyed.

(4) When the scope of a bean is Session, it means that in an HTTP Session, a bean definition corresponds to an instance. This scope is only valid in the case of web-based Spring ApplicationContext. Consider the following bean definitions:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

For an HTTP Session, the Spring container will create a new userPreferences bean instance according to the userPreferences bean definition, and the userPreferences bean is only valid in the current HTTP Session. Like the request scope, you can safely change the internal state of the created instance as needed. For instances created according to userPreferences in other HTTP sessions, you will not see these state changes specific to an HTTP Session. When the HTTP Session is finally discarded, the beans within the scope of the HTTP Session will also be discarded.

(5) When the scope of a bean is Global Session, it means that in a global HTTP Session, a bean definition corresponds to an instance. Typically, it is only valid when using portlet context. This scope is only valid in the case of web-based Spring ApplicationContext. Consider the following bean definitions:

<bean id="user" class="com.foo.Preferences "scope="globalSession"/>

The global session scope is similar to the standard HTTP Session scope, but only meaningful in Portlet based web applications. The Portlet specification defines the concept of global session, which is shared by all the different portlets that make up a portlet web application. The bean s defined in the global session scope are limited to the life cycle of the global portlet Session.

The lifecycle of a bean

Instantiation process of Bean in Spring:

Bean lifecycle:

The execution process of Bean instance life cycle is as follows:

  • Spring instantiates beans. The default bean is a singleton;
  • Spring performs dependency injection on bean s;
  • If the bean implements the BeanNameAware interface, Spring passes the bean name to the setBeanName() method;
  • If the bean implements the BeanFactoryAware interface, Spring will call the setBeanFactory() method to pass in the BeanFactory instance;
  • If the bean implements the ApplicationContextAware interface, its setApplicationContext() method will be called to pass the reference of the application context into the bean;
  • If the bean implements the BeanPostProcessor interface, its postProcessBeforeInitialization() method will be called;
  • If the @ PostConstruct annotation is added to a method in the bean, the method will be called;
  • If the bean implements the InitializingBean interface, spring will call its afterpropertieset () interface method. Similarly, if the bean declares the initialization method using the init method property, the method will also be called;
  • If the initialization method is specified in the xml file through the init method element of the < bean > tag, the method will be called;
  • If the bean implements the BeanPostProcessor interface, its postProcessAfterInitialization() interface method will be called;
  • At this point, the bean s are ready to be used by the application, and they will stay in the application context until the application context is destroyed;
  • If the @ PreDestroy annotation is added to a method in the bean, the method will be called;
  • If bean implements DisposableBean interface, spring will call its destroy () interface method. Similarly, if the bean declares a destroy method using the destroy method attribute, the method is called;

The aware interface is specially explained here. The biggest highlight of Spring dependency injection is that all beans are unaware of the existence of Spring containers. However, in actual projects, we sometimes inevitably need to use the resources provided by the Spring container itself. At this time, it is necessary to make the Bean actively aware of the existence of the Spring container before calling the resources provided by Spring. This is the aware interface of Spring. The aware interface is a tag interface, which is used to "perceive" attributes, Aware's many sub interfaces represent the specific attributes to be "perceived". For example, the BeanNameAware interface is used to "sense" its own name, and the ApplicationContextAware interface is used to "sense" its own context. In fact, Spring's aware interfaces are designed for internal use within the framework. In most cases, we don't need to use any aware interfaces unless we really need them. Implementing these interfaces will couple the application layer code to the Spring framework code.

In fact, many times we don't really implement the interfaces described above, so let's remove those interfaces and describe the bean life cycle for single and non single instances of beans:

Object of singleton management

When scope="singleton", that is, by default, it will be instantiated when the container is started (that is, when the container is instantiated). However, we can specify the lazy init = "true" of the bean node to delay the initialization of the bean. At this time, the bean will be initialized only when the bean is obtained for the first time, that is, when the bean is requested for the first time. The configuration is as follows:

<bean id="serviceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>

If you want to apply deferred initialization to all default singleton beans, you can set the default lazy init attribute to true on the root node beans, as shown below:

<beans default-lazy-init="true">

By default, Spring creates objects when it reads xml files. When the object is created, the constructor is first invoked and the method specified in the init-method attribute value is called. When the object is destroyed, the method specified in the destroy method attribute value will be called (for example, when the Container.destroy() method is called). Write a test class with the following code:

public class LifeBean {
 private String name;  
    
    public LifeBean(){  
        System.out.println("LifeBean()Constructor");  
    }  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        System.out.println("setName()");  
        this.name = name;  
    }  

    public void init(){  
        System.out.println("this is init of lifeBean");  
    }  
      
    public void destory(){  
        System.out.println("this is destory of lifeBean " + this);  
    }  
}

life.xml is configured as follows:

<bean id="life_singleton" class="com.bean.LifeBean" scope="singleton" 
   init-method="init" destroy-method="destory" lazy-init="true"/>

The test code is as follows:

public class LifeTest {
 @Test 
 public void test() {
  AbstractApplicationContext container = 
  new ClassPathXmlApplicationContext("life.xml");
  LifeBean life1 = (LifeBean)container.getBean("life");
  System.out.println(life1);
  container.close();
 }
}

The operation results are as follows:

LifeBean()Constructor
this is init of lifeBean
com.bean.LifeBean@573f2bb1
......
this is destory of lifeBean com.bean.LifeBean@573f2bb1

Non singleton managed objects

When scope="prototype", the container will also delay initializing the bean. When spring reads the xml file, it will not create the object immediately, but will initialize the bean the first time it is requested (such as when calling the getBean method). At the first request for every prototype bean, the Spring container calls its constructor to create the object and then calls the method specified in the init-method attribute value. When the object is destroyed, the spring container will not call any methods for us, because it is a non singleton. There are many objects of this type. Once the spring container gives you this object, it will no longer manage this object.

To test the lifecycle of prototype bean s, life.xml is configured as follows:

<bean id="life_prototype" class="com.bean.LifeBean" scope="prototype" init-method="init" destroy-method="destory"/>

The test procedure is as follows:

public class LifeTest {
 @Test 
 public void test() {
  AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml");
  LifeBean life1 = (LifeBean)container.getBean("life_singleton");
  System.out.println(life1);
  
  LifeBean life3 = (LifeBean)container.getBean("life_prototype");
  System.out.println(life3);
  container.close();
 }
}

The operation results are as follows:

LifeBean()Constructor
this is init of lifeBean
com.bean.LifeBean@573f2bb1
LifeBean()Constructor
this is init of lifeBean
com.bean.LifeBean@5ae9a829
......
this is destory of lifeBean com.bean.LifeBean@573f2bb1

It can be found that the destroy method is not called for beans with the scope of prototype. If the scope of the bean is set to prototype, the destroy method will not be called when the container is closed. For a prototype scoped bean, it is very important that Spring cannot be responsible for the whole life cycle of a prototype bean: after initializing, configuring, decorating or assembling a prototype instance, the container gives it to the client, and then ignores the prototype instance. Regardless of the scope, the container will call the initialization lifecycle callback method of all objects. However, for prototype, any configured destruct lifecycle callback method will not be called. It is the responsibility of the client code to clear the objects of the prototype scope and release the expensive resources held by any prototype bean (a possible way for the Spring container to release the resources occupied by the prototype scope bean is to use the bean's post processor, which holds the reference of the bean to be cleared). When talking about prototype scoped beans, in some aspects, you can regard the role of Spring container as a substitute for Java new operation, and any life cycle matters later than that point in time have to be handled by the client.

The spring container can manage the life cycle of beans under the singleton scope. Under this scope, spring can accurately know when beans are created, initialized, and destroyed. Spring is only responsible for creating beans with prototype scope. After the container creates bean instances, the bean instances are handed over to the client for code management. The spring container will no longer track their life cycles, and will not manage the life cycles of beans configured as prototype scope.

Extended

In the process of learning Spring IoC, it is found that each time an ApplicationContext factory is generated:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

There is a disadvantage in generating ApplicationContext. This factory will be generated every time you access the loaded bean, so this problem needs to be solved here.

ApplicationContext is an interface that inherits from the BeanFactory interface. In addition to including all functions of BeanFactory, it provides good support in internationalization support, resource access (such as URL s and files), event propagation, etc.

The solution to the problem is very simple. When the web container starts, transfer the ApplicationContext to the ServletContext, because all servlets in the web application share a ServletContext object. Then we can use the ServletContextListener to listen for ServletContext events. When the web application starts, we load the ApplicationContext into the ServletContext. The Spring container bottom layer has thought of this for us. In the spring-web-xxx-release.jar package, there is a class ContextLoader that has implemented the ServletContextListener interface. Its source code is as follows:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
 private ContextLoader contextLoader;

 public ContextLoaderListener() {

 }

 public ContextLoaderListener(WebApplicationContext context) {
  super(context);
 }

 public void contextInitialized(ServletContextEvent event) {
  this.contextLoader = createContextLoader();
  if (this.contextLoader == null) {
   this.contextLoader = this;
  }
  this.contextLoader.initWebApplicationContext(event.getServletContext());
 }

 @Deprecated
 protected ContextLoader createContextLoader() {
  return null;
 }

 @Deprecated
 public ContextLoader getContextLoader() {
  return this.contextLoader;
 }

 public void contextDestroyed(ServletContextEvent event) {
  if (this.contextLoader != null) {
  this.contextLoader.closeWebApplicationContext(event.getServletContext());
  }
  ContextCleanupListener.cleanupAttributes(event.getServletContext());
 }
}

Here we have listened to the creation process of servletContext. How does this class load applicationContext into the servletContext container?

In the specific implementation of this.contextLoader.initWebApplicationContext(event.getServletContext()) method:

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

The focus here is servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context), using key: webapplicationcontext.root_ WEB_ APPLICATION_ CONTEXT_ Attribute value: the applicationContext is loaded into servletContext in the form of this. Context. In addition, we can see from the above comments: WEB-INF/applicationContext.xml, if the configuration file in our project is not such a path, we will have problems using ContextLoaderListener, so we also need to configure the path of our applicationContext.xml configuration file in web.xml.

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

<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:applicationContext.xml</param-value>
</context-param>

The rest is to start using the applicationContext object loaded in the servletContext in the project: then there is another problem. The key during loading is WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, do we really want to use this in our code? In fact, Spring provides us with a tool class WebApplicationContextUtils. Let's first look at how to use it, and then look at the source code of this tool class:

WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());

Next, let's look at the source code of this tool class:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
 return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

Here, you can intuitively and clearly see the applicationContext object loaded into the servletContext through the key value.

The function of the ContextLoaderListener listener is to automatically assemble the configuration information of ApplicationContext when starting the Web container, because it implements the interface ServletContextListener. When configuring the listener in web.xml and starting the container, its implementation method will be executed by default. The ContextLoader class is associated in the ContextLoaderListener. The whole loading configuration process is completed by the ContextLoader.