??????????????《RabbitMQ》《Spring》《SpringMVC》
前言
前面我們講訴了將Bean正確地裝配到IoC容器,卻未講訴IoC如何裝配和銷毀Bean。本篇文章主要講訴一下Bean的生命周期和作用域。
一、生命周期
Bean 的生命周期的過程, 它大致分為Bean定義、Bean 的初始化、 Bean 的生存期和 Bean 的銷毀4個部分。 其中 Bean 定義過程大致如下:
- Spring 通過我們的配置,如@ComponentScan 定義的掃描路徑去找到帶有@Component 的類,
這個過程就是一個資源定位的過程。- 一旦找到了資源,那么它就開始解析,并且將定義的信息保存起來。注意,此時還沒有初始
化Bean,也就沒有Bean 的實例,它有的僅僅是Bean 的定義。- 然后就會把Bean 定義發(fā)布到 Spring IoC 容器中。 此時, IoC 容器也只有Bean 的定義,還是
沒有Bean 的實例生成。完成了這3 步只是一個資源定位并將Bean 的定義發(fā)布到IoC容器的過程,還沒有Bean實例的生成,更沒有完成依賴注入。在默認的情況下, Spring會繼續(xù)去完成Bean 的實例化和依賴注入,這樣從IoC 容器中就可以得到一個依賴注入完成的Bean。 但是,有些Bean會受到變化因素的影響,這時我們倒希望是取出 Bean 的時候完成初始化和依賴注入,換句話說就是讓那些 Bean 只是將定義發(fā)布到IoC 容器而不做實例化和依賴注入, 當我們取出來的時候才做初始化和依賴注入等操作。
Spring Bean的初始化過程:
ComponentScan 中還有一個配置項 lazyI nit,只可以配置 Boolean 值,且默認值為 false,也就是默認不進行延遲初始化,因此在默認的情況下Spring會對Bean進行實例化和依賴注入對應的屬性值。
引入例子:人類(Person)有時候利用一些動物(Animal)去完成一些事情,比方說狗(Dog)是用來看門的,貓(Cat)是用來抓老鼠的.。
代碼如下:
//定義人類接口
public interface Person {
void service();
void setAnimal(Animal animal);
}
//定義動物接口
public interface Animal {
void user();
}
//定義狗
@Component
public class Dog implements Animal {
@Override
public void user() {
System.out.println("狗【" + Dog.class.getSimpleName() + "】是用來看門的");
}
}
//定義年輕人
@Component
public class YoungPerson implements Person {
@Autowired
private Animal animal = null;
@Override
public void service() {
this.animal.user();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
//定義貓
@Component
public class Cat implements Animal{
@Override
public void user() {
System.out.println("貓【" + Cat.class.getSimpleName() + "】是抓老鼠的");
}
}
//定義配置類
@Configuration
@ComponentScan("com.dragon.restart")//所有的包和類都在restart下
public class AppConfig {
}
此時沒有配置lazyInit的情況進行斷點測試如下:
可以看到在斷點處,我們并沒有獲取Bean 的實例,而日志就已經(jīng)打出了,可見它是在SpringIoC容器初
始化時就執(zhí)行了實例化和依賴注入。為了改變這個情況,我們在配置類AppConfig的@ComponentScan
中加入lazylnit 配置,如下面的代碼:
@Configuration
@ComponentScan(value = "com.dragon.restart",lazyInit = true)
public class AppConfig {
}
就可以發(fā)現(xiàn)在斷點處“延遲依賴注入”這行并不會出現(xiàn)在日志中,只有運行過斷點處才會出現(xiàn)這行日志,這是因為我們把它修改為了延遲初始化, Spring并不會在發(fā)布Bean定義后馬上為我們完成實例化和依賴注入。
如果僅僅是實例化和依賴注入還是比較簡單的,還不能完成進行自定義的要求。 為了完成依賴注入的功能, Spring 在完成依賴注入之后,還提供了一系列的接口和配置來完成Bean初始化的過程,讓我們學習這個過程。 Spring在完成依賴注入后,還會進行如下圖所示流程來完成它的生命周期:
圖中描述的是整個IoC容器初始化Bean 的流程,作為開發(fā)者,需要注意這些流程。除此之外,還需要注意以下兩點:
- 這些接口和方法是針對什么而言的。 對于上圖, 在沒有注釋的情況下的流程節(jié)點都是針對單個Bean 而言的,但是BeanPostProcessor 是針對所有 Bean 而言的,這是我們需要注意的地方。
- 即使你定義了 ApplicationContextAware 接口,但是有時候并不會調用,這要根據(jù)你的 IoC 容器來決定。 我們知道, Spring IoC 容器最低的要求是實現(xiàn) BeanFactory 接口,而不是實現(xiàn)ApplicationContext 接口 。 對于那些沒有實現(xiàn) ApplicationContext 接口的容器,在生命周期對應的ApplicationContextAware 定義的方法也是不會被調用的,只有實現(xiàn)了 ApplicationContext 接口的容器,才會在生命周期調用 ApplicationContextAware 所定義的 setApplicationContext方法。
現(xiàn)在改造一下YoungPerson類:
@Component
public class YoungPerson implements Person, BeanNameAware , BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
private Animal animal = null;
@Override
public void service() {
this.animal.user();
}
@Autowired
@Qualifier("dog")
@Override
public void setAnimal(Animal animal) {
System.out.println("延遲依賴注入");
this.animal = animal;
}
@Override
public void setBeanName(String name) {
System.out.println ("【" + this.getClass().getSimpleName() + "】調用BeanNameAware的setBeanName");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println ("【" + this.getClass().getSimpleName() + "】調用BeanFactoryAware的setBeanFactory");
}
@Override
public void destroy() throws Exception {
System.out.println ("【" + this.getClass().getSimpleName() + "】調用DisposableBean方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println ("【" + this.getClass().getSimpleName() + "】調用InitializingBean方法的afterPropertiesSet方法");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println ("【" + this.getClass().getSimpleName() + "】調用ApplicationContextAware方法的setApplicationContext方法");
}
@PostConstruct
public void init () {
System.out.println("【" + this.getClass().getSimpleName() + "】注解@PostConstruct定義的自定義初始化方法");
}
@PreDestroy
public void destroyl () {
System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定義的自定義銷毀方法");
}
}
這樣,這個 B巳an 就實現(xiàn)了生命周期中單個 Bean 可以實現(xiàn)的所有接口, 并且通過注解@PostConstruct 定義了初始化方法,通過注解@PreDestroy 定義了銷毀方法。 為了測試 Bean 的后置處理器, 這里創(chuàng)建一個類BeanPostProcessorExampIe,如下:
/**
* @Version: 1.0.0
* @Author: Dragon_王
* @ClassName: BeanPostProcessorExample
* @Description: TODO描述
* @Date: 2024/1/20 23:34
*/
public class BeanPostProcessorExample implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor調用"+
"postProcessBeforeinitialization方法,參數(shù)【"+
bean.getClass().getSimpleName()+"】【"+beanName+"】");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor調用"+
"postProcessAfterinitialization方法,參數(shù)【"+
bean.getClass().getSimpleName()+"】【"+beanName+"】");
return bean;
}
}
注意,這個Bean后置處理器將對所有的Bean有效,運行測試如下:
測試類:
AnnotationConfigApplicationContext ctx =new AnnotationConfigApplicationContext(AppConfig.class) ;
ctx.close();
2024-01-20T23:43:23.135+08:00 INFO 748 --- [ main] c.d.restart.RestartApplicationTests : Starting RestartApplicationTests using Java 19 with PID 748 (started by ThundeRobot in E:\IDEA_projects\restart)
2024-01-20T23:43:23.136+08:00 INFO 748 --- [ main] c.d.restart.RestartApplicationTests : No active profile set, falling back to 1 default profile: "default"
BeanPostProcessor調用postProcessBeforeinitialization方法,參數(shù)【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor調用postProcessAfterinitialization方法,參數(shù)【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor調用postProcessBeforeinitialization方法,參數(shù)【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor調用postProcessAfterinitialization方法,參數(shù)【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor調用postProcessBeforeinitialization方法,參數(shù)【Cat】【cat】
BeanPostProcessor調用postProcessAfterinitialization方法,參數(shù)【Cat】【cat】
BeanPostProcessor調用postProcessBeforeinitialization方法,參數(shù)【Dog】【dog】
BeanPostProcessor調用postProcessAfterinitialization方法,參數(shù)【Dog】【dog】
延遲依賴注入
【YoungPerson】調用BeanNameAware的setBeanName
【YoungPerson】調用BeanFactoryAware的setBeanFactory
【YoungPerson】調用ApplicationContextAware方法的setApplicationContext方法
BeanPostProcessor調用postProcessBeforeinitialization方法,參數(shù)【YoungPerson】【youngPerson】
【YoungPerson】注解@PostConstruct定義的自定義初始化方法
【YoungPerson】調用InitializingBean方法的afterPropertiesSet方法
BeanPostProcessor調用postProcessAfterinitialization方法,參數(shù)【YoungPerson】【youngPerson】
BeanPostProcessor 調用 postProcessBeforeinitialization 方法,參數(shù) 【Cat】【cat】
BeanPostProcessor 調用 postProcessAfterinitialization 方法, 參數(shù) 【Cat】【cat】
2024-01-20T23:43:24.044+08:00 INFO 748 --- [main] c.d.restart.RestartApplicationTests : Started RestartApplicationTests in 1.142 seconds (process running for 1.772)
【YoungPerson】注解@PreDestroy定義的自定義銷毀方法
【YoungPerson】調用DisposableBean方法
從日志可以看出,對于Bean后置處理器(BeanPostProcessor)而言, 它對所有的 Bean 都起作用,而其他的接口則是對于單個Bean起作用。我們還可以注意到BussinessPerson執(zhí)行的流程是上圖所畫出的流程。有時候Bean 的定義可能使用的是第三方的類,此時可以使用注解@Bean來配置自定義初始化和銷毀方法,如下所示:
@Bean(InitMethod =”Init”, destroyMethod = ”destroy” )
二、作用域
在介紹IoC 容器最頂級接口 BeanFactory 的時候, 可以看到 isSingleton 和 isPrototype 兩個方法。其中,isSingleton 方法如果返回 true,則 Bean 在 loC 容器中以單例存在,這也是 Spring IoC 容器的默認值;如果 isPrototype 方法返回 true,則當我們每次獲取 Bean 的時候, IoC 容器都會創(chuàng)建一個新的 Bean,這顯然存在很大的不同,這便是Spring Bean 的作用域的問題。在一般的容器中, Bean都會存在單例(Singleton)和原型(Prototype)兩種作用域, Java EE 廣泛地使用在互聯(lián)網(wǎng)中,而在 Web容器中, 則存在頁面(page)、請求(request)、會話 (session)和應用(application) 4 種作用域。對于頁面(page),是針對 JSP 當前頁面的作用域,所以 Spring是無法支持的。為了滿足各類的作用域,在Spring 的作用域中就存在如表所示的幾種類型。
作用域類型 | 使用范圍 | 作用域描述 |
---|---|---|
singleton | 所有Spring 應用 | 默認值, loC 容器只存在單例 |
prototype | 所有Spring 應用 | 每當從IoC 容器中取出一個 Bean,則創(chuàng)建一個新的Bean |
session | Spring Web 應用 | HTTP 會話 |
application | Spring Web 應用 | Web 工程生命周期 |
request | Spring Web 應用 | Web 工程單次請求 (request) |
globalSession | Spring Web 應用 | 在一個全局的HTTPSession 中, 一個 Bean 定義對應一個實例。 實踐中基本不使用 |
- 前四個最常用
- 對于application作用域,完全可以使用單例來替代。
下面我們探討單例 (Singleton)和原型(prototype)的區(qū)別
首先定義一個類
@Component
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean { }
這是一個簡單的類, 可以看到這里聲明作用域的代碼已經(jīng)被注釋掉了, 這樣就是啟用默認的作用域,實際就是單例。為了證明作用域的存在,我們進行一下測試:
AnnotationConfigApplicationContext ctx
=new AnnotationConfigApplicationContext (AppConfig.class);
ScopeBean scopeBeanl = ctx.getBean (ScopeBean.class);
ScopeBean scopeBean2 = ctx.getBean (ScopeBean .class);
System.out.println (scopeBeanl == scopeBean2) ;
從測試的結果來看,顯然scopeBeanl 和 scopeBean2 這兩個變量都指向了同一的實例,所以在IoC容器中, 只有一個ScopeBean 的實例。 然后取消代碼中作用域代碼的注釋,進行同樣的測試, 則可以看到scopeBeanl == scopeBean2 返回的將是 false,而不再是 true, 那是因為我們將Bean 的作用域修改為了 prototype,這樣就能讓IoC 容器在每次獲取Bean 時,都新建一個Bean的實例返回給調用者。
這里的 ConfigurableBeanFactory 只能提供單例 ( SCOPE_ SINGLETON )和原型 ( SCOPE_PROTOTYPE)兩種作用域供選擇, 如果是在 SpringMVC環(huán)境中,還可以使用 WebApplicationContext去定義其他作用域, 如請求(SCOPE REQUEST)、 會話 (SCOPE_SESSION) 和應用 (SCOPE_APPLICATION)。 例如,下面的代碼就是定義請求作用域:文章來源:http://www.zghlxwxcb.cn/news/detail-811639.html
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class ScopeBean { }
總結
以上就是Bean生命周期和作用域的講解。文章來源地址http://www.zghlxwxcb.cn/news/detail-811639.html
到了這里,關于SpringBoot:詳解Bean生命周期和作用域的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!