🙈Застенчивый, у инициализации/уничтожения Spring Bean так много жестов

Java

Источник статьи:1t.click/bfHN

Введение

Ежедневный процесс разработки иногда требует загрузки определенных ресурсов после запуска приложения или освобождения ресурсов до закрытия приложения. Spring Framework предоставляет соответствующие функции дляSpring Beanжизненный цикл, который может бытьBeanПроцесс создания инициализирует ресурсы и уничтожает ихBeanПроцесс освобождает ресурсы. Spring предоставляет множество различных способов инициализации/уничтоженияBean, если вы используете эти методы одновременно, как Spring обрабатывает порядок между ними?

Как вы думаете, название очень знакомо, да название подражает второму брату"@Король Тишины 2" статьяПозор, есть так много поз для сращивания строк Java.

2. Анализ позы

Во-первых, давайте рассмотрим инициализацию/уничтожение SpringBeanНесколько способов, а именно:

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

PS: На самом деле есть еще один способ - наследовать SpringLifecycleинтерфейс. Однако этот метод более сложен и здесь рассматриваться не будет.

2.1, метод инициализации/метод уничтожения

Таким образом, методы инициализации/уничтожения указываются в файле конфигурации. Конфигурация XML выглядит следующим образом

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

Или вы можете настроить его с помощью аннотаций:

@Configurable
public class AppConfig {

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

Я до сих пор помню, когда впервые начал изучать среду Spring, вот как я ее использовал.

2.2, InitializingBean/DisposableBean

Этот метод должен наследовать интерфейс Spring.InitializingBean/DisposableBeanInitializingBeanиспользуется для инициализации действия, аDisposableBeanИспользуется для действий по очистке перед уничтожением. Он используется следующим образом:

@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

По сравнению с двумя вышеупомянутыми методами этот метод является самым простым в использовании, и вам нужно использовать только аннотации для соответствующих методов. Он используется следующим образом:

@Service
public class HelloService {


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

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

Я наступил на яму здесь.Если вы используете версию после JDK9,@PostConstruct/@PreDestroyНеобходимо импортировать отдельно с помощью mavenjavax.annotation-api, иначе аннотация не вступит в силу.

2.4, ContextStartedEvent/ContextClosedEvent

Этот метод использует механизм событий Spring, который относительно редко используется в повседневной разработке бизнеса и часто интегрируется с инфраструктурой. Будет отправлен после начала весныContextStartedEventсобытие, которое будет отправлено перед закрытиемContextClosedEventсобытие. Нам нужно унаследовать веснуApplicationListenerдля наблюдения за двумя вышеуказанными событиями.

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

    }
}

также можно использовать@EventListenerАннотация используется следующим образом:

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

пс: только звонитьApplicationContext#startбудет отправленContextStartedEvent. Если вы не хотите быть таким хлопотным, вы можете контролироватьContextRefreshedEventсобытие вместо этого. Как только контейнер Spring инициализирован, он отправляетсяContextRefreshedEvent.

3. Комплексное использование

После рассмотрения вышеуказанных методов здесь мы всесторонне используем четыре вышеупомянутых метода, чтобы увидеть внутренний порядок обработки Spring. Перед просмотром результатов читатели и взрослые могут угадать порядок выполнения этих методов.

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 выглядит следующим образом:

    <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"/>

Способ запуска приложения следующий:

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

Вывод программы следующий:

Наконец, графическая иллюстрация используется для обобщения вышеприведенных результатов:

Четыре, анализ исходного кода

Не знаю, угадали ли читатели порядок выполнения этих методов, давайте проанализируем порядок внутренней обработки в Spring с точки зрения исходного кода.

4.1 Процесс инициализации

использоватьClassPathXmlApplicationContextЗапустите контейнер Spring, который вызоветrefreshметод инициализации контейнера. Процесс инициализации создастBean. Наконец, когда все будет готово, отправлюContextRefreshedEvent. Когда контейнер инициализирован, вызовитеcontext.start()просто отправьContextStartedEventсобытие.

refreshИсходный код метода выглядит следующим образом:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
            //... 忽略无关代码

            // 初始化所有非延迟初始化的 Bean
            finishBeanFactoryInitialization(beanFactory);

            // 发送 ContextRefreshedEvent
            finishRefresh();

            //... 忽略无关代码
    }
}

Следуйте до концаfinishBeanFactoryInitializationисходный код доAbstractAutowireCapableBeanFactory#initializeBean, исходный код выглядит следующим образом:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 调用 BeanPostProcessor#postProcessBeforeInitialization 方法
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

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

BeanPostProcessorБудет действовать как перехватчик, как только bean-компонент будет подходящим, будет выполнена некоторая обработка. здесь с@PostConstructаннотированныйBeanбудетCommonAnnotationBeanPostProcessorПерехват класса, который будет запущен внутри@PostConstructспособ маркировки.

Затем выполнитеinvokeInitMethods, Методы, как показано ниже:

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

    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        // 省略无关代码
        // 如果是 Bean 继承 InitializingBean,将会执行  afterPropertiesSet 方法
        ((InitializingBean) bean).afterPropertiesSet();
    }

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

еслиBeanнаследоватьInitializingBeanинтерфейс, который будет выполнятьafterPropertiesSetметод, и если он указан в XMLinit-method, также сработает.

Приведенный выше исходный код фактически вращается вокругBeanпроцесс создания, когда всеBeanПосле завершения создания позвонитеcontext#startпошлетContextStartedEvent. Исходный код здесь относительно прост:

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

4.2. Процесс уничтожения

перечислитьClassPathXmlApplicationContext#closeметод закроет контейнер, конкретная логика будет вdoCloseметод выполняется.

doCloseЭтот метод сначала отправляетContextClosedEvent, а затем начать уничтожатьBean.

Душевная пытка: если мы поменяем порядок двух предыдущих, будет ли результат таким же?

doCloseИсходный код выглядит следующим образом:

protected void doClose() {
    if (this.active.get() && this.closed.compareAndSet(false, true)) {
        // 省略无关代码

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


        // 销毁 Bean
        destroyBeans();

        // 省略无关代码
    }
}

destroyBeansв конечном итоге выполнитDisposableBeanAdapter#destroy,@PreDestroy,DisposableBean,destroy-methodМетоды, определенные этими тремя, будут выполняться внутри.

сначала выполнитьDestructionAwareBeanPostProcessor#postProcessBeforeDestruction, этот метод аналогичен предыдущемуBeanPostProcessor.

@PreDestroyАннотации будутCommonAnnotationBeanPostProcessorIntercept, этот класс также наследуетDestructionAwareBeanPostProcessor.

Наконец, еслиBeanзаDisposableBeanПодкласс , будет выполнятьdestroyметод, если он определен в xmldestroy-methodметод, который также будет выполнен.

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

    if (this.invokeDisposableBean) {
        // 省略无关代码
        // 如果 Bean 继承 DisposableBean,执行 destroy 方法
        ((DisposableBean) bean).destroy();
        
    }

    if (this.destroyMethod != null) {
        // 执行 xml 指定的  destroy-method 方法
        invokeCustomDestroyMethod(this.destroyMethod);
    }
    else if (this.destroyMethodName != null) {
        Method methodToCall = determineDestroyMethod();
        if (methodToCall != null) {
            invokeCustomDestroyMethod(methodToCall);
        }
    }
}

V. Резюме

init-method/destroy-methodЭтот метод требует использования файлов конфигурации XML или отдельных классов конфигурации аннотаций, что является относительно громоздким. иInitializingBean/DisposableBeanЭтот метод требует отдельного наследования интерфейса Spring для реализации связанных методов.@PostConstruct/@PreDestroyЭтот метод аннотации прост в использовании, код понятен, рекомендуется использовать этот метод.

Кроме тогоContextStartedEvent/ContextClosedEventЭтот метод больше подходит для использования в некоторых средах интеграции, таких как корректное завершение работы Dubbo 2.6.X для изменения механизма.

Шесть, весенняя рекомендация по исторической статье

1. Псевдонимы атрибутов аннотаций и переопределения программирования аннотаций Spring
2. AnnotationMetadata программирования аннотаций Spring
3, аннотация режима программирования аннотаций Spring
4. Происхождение Dubbo, рассказ о механизме расширения Spring XML Schema

Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: программа для общения, ежедневный толчок галантерейных товаров. Если вам интересен мой рекомендуемый контент, вы также можете подписаться на мой блог:studyidea.cn

其他平台.png