Springboot extension point series_SmartInitializingSingleton

Features

1. SmartInitializingSingleton is mainly used to perform expansion operations when the Spring container is started, that is, afterSingletonsInstantiated();

2. The scope of the bean that implements the SmartInitializingSingleton interface must be a singleton, and only afterSingletonsInstantiated() will be triggered;

3. When afterSingletonsInstantiated() triggers execution, the non-lazy-loaded singleton bean has completed implementation, property injection, and related initialization operations;

3. The execution timing of afterSingletonsInstantiated() is in DefaultListableBeanFactory#preInstantiateSingletons();

There are seven scopes for Spring beans: default singleton (singleton), prototype, request, session, globalSession, application, websocket;

1. Singleton: The Spring container will only create one bean object;

2. Prototype: Each time a bean is obtained, a bean object will be re-created;

3. Request: For each http request, the Spring container will only create one bean object in the same request. If the request ends, the bean will also be destroyed;

4. Session: In the same http session, the Spring container will only create one bean object. If the communication ends, it will also be destroyed;

5. globalSession: The effect of globalSession scope is similar to that of session scope, but it only applies to portlet-based web applications.

6. Application: In the servlet program, the beans in this scope will be globally accessed as attributes of the ServletContext object. The difference from singleton is that there is only one bean in the singleton scope in the Spring container; the bean in the application scope is in The only one in ServletContex;

7. websocket: Create an instance for each websocket object. Only takes effect in Web-related ApplicationContext.

Implementation

Here we use an example to verify the functional characteristics of SmartInitializingSingleton, and analyze its working process through debug:

1. Define the Dog class and use setter injection to inject attributes. At the same time, the Dog class implements the SmartInitializingSingleton interface, overrides afterSingletonsInstantiated(), and prints logs inside the method. If this extension point is used in the actual business development process, the relevant extensions The operation logic is implemented in this method;

@Slf4j
public class Dog implements InitializingBean, DisposableBean, SmartInitializingSingleton {<!-- -->
    private String name = "wang cai";
 
    private food food;
 
    public Dog() {<!-- -->
        log.info("----Dog's parameterless construction method is executed");
    }
    @Autowired
    public void setFood(Food food) {<!-- -->
        this.food = food;
        log.info("----dog's food attribute is injected");
    }
    @Override
    public void afterPropertiesSet() throws Exception {<!-- -->
        log.info("----com.fanfu.entity.Dog.afterPropertiesSet triggers execution");
    }
 
    public void myInitMethod() {<!-- -->
        log.info("----com.fanfu.entity.Dog.myInitMethod triggers execution");
    }
 
    @Override
    public void destroy() throws Exception {<!-- -->
        log.info("----com.fanfu.entity.Dog.destroy triggers execution");
    }
 
    @Override
    public void afterSingletonsInstantiated() {<!-- -->
        log.info("----com.fanfu.entity.Dog.afterSingletonsInstantiated triggers execution");
    }
}

2. Define the Food class as an attribute of the Dog class;

@Slf4j
public class Food {<!-- -->
    private String name = "Big Bone";
 
    public Food() {<!-- -->
        log.info("----Food's parameterless constructor is executed");
    }
}

3. Use the Configuration configuration class to register Dog and Food into the Spring container; the default is a singleton object;

@Configuration
public class SpringConfig {<!-- -->
    @Bean(initMethod = "myInitMethod")
    public Dog dog(){<!-- -->
        Dog dog = new Dog();
        return dog;
    }
    @Bean
    public Food food(){<!-- -->
        Food food = new Food();
        return food;
    }
}

4. Unit testing is used to verify the results;

 @Test
    public void test5(){<!-- -->
        log.info("----Unit test execution starts");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
        log.info("----Unit test execution completed");
    }

img

How it works

If you have debugged the article I shared about the Springboot extension point system from the beginning, you must be familiar with AbstractApplicationContext#refresh(). The core logic related to Spring container startup is all in this method.

1. In AbstractApplicationContext#refresh(), you will find that finishBeanFactoryInitialization() is called. As can be seen from the above comments, this method should instantiate all non-lazy loading singleton beans;

img

2. Enter the finishBeanFactoryInitialization() method, and after doing some beanFactory preparations, call preInstantiateSingletons() to start the non-lazy loading singleton bean instantiation;

img

3. Going on is actually calling DefaultListableBeanFactory#preInstantiateSingletons(). Looking at this method alone, the logic is relatively simple and divided into two parts: the first part, the instantiation of the bean, the injection of attributes, and the related initialization operations; the second part, finding Extract all implementation classes that implement the SmartInitializingSingleton interface, traverse and execute afterSingletonsInstantiated();

public void preInstantiateSingletons() throws BeansException {<!-- -->
    //----------start------------------instantiate the bean---------------- --------------------------
   if (logger.isTraceEnabled()) {<!-- -->
      logger.trace("Pre-instantiating singletons in " + this);
   }
   List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
 
   // Trigger initialization of all non-lazy singleton beans...
   for (String beanName : beanNames) {<!-- -->
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      if (!bd.isAbstract() & amp; & amp; bd.isSingleton() & amp; & amp; !bd.isLazyInit()) {<!-- -->
         if (isFactoryBean(beanName)) {<!-- -->
            Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
            if (bean instanceof FactoryBean) {<!-- -->
               FactoryBean<?> factory = (FactoryBean<?>) bean;
               boolean isEagerInit;
               if (System.getSecurityManager() != null & amp; & amp; factory instanceof SmartFactoryBean) {<!-- -->
                  isEagerInit = AccessController.doPrivileged(
                        (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                        getAccessControlContext());
               }
               else {<!-- -->
                  isEagerInit = (factory instanceof SmartFactoryBean & amp; & amp;
                        ((SmartFactoryBean<?>) factory).isEagerInit());
               }
               if (isEagerInit) {<!-- -->
                  getBean(beanName);
               }
            }
         }
         else {<!-- -->
            getBean(beanName);
         }
      }
   }
   //----------end------------------instantiate the bean---------------- --------------------------
   //----------start------------------SmartInitializingSingleton#afterSingletonsInstantiated---------------- --------------------------
   for (String beanName : beanNames) {<!-- -->
      Object singletonInstance = getSingleton(beanName);
      //Determine whether the singleton bean implements the SmartInitializingSingleton interface
      if (singletonInstance instanceof SmartInitializingSingleton) {<!-- -->
         SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
         if (System.getSecurityManager() != null) {<!-- -->
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {<!-- -->
               smartSingleton.afterSingletonsInstantiated();
               return null;
            }, getAccessControlContext());
         }
         else {<!-- -->
             //Execute afterSingletonsInstantiated()
            smartSingleton.afterSingletonsInstantiated();
         }
      }
   }
   //----------start------------------SmartInitializingSingleton#afterSingletonsInstantiated---------------- --------------------------
}

Summary

If you want to use the SmartInitializingSingleton extension point in business development, you need to pay special attention to the fact that the bean that implements this interface should be a non-lazy-loaded singleton bean. The execution time is after the bean completes instantiation, attribute injection, and related initialization operations, otherwise the execution cannot be triggered.