【Spring】Bean生命週期以及應用場景(註解版)

前言

理清Spring容器管理Bean的過程有助於我們更好地根據需求制定更合理的設計方案,提升系統擴展性和執行效率。

Bean生命週期

下面通過簡單的示例來理清Bean在Spring容器中的生命週期。

我們從整體到細節,首先是啓動容器加載Bean,這是一個整體的過程。

/**
 * 測試 Spring Bean 生命週期
 * @author zyj
 */
public class LifeCycleTest {
    @Test
    public void testLifeCycle() {
        // 通過註解開啓容器,並加載Bean
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(GlobalConfig.class);
        // 關閉容器
        context.close();
    }
}

@Configuration
public class GlobalConfig {
    @Bean
    public Calculator calculator() {
        return new Calculator();
    }
    /**
     * @Order:加載到容器的優先級,越小代表優先級越高
     * @return
     */
    @Order(0)
    @Bean
    public FullyBeanPostProcessor fullyBeanPostProcessor() {
        return new FullyBeanPostProcessor();
    }
}

接着看看容器在加載Bean的時候都做了哪些處理。

我們需要對Bean加入Spring提供的接口,這些接口可以看做是給開發者干預加載Bean的過程的入口。

public class Calculator implements BeanNameAware, ApplicationContextAware {

    public Calculator() {
        System.out.println("<<<構造器執行>>>");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("<<<BeanNameAware.setBeanName執行>>>");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("<<<ApplicationContextAware.setApplicationContext執行>>>");
    }

    @PostConstruct
    public void init() {
        System.out.println("<<<@PostConstruct執行>>>");
    }

	@PreDestroy
    public void destroy() {
        System.out.println("<<<@PreDestroy執行>>>");
    }
}

從代碼可以看到這裏有以下幾個接口以及註解:

  • BeanNameAware:獲取當前Bean在容器中的名字
  • ApplicationContextAware:爲當前Bean注入容器
  • @PostConstruct:初始化方法(相當於Spring配置文件中的init-method、接口InitializingBean
  • @PreDestroy:銷燬方法(相當於Spring配置文件中的destroy-method、接口DisposableBean

執行順序

三月 06, 2020 3:09:52 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f47d241: startup date [Fri Mar 06 15:09:52 CST 2020]; root of context hierarchy
三月 06, 2020 3:09:52 下午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
信息: Bean 'globalConfig' of type [class com.zyj.spring.config.GlobalConfig$$EnhancerBySpringCGLIB$$5aa1013d] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

<<<構造器執行>>>
<<<BeanNameAware.setBeanName執行>>>
<<<ApplicationContextAware.setApplicationContext執行>>>
<<<BeanPostProcessor.postProcessBeforeInitialization執行>>>
<<<@PostConstruct執行>>>
<<<BeanPostProcessor.postProcessAfterInitialization執行>>>

三月 06, 2020 3:09:52 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f47d241: startup date [Fri Mar 06 15:09:52 CST 2020]; root of context hierarchy

<<<@PreDestroy執行>>>

Process finished with exit code 0

細心的小夥伴肯定注意到了BeanPostProcessor的執行,我在Calculator中沒有實現該接口,爲什麼加載的時候會打印呢?

一開始我理所應當地認爲應該由每個Bean來實現BeanPostProcessor,這樣就可以在@PostConstruct執行前後進行處理了。

但是我錯了,驗證的結果就是在加載當前Bean的時候並沒有執行它所實現的BeanPostProcessor

在找了一些資料之後,才瞭解BeanPostProcessor的用法。

請看上面的配置類,你可能漏掉了GlobalConfig中的FullyBeanPostProcessor

public class FullyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Calculator) {
            System.out.println("<<<BeanPostProcessor.postProcessBeforeInitialization執行>>>");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Calculator) {
            System.out.println("<<<BeanPostProcessor.postProcessAfterInitialization執行>>>");
        }
        return bean;
    }
}

BeanPostProcessor由單獨一個類來執行,統一由這個類來判斷對哪些Bean執行哪些處理。

應用場景

ApplicationContextAware

作用:通過它,可以獲取到Spring容器

應用場景:當實例並沒有交給Spring管理時,我們就不能使用自動注入了。比如Utils使用Dao,這個時候就可以通過一個持有Spring容器的組件來獲取Dao。

@Component
public class SpringJobBeanFactory implements ApplicationContextAware {

    private static ApplicationContext applicationContext;
        
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringJobBeanFactory.applicationContext=applicationContext;    
    }
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
    }
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
		if (applicationContext == null){
			return null;
		}
		return (T)applicationContext.getBean(name);
	}
}

使用:

TypeDao typeDao = SpringJobBeanFactory.getBean("typeDao");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章