Unfortunately, Spring Bean initialization / destruction has so many postures

Posted by cute_girl on Mon, 25 Nov 2019 21:10:55 +0100

Source: http://1t.click/bfHN

I. Preface

The daily development process sometimes needs to load some resources after the application is started, or release resources before the application is closed. The spring framework provides related functions. Around the Spring Bean life cycle, you can initialize resources in the Bean creation process and release resources by destroying the Bean process. Spring provides many different ways to initialize / destroy beans. If you use these ways at the same time, how does spring handle the order between them?

Do you think the title is very familiar? Yes, the title imitates the second brother's article "silence King II" Shame, there are so many postures in Java string splicing.

II. Posture analysis

First of all, let's review several ways Spring initializes / destroys beans:

  • init-method/destroy-method
  • InitializingBean/DisposableBean
  • @PostConstruct/@PreDestroy
  • ContextStartedEvent/ContextClosedEvent

PS: actually, there is another way to inherit the Spring Lifecycle interface. But this way is more complicated, so we will not analyze it here.

2.1,init-method/destroy-method

This way, the initialization / destruction method is specified in the configuration file. The XML configuration is as follows

<bean id="demoService" class="com.dubbo.example.provider.DemoServiceImpl"  destroy-method="close"  init-method="initMethod"/>

Or you can use annotation to configure:

@Configurable
public class AppConfig {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public HelloService hello() {
        return new HelloService();
    }
}

Remember that you just started to learn about the Spring framework, and that's how you use it.

2.2,InitializingBean/DisposableBean

This method needs to inherit the Spring interface InitializingBean/DisposableBean, where InitializingBean is used to initialize actions and DisposableBean is used to clean up actions before destroying. Use as follows:

@Service
public class HelloService implements InitializingBean, DisposableBean {
    
    @Override
    public void destroy() throws Exception {
        System.out.println("hello destroy...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("hello init....");
    }
}

2.3,@PostConstruct/@PreDestroy

Compared with the above two methods, this method is the easiest to use. You only need to use annotations on the corresponding methods. Use as follows:

@Service
public class HelloService {


    @PostConstruct
    public void init() {
        System.out.println("hello @PostConstruct");
    }

    @PreDestroy
    public void PreDestroy() {
        System.out.println("hello @PreDestroy");
    }
}

If you use the version after JDK9, @ PostConstruct/@PreDestroy needs to use maven to introduce javax.annotation-api separately, otherwise the annotation will not take effect.

2.4,ContextStartedEvent/ContextClosedEvent

In this way, Spring event mechanism is used, and daily business development is rare, which is often integrated with framework. The ContextStartedEvent event will be sent after Spring starts, and the ContextClosedEvent event will be sent before Spring closes. We need to inherit Spring ApplicationListener to listen to the above two events.

@Service
public class HelloListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ContextClosedEvent){
            System.out.println("hello ContextClosedEvent");
        }else if(event instanceof ContextStartedEvent){
            System.out.println("hello ContextStartedEvent");
        }

    }
}

You can also use the @ EventListener annotation as follows:

public class HelloListenerV2 {
    
    @EventListener(value = {ContextClosedEvent.class, ContextStartedEvent.class})
    public void receiveEvents(ApplicationEvent event) {
        if (event instanceof ContextClosedEvent) {
            System.out.println("hello ContextClosedEvent");
        } else if (event instanceof ContextStartedEvent) {
            System.out.println("hello ContextStartedEvent");
        }
    }
}

PS: ContextStartedEvent will be sent only when ApplicationContext is called. If you don't want to be so troublesome, you can listen to the ContextRefreshedEvent event instead. Once the Spring container is initialized, a ContextRefreshedEvent is sent.

III. comprehensive use

After reviewing the above methods, here we use the above four methods to see the internal processing order of Spring. Before looking at the results, you can guess the execution order of these methods.

public class HelloService implements InitializingBean, DisposableBean {


    @PostConstruct
    public void init() {
        System.out.println("hello @PostConstruct");
    }

    @PreDestroy
    public void PreDestroy() {
        System.out.println("hello @PreDestroy");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("bye DisposableBean...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("hello InitializingBean....");
    }

    public void xmlinit(){
        System.out.println("hello xml-init...");
    }

    public void xmlDestory(){
        System.out.println("bye xmlDestory...");
    }

    @EventListener(value = {ContextClosedEvent.class, ContextStartedEvent.class})
    public void receiveEvents(ApplicationEvent event) {
        if (event instanceof ContextClosedEvent) {
            System.out.println("bye ContextClosedEvent");
        } else if (event instanceof ContextStartedEvent) {
            System.out.println("hello ContextStartedEvent");
        }
    }

}

xml is configured as follows:

    <context:annotation-config />
    <context:component-scan base-package="com.dubbo.example.demo"/>
    
    <bean class="com.dubbo.example.demo.HelloService" init-method="xmlinit" destroy-method="xmlDestory"/>

The application startup method is as follows:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
context.close();

The program output is as follows:

Finally, the above results are summarized by illustration

Source code analysis

I don't know if you have guessed the execution order of these methods. Let's analyze the internal processing order of Spring from the source point of view.

4.1 initialization process

Starting the Spring container with ClassPathXmlApplicationContext will call the refresh method to initialize the container. The initialization process creates the Bean. Finally, when everything is ready, the ContextRefreshedEvent will be sent. When the container is initialized, call context.start() to send the ContextStartedEvent event.

The source code of the refresh method is as follows:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
            //... ignore irrelevant code

            // Initialize all non deferred initialized beans
            finishBeanFactoryInitialization(beanFactory);

            // Send ContextRefreshedEvent
            finishRefresh();

            //... ignore irrelevant code
    }
}

Trace the finishBeanFactoryInitialization source code all the way to abstractautowirecapablebeanfactory ා initializebean. The source code is as follows:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // Call beanpostprocessor ා postprocessbeforeinitialization method
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // Initialize Bean
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
                (mbd != null ? mbd.getResourceDescription() : null),
                beanName, "Invocation of init method failed", ex);
    }
}

BeanPostProcessor will act as an interceptor, and once the Bean meets the conditions, it will perform some processing. The beans with @ PostConstruct annotation here will be blocked by the CommonAnnotationBeanPostProcessor class, and the @ PostConstruct annotation method will be triggered internally.

Then execute invokeInitMethods as follows:

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
        throws Throwable {

    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        // Omit irrelevant code
        // If the Bean inherits the InitializingBean, the afterpropertieset method will be executed
        ((InitializingBean) bean).afterPropertiesSet();
    }

    if (mbd != null) {
        String initMethodName = mbd.getInitMethodName();
        if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                !mbd.isExternallyManagedInitMethod(initMethodName)) {
            // Execute XML definition init method
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

If the Bean inherits the InitializingBean interface, the afterpropertieset method will be executed, and if init method is specified in the XML, it will also be triggered.

The above source code is actually around the process of Bean creation. When all beans are created, calling context ා start will send ContextStartedEvent. The source code here is relatively simple, as follows:

public void start() {
    getLifecycleProcessor().start();
    publishEvent(new ContextStartedEvent(this));
}

4.2 destruction process

Calling classpathxmlapplicationcontext? Method will close the container, and the specific logic will be executed in the doClose method.

The doClose method first sends the ContextClosedEvent, and then starts to destroy the Bean.

Soul torture: if we reverse the order of the two above, will the result be the same?

The doClose source code is as follows:

protected void doClose() {
    if (this.active.get() && this.closed.compareAndSet(false, true)) {
        // Omit irrelevant code

        try {
            // Publish shutdown event.
            publishEvent(new ContextClosedEvent(this));
        }
        catch (Throwable ex) {
            logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
        }


        // Destroy Bean
        destroyBeans();

        // Omit irrelevant code
    }
}

destroyBeans will finally execute the disposablebeanadapter ා destroy, @ PreDestroy, DisposableBean and destroy method will all be executed internally.

First, execute the destructionawarebeanpostprocessor ා postprocessbeforedestruction. Here, the method is similar to the above BeanPostProcessor.

@The PreDestroy annotation will be blocked by CommonAnnotationBeanPostProcessor, where the class also inherits the DestructionAwareBeanPostProcessor.

Finally, if the Bean is a subclass of DisposableBean, the destroy method will be executed. If the destroy method is defined in xml, the method will also be executed.

public void destroy() {
    if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
        for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
            processor.postProcessBeforeDestruction(this.bean, this.beanName);
        }
    }

    if (this.invokeDisposableBean) {
        // Omit irrelevant code
        // If Bean inherits DisposableBean, execute destroy method
        ((DisposableBean) bean).destroy();
        
    }

    if (this.destroyMethod != null) {
        // Execute the destroy method specified by xml
        invokeCustomDestroyMethod(this.destroyMethod);
    }
    else if (this.destroyMethodName != null) {
        Method methodToCall = determineDestroyMethod();
        if (methodToCall != null) {
            invokeCustomDestroyMethod(methodToCall);
        }
    }
}

Five, summary

Init method / destroy method requires XML configuration file or separate annotation for configuration class, which is relatively cumbersome. InitializingBean/DisposableBean needs to inherit Spring's interface implementation methods separately. @PostConstruct/@PreDestroy is a simple and clear annotation method, which is recommended.

In addition, ContextStartedEvent/ContextClosedEvent is more suitable for use in some integration frameworks, such as Dubbo 2.6.X elegant downtime is the use of change mechanism.

Vi. recommendation of Spring historical articles

1. Annotation attribute aliases and overrides in Spring annotation programming
2. Annotation metadata of Spring annotation programming
3. Mode annotation of Spring annotation programming
4. The origin of Dubbo and the extension mechanism of Spring XML Schema

Welcome to pay attention to my public number: procedure, get daily dry goods push. If you are interested in my topic content, you can also follow my blog: studyidea.cn

Topics: Java Spring xml Dubbo