Spring文檔筆記二(bean的範圍和自定義bean)
bean範圍
表格 1_.Bean 範圍
範圍 | 描述 |
---|---|
[singleton](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-custom) | (默認)根據 Spring IoC 容器將單個 bean 定義範圍限定爲單個 object 實例。 |
[原型](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-singleton)(prototype) | 爲任意數量的 object 實例定義單個 bean 定義。 |
[請求](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-prototype) | 將單個 bean 定義範圍限定爲單個 HTTP 請求的生命週期;也就是說,每個 HTTP 請求都有自己的 bean 實例,它是在單個 bean 定義的後面創建的。僅在 web-aware Spring ApplicationContext 的 context 中有效。 |
[session](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-request) | 將單個 bean 定義範圍限定爲 HTTP Session 的生命週期。僅在 web-aware Spring ApplicationContext 的 context 中有效。 |
[應用](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-global-session) | 將單個 bean 定義範圍限定爲ServletContext 的生命週期。僅在 web-aware Spring ApplicationContext 的 context 中有效。 |
[WebSocket](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/websocket.html#beans-factory-scopes-application) | 將單個 bean 定義範圍限定爲WebSocket 的生命週期。僅在 web-aware Spring ApplicationContext 的 context 中有效。 |
singleton 範圍
只管理 singleton bean 的一個共享實例,並且對具有與該 bean 定義匹配的 id 或 id 的 beans 的所有請求都會導致 Spring 容器返回一個特定的 bean 實例。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-C8d0PTf7-1576661816524)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191110121020116.png)]
原型範圍(prototype)
每次對特定bean提出請求時,bean部署的非單一原型範圍都會導致創建一個新bean實例。也就是說,該Bean被注入到另一個Bean中,或者您通過getBean()
容器上的方法調用來請求它。通常,應將原型作用域用於所有有狀態Bean,將單例作用域用於無狀態Bean。
下圖說明了Spring原型範圍:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qtUR6LCs-1576661816525)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191110121111914.png)]
以下示例將bean定義爲XML中的原型:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
與其他作用域相比,Spring不能管理原型(prototype)Bean的完整生命週期。容器實例化,配置或組裝原型對象,然後將其交給客戶端,而無需對該原型實例的進一步記錄。因此,儘管在不考慮作用域的情況下在所有對象上都調用了初始化生命週期回調方法,但在原型的情況下,不會調用已配置的銷燬生命週期回調。客戶端代碼必須清除原型作用域內的對象並釋放原型Bean擁有的昂貴資源。爲了使Spring容器釋放原型作用下的bean所擁有的資源,請嘗試使用自定義bean後處理器,其中包含對需要清理的bean的引用。
在某些方面,Spring容器在原型作用域bean方面的角色是Java new
運算符的替代。超過該時間點的所有生命週期管理必須由客戶端處理。(有關Spring容器中bean生命週期的詳細信息,請參閱Lifecycle Callbacks。)
request,session,application和WebSocket範圍
在request
,session
,application
,和websocket
範圍只有當你使用一個基於web的SpringApplicationContext
可實現(例如 XmlWebApplicationContext
)。如果您將這些作用域與常規的Spring IoC容器(例如ClassPathXmlApplicationContext
)一起使用,則會拋出IllegalStateException
抱怨未知bean作用域的。
初始Web配置
爲了支持豆的範圍界定在request
,session
,application
,和 websocket
(即具有web作用域bean),定義你的bean之前需要做少量的初始配置。(對於標準示波器,不需要此初始設置:singleton
和prototype
。)
如何完成此初始設置取決於您的特定Servlet環境。
如果您在Spring Web MVC中訪問作用域化的bean,實際上是在Spring處理的請求中,則DispatcherServlet
不需要特殊的設置。 DispatcherServlet
已經公開了所有相關狀態。
request範圍
考慮以下XML配置來定義bean:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring容器LoginAction
通過loginAction
爲每個HTTP請求使用bean定義來創建bean 的新實例。也就是說, loginAction
bean的作用域位於HTTP請求級別。您可以根據需要更改創建實例的內部狀態,因爲從同一loginAction
bean定義創建的其他實例看不到這些狀態變化。它們特定於單個請求。當請求完成處理時,將限制作用於該請求的Bean。
使用註釋驅動的組件或Java配置時,@RequestScope
可以使用註釋將組件分配給合併request
範圍。以下示例顯示瞭如何執行此操作:
@RequestScope
@Component
public class LoginAction {
// ...
}
session範圍
考慮以下XML配置來定義bean:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
基本介紹和request範圍一致 生命週期不同
@SessionScope
@Component
public class UserPreferences {
// ...
}
application 範圍
考慮以下XML配置來定義bean:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
在應用程序範圍內,容器爲每個web
應用程序運行時創建一個實例。它幾乎類似於單例範圍,只有兩個不同之處。即:
-
應用程序作用域
bean
是每個ServletContext
的單例對象,而單例作用域bean
是每個ApplicationContext
的單例對象。請注意,單個應用程序可能有多個應用程序上下文。 -
應用程序作用域
bean
作爲ServletContext
屬性可見。
使用註釋驅動的組件或Java配置時,可以使用 @ApplicationScope
註釋將組件分配給application
作用域。以下示例顯示瞭如何執行此操作:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
範圍bean作爲依賴項
Spring IoC容器不僅管理對象(bean)的實例化,而且還管理協作者(或依賴項)的連接。如果要將(例如)HTTP請求範圍的Bean注入(例如)另一個作用域更長的Bean,則可以選擇注入AOP代理來代替作用域的Bean。也就是說,您需要注入一個代理對象,該對象公開與範圍對象相同的公共接口,但也可以從相關範圍(例如HTTP請求)中檢索實際目標對象,並將方法調用委託給實際對象
您也可以在作爲
singleton
的 beans 之間使用,然後 reference 將通過可序列化的中間代理,因此能夠在反序列化時 re-obtain 目標 singleton bean。 當對範圍`prototype`的 bean 聲明
時,共享代理上的每個方法調用都將導致創建一個新的目標實例,然後該調用將被轉發到該目標實例。
此外,範圍代理不是以 lifecycle-safe 方式從較短範圍訪問 beans 的唯一方法。您也可以簡單地將注入點(i.e.constructor/setter 參數或自動裝配字段)聲明爲ObjectFactory
,允許getObject()
調用在每次需要時按需檢索當前實例 - 無需保留實例或單獨存儲它。
作爲擴展變體,您可以聲明ObjectProvider
,它提供了幾個額外的訪問變體,包括getIfAvailable
和getIfUnique
。
這個 JSR-330 變體稱爲Provider
,與Provider
聲明一起使用,並且每次檢索嘗試都使用相應的get()
調用。有關 JSR-330 整體的更多詳細信息,請參閱這裏。
以下 example 中的 configuration 只有一行,但瞭解“爲什麼”以及它背後的“如何”非常重要。
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.foo.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
理解
假設有bean兩個,A和B,A的scope設置爲request,B的scope隨意,在spring配置文件中如下:
…
…
此時,B要注入A這個屬性,如下:
public class B{
@Autowired
private A a;
//…
}
如果就這麼使用,spring會拋異常(是什麼異常在這裏不重要,反正是你寫錯了)
根據前面貼出的關於request和session的官方文檔解釋,應該知道這兩個作用域的一些知識了。
在這裏A的scope爲request,說明每個請求擁有一個A的實例,
而B默認是單例的,那麼,當A注入到B中,豈不是B的a屬性只被注入爲web應用的第一個request對象?之後的新的request不會再注入給a了。B是單例的,
spring自然有解決之道:
只要修改spring配置文件(也就是把bean A修改一下)
就僅僅在A中加了aop:scoped-proxy/標籤,就OK了。
爲什麼加aop:scoped-proxy/標籤就可以了呢?
因爲這個標籤就是提示spring給A這個bean創建一個代理對象。
當 B要注入A時,如下:
public class B{
@Autowired
private A a;
//…
}
其實注入的是spring給A創建的代理對象。
每個請求都會創建一個A對象,然後由A的代理對象來判斷當前使用的實際對象時哪個。(太拗口了)
大概原理是這樣吧:
當你使用代理對象的任何方法時,會根據你當前所處線程獲得對應的請求(每個請求都是開啓一個線程,用ThreadLocal來做)
這樣spring就可以通過 你當前所處線程來獲得對應的請求。
再從請求中獲得實際使用的bean,再調用該bean中與代理對象對應的方法。
這樣,代理對象在整個spring上下文中只有一個,但通過代理對象使用的實際對象是屬於當前請求範圍內的。
選擇要創建的代理類型
默認情況下,當Spring容器爲使用<aop:scoped-proxy/>
元素標記的bean創建代理時,將創建基於CGLIB的類代理。
CGLIB代理僅攔截公共方法調用!不要在這樣的代理上調用非公共方法。它們沒有被委派給實際的作用域目標對象。
另外,您可以通過指定元素false
的proxy-target-class
屬性值,將Spring容器配置爲爲此類作用域的Bean創建基於標準JDK接口的代理<aop:scoped-proxy/>
。使用基於JDK接口的代理意味着您不需要應用程序類路徑中的其他庫即可影響此類代理。但是,這也意味着作用域Bean的類必須實現至少一個接口,並且作用域Bean注入到其中的所有協作者必須通過其接口之一引用該Bean。以下示例顯示了基於接口的代理:
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
有關選擇基於類或基於接口的代理的詳細信息,請參閱代理機制。
自定義範圍
bean的作用域機制是可擴展的。您可以定義自己的作用域,甚至重新定義現有作用域,儘管後者被認爲是不好的做法,您不能覆蓋內置作用域singleton
和prototype
作用域。
略
查看自定義範圍相關博客https://waylau.com/custom-scope-in-spring/
自定義bean的性質
生命週期回調
爲了與容器對bean生命週期的管理進行交互,可以實現Spring InitializingBean
和DisposableBean
接口。容器要求 afterPropertiesSet()
前者和destroy()
後者使bean在初始化和銷燬bean時執行某些操作。
通常,JSR-250
@PostConstruct
和@PreDestroy
註釋被認爲是在現代Spring應用程序中接收生命週期回調的最佳實踐。使用這些註釋意味着您的bean沒有耦合到特定於Spring的接口。有關詳細信息,請參見@PostConstruct和@PreDestroy。如果您不想使用JSR-250批註,但仍希望刪除耦合,請考慮使用init-method和destroy-method對象定義元數據。
在內部,Spring Framework使用BeanPostProcessor
實現來處理它可以找到的任何回調接口並調用適當的方法。如果您需要自定義功能或其他生命週期行爲,Spring不會提供現成的功能,則您可以BeanPostProcessor
自己實現。有關更多信息,請參見 容器擴展點。
除了初始化和銷燬回調,Spring管理的對象還可以實現該Lifecycle
接口,以便這些對象可以在容器自身生命週期的驅動下參與啓動和關閉過程。
初始化回調
org.springframework.beans.factory.InitializingBean
容器在bean上設置了所有必需的屬性後,該接口使bean可以執行初始化工作。該InitializingBean
接口指定一個方法:
void afterPropertiesSet() throws Exception;
我們建議您不要使用該InitializingBean
接口,因爲它不必要地將代碼耦合到Spring。另外,我們建議使用@PostConstruct
註釋或指定POJO初始化方法。對於基於XML的配置元數據,可以使用init-method
屬性指定具有無效無參數簽名的方法的名稱。通過Java配置,您可以使用的initMethod
屬性 @Bean
。請參閱接收生命週期回調。考慮以下示例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
…與…完全相同
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
但不會將代碼耦合到Spring。
銷燬回調
org.springframework.beans.factory.DisposableBean
當包含該接口的容器被銷燬時,實現該接口可使Bean獲得回調
我們建議您不要使用DisposableBean
回調接口,因爲它不必要地將代碼耦合到Spring。另外,我們建議使用@PreDestroy
註釋或指定bean定義支持的通用方法。使用基於XML的配置元數據時,您可以在destroy-method
上使用屬性``。通過Java配置,您可以使用的destroyMethod
屬性@Bean
。請參閱 接收生命週期回調。考慮以下定義:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
一次配置多個初始化
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
頂層元素屬性上存在default-init-method屬性,導致Spring IoC容器將bean上稱爲init的方法識別爲初始化方法回調。 在創建和組裝bean時,如果bean類具有這種方法,則會在適當的時間調用它。
您可以通過使用頂級元素上的default-destroy-method屬性類似地(在XML中)配置destroy方法回調。
如果現有的Bean類已經具有按約定命名的回調方法,則可以通過使用的init-method和destroy-method屬性指定(在XML中)方法名稱來覆蓋默認值 本身。
*初始化和aop攔截器
😓暫時還不完全懂
Spring容器保證在爲bean提供所有依賴項後立即調用配置的初始化回調。 因此,在原始bean引用上調用了初始化回調,這意味着AOP攔截器等尚未應用於bean。 首先完全創建目標bean,然後 應用具有其攔截器鏈的AOP代理(例如)。 如果目標Bean和代理分別定義,則您的代碼甚至可以繞過代理與原始目標Bean進行交互。 因此,將攔截器應用於init方法將是不一致的,因爲這樣做會將目標Bean的生命週期與其代理/攔截器耦合在一起,並且當您的代碼直接與原始目標Bean交互時會留下奇怪的語義。
結合生命週期機制
從 Spring 2.5 開始,您有三個控制 bean 生命週期行爲的選項:繼承InitializingBean和DisposableBean回調接口;自定義init()
和destroy()
方法然後註明;和@PostConstruct 和 @PreDestroy 註釋。您可以組合這些機制來控制給定的 bean。
// 詳情點擊上面的鏈接
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
如果爲 bean 配置了多個生命週期機制,並且每個機制都配置了不同的方法 name,則每個配置的方法都在下面列出的 order 中執行。但是,如果爲多個這些生命週期機制配置了相同的方法 name(對於 example,
init()
表示初始化方法),則該方法將執行一次,如上一節中所述。
爲同一 bean 配置的多個生命週期機制具有不同的初始化方法,如下所示:
- 用
@PostConstruct
註釋的方法 afterPropertiesSet()
由InitializingBean
回調接口定義- 自定義配置的
init()
方法
Destroy 方法在同一個 order 中調用:
- 用
@PreDestroy
註釋的方法 destroy()
由DisposableBean
回調接口定義- 自定義配置的
destroy()
方法
啓動和關閉回調(暫時不太理解)
該Lifecycle
接口爲具有自己的生命週期要求(例如啓動和停止某些後臺進程)的任何對象定義了基本方法:
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的對象都可以實現該Lifecycle
接口。然後,當 ApplicationContext
自身接收到啓動和停止信號時(例如,對於運行時的停止/重新啓動場景),它會將那些調用級聯到Lifecycle
在該上下文中定義的所有實現。它通過委派給一個LifecycleProcessor
,以實現此目的,如以下清單所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
請注意,LifecycleProcessor
本身是Lifecycle
接口的擴展。它還添加了其他兩種方法來對正在刷新和關閉的上下文做出反應。
請注意,常規
org.springframework.context.Lifecycle
接口是用於顯式啓動和停止通知的普通協議,並不意味着在上下文刷新時自動啓動。爲了對特定bean的自動啓動進行精細控制(包括啓動階段),請考慮實施org.springframework.context.SmartLifecycle
。另外,請注意,不能保證會在銷燬之前發出停止通知。在常規關閉時,Lifecycle
在傳播常規銷燬回調之前,所有Bean首先都會收到停止通知。但是,在上下文生命週期中進行熱刷新或中止刷新嘗試時,僅調用destroy方法。
啓動和關閉調用的 order 非常重要。如果任何兩個 object 之間存在“depends-on”關係,則依賴方將在其依賴之後啓動,並且它將在其依賴之前停止。但是,有時直接依賴性是未知的。您可能只知道某種類型的 objects 應該在另一種類型的 objects 之前開始。在這些情況下,SmartLifecycle
接口定義了另一個選項,即,Phased
上定義的getPhase()
方法。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
略********
、
在非Web應用程序中正常關閉Spring IoC容器
本節僅適用於非Web應用程序。Spring的基於Web的
ApplicationContext
實現已經有了相應的代碼,可以在相關Web應用程序關閉時正常關閉Spring IoC容器。
如果您在非Web應用程序環境中使用Spring的IoC容器,例如,在富客戶端桌面環境中;您向JVM註冊了一個關閉掛鉤。這樣做可以確保正常關機,並在您的Singleton bean上調用相關的destroy方法,以便釋放所有資源。當然,您仍然必須正確配置和實現這些destroy回調。
要註冊關閉掛鉤,請調用registerShutdownHook()
在ConfigurableApplicationContext
接口上聲明的方法:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
Aware interfaces
看到Spring源碼中接口以Aware結尾的接口(XXXAware)在Spring中表示對XXX可以感知,通俗點解釋就是:如果在某個類裏面想要使用spring的一些東西,就可以通過實現XXXAware接口告訴Spring,Spring看到後就會給你送過來,而接收的方式是通過實現接口唯一方法set-XXX.比如:有一個類想要使用當前的ApplicationContext,那麼我們只需要讓它實現ApplicationContextAware接口,然後實現接口中的唯一方法
void setApplicationContext(ApplicationContext applicationContext)1
就可以了,spring會自動調用這個方法將applicationContext傳給我們,我們只需要接受就可以了,並可以用接收到的內容做一些業務邏輯
注意:除了通過實現Aware結尾接口獲取spring內置對象,也可以通過@Autowired註解直接注入相關對象,如下:
(如果需要用到靜態方法中,如工具方法,還是採用實現接口的方式)
口以Aware結尾的接口(XXXAware)在Spring中表示對XXX可以感知,通俗點解釋就是:如果在某個類裏面想要使用spring的一些東西,就可以通過實現XXXAware接口告訴Spring,Spring看到後就會給你送過來,而接收的方式是通過實現接口唯一方法set-XXX.比如:有一個類想要使用當前的ApplicationContext,那麼我們只需要讓它實現ApplicationContextAware接口,然後實現接口中的唯一方法
void setApplicationContext(ApplicationContext applicationContext)1
就可以了,spring會自動調用這個方法將applicationContext傳給我們,我們只需要接受就可以了,並可以用接收到的內容做一些業務邏輯
注意:除了通過實現Aware結尾接口獲取spring內置對象,也可以通過@Autowired註解直接注入相關對象,如下:
(如果需要用到靜態方法中,如工具方法,還是採用實現接口的方式)
略。。。。。。。
1.8 Container Extension Point
略 。。。。。。。。。。。。。。