bean: 應(yīng)用里被Spring IoC容器管理的對象叫做bean.Spring IoC容器負(fù)責(zé)bean的初始化韵丑,組裝和管理。bean以及它們之間的依賴關(guān)系反映在容器使用的配置元數(shù)據(jù)中壹粟。
BeanFactory: 提供了配置框架和基本功能。BeanFactory提供了一套高級的配置機制可以管理所有類型的對象西土。
ApplicationContext: BeanFactory的子類陆错。用來表示IoC容器并負(fù)責(zé)bean的初始化,配置剪侮,組裝拭宁。容器通過讀取配置元數(shù)據(jù)獲取有關(guān)要實例化,配置和組裝的對象的說明.
循環(huán)依賴問題
循環(huán)依賴:類A在構(gòu)造方法里依賴類B瓣俯,類B的構(gòu)造方法里需要類A杰标。此時就會導(dǎo)致循環(huán)依賴問題,Spring IoC容器在運行時檢測到后降铸,會拋出BeanCurrentlyInCreationException
一種解決辦法就是使用setter注入而不是構(gòu)造注入在旱,盡管推薦構(gòu)造注入,但是setter注入可以解決循環(huán)依賴推掸。
bean引入不同生命周期bean的解決方法
問題桶蝎,當(dāng)單例bean A需要非單例的bean B時,容器只會創(chuàng)建一次bean A谅畅,也就是只有一次機會設(shè)置屬性登渣。容器不能在每次bean A都需要bean B的時候?qū)嵗痓ean B。
一種解決方法就是不用IoC毡泻,通過實現(xiàn)ApplicationContextAware接口讓bean A知道容器胜茧,然后加入一個getBean("B")的方法來在bean A需要B的時候?qū)嵗H缦拢?/p>
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class BManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("B", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
不過這樣不太好仇味,畢竟業(yè)務(wù)代碼和Spring框架耦合了呻顽。更高級的方式就是使用方法注入,從這可以了解更多this blog entry.
查找方法注入
查找方法注入是容器重載容器管理的bean上方法的能力丹墨,以返回容器中另一個命名bean的查找結(jié)果廊遍。Spring框架通過使用cglib庫中的字節(jié)碼生成來實現(xiàn)此方法注入,以動態(tài)生成覆蓋該方法的子類贩挣。
- 為了spring動態(tài)創(chuàng)建子類能正常工作喉前,Spring容器將要繼承的類和要被重載的方法不能是
final
。- 另一個關(guān)鍵的限制是查找方法不能用于工廠方法王财,特別是不能在配置類中使用
@bean
方法卵迂,因為在這種情況下容器不負(fù)責(zé)創(chuàng)建實例,因此不能創(chuàng)建運行時生成的子類在飛行中绒净。
新的代碼:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
@Lookup("myCommand")
protected abstract Command createCommand();
}
管理bean生命周期的行為
通過實現(xiàn)Spring的InitializingBean
和DisposableBean
接口對bean的生命周期做出不同的反應(yīng)见咒。不過更好的是使用@PostConstruct
和@PreDestroy
注解管理初始化和銷毀。不用和Spring的接口產(chǎn)生耦合挂疆。還有xml的init-method
和destroy-method
都可以解耦改览。
使用BeanPostProcessor自定義bean
The BeanPostProcessor
接口定了回調(diào)方法讓你可以實現(xiàn)你自己或重載容器默認(rèn)的實例化邏輯哎垦,依賴解析邏輯等等。如果需要再Spring的容器完成實例化恃疯、配置化和初始化一個bean后加上自己的邏輯漏设,可以實現(xiàn)一個或者多個BeanPostProcessor
做到。如果配置了多個BeanPostProcessor
今妄,想要設(shè)置順序的話郑口,通過implement Ordered
接口提供的Ordered
屬性來設(shè)置執(zhí)行順序
通過索引提高啟動速度
雖然類路勁掃描很快,但依然可以在編譯期間創(chuàng)建一組靜態(tài)候選者提高大型應(yīng)用的啟動速度盾鳞,原理是當(dāng)ApplicationContext
檢測到索引后犬性,會使用它而不是再掃描。
生成索引的方式是腾仅,在每個包含組件(component)的模塊下添加以下依賴
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.0.5.BUILD-SNAPSHOT</version>
<optional>true</optional>
</dependency>
</dependencies>
gradle添加的方式:
dependencies {
compileOnly("org.springframework:spring-context-indexer:5.0.5.BUILD-SNAPSHOT")
}
這個過程會創(chuàng)建一個META-INF/spring.component
的文件乒裆,會被包含在jar里。
限定符的方式注入對象
- @Primary注解標(biāo)識為優(yōu)先注入的對象
- @Qualifier 注解的方式指定要注入的
- 使用泛型的方式
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
@Autowired
private Store<String> s1; // <String> 泛型限定符, 注入stringStore()
@Autowired
private Store<Integer> s2; // <Integer>泛型限定符, 注入integerStore()
// 注入所有的Store推励,只要他們的泛型是<Integer>
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
ApplicationContext事件處理和自定義事件
ApplicationContext事件處理是通過ApplicationEvent
和ApplicationListener
接口實現(xiàn)的鹤耍,當(dāng)實現(xiàn)了ApplicationListener
接口的bean部署到context里,每當(dāng)ApplicationEvent
獲取到了發(fā)布給ApplicationContext
的事件验辞,相關(guān)的bean就會被通知稿黄。從本質(zhì)上來講,這是標(biāo)準(zhǔn)的觀察者模式跌造。
Spring提供了以下標(biāo)準(zhǔn)的event,ContextRefreshedEvent
杆怕,ContextStartedEvent
,ContextStoppedEvent
,ContextStoppedEvent
,RequestHandledEvent
。
自定義事件
首先定義一個event, 需要繼承Spring的ApplicationEvent
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String test;
public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}
// accessor and other methods...
}
要發(fā)布event的話壳贪,調(diào)用ApplicationEventPublisher
的publishEvent()
方法陵珍。然后相關(guān)的bean需要實現(xiàn)ApplicationEventPublisher
。代碼如下:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}
}
Spring容器會在配置期間檢測到實現(xiàn)了ApplicationEventPublisherAware
的EmailService
類违施,然后自動調(diào)用setApplicationEventPublisher()
互纯。實際上,傳入的是Spring容器自己醉拓。
想要接收到發(fā)送的event,創(chuàng)建一個實現(xiàn)了ApplicationListener
接口的類并注冊為Spring bean伟姐。代碼如下:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
注意ApplicationListener
被參數(shù)化為自定義的event,BlackListEvent
收苏。這意味著onApplicationEvent()
方法可以保持類型安全亿卤,避免不必要的向下轉(zhuǎn)換。你可以注冊多個event listener鹿霸,但是默認(rèn)的event listener接收events是同步的排吴。這就會導(dǎo)致publishEvent()
被阻塞,直到所有的listeners處理完event懦鼠。單線程和同步的一個好處是可以共享事務(wù)钻哩。如果需要另個一event發(fā)布策略屹堰,可以參考Spring的ApplicationEventMulticaster
接口。
注解的方式創(chuàng)建listeners
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
使用@EventListener后街氢,不需要實現(xiàn)指定的接口扯键,方法名也可以自定義。
如果想監(jiān)聽多個event珊肃,并且不想定義任何參數(shù)荣刑,可以在注解上指定event類型,如下:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
如果想要把發(fā)布的event的結(jié)果處理另一個event伦乔,只需要將方法返回類型改為想要被發(fā)布的event厉亏,當(dāng)然不支持異步,如下:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
上面的代碼就會處理完BlackListEvent
后發(fā)布一個新的ListUpdateEvent
烈和。如果需要發(fā)布多個event爱只,只需要返回events的Collection
。
異步監(jiān)聽器
如果想要特定的listeners異步處理招刹,只需要簡單的加上一個@Async
注解恬试,如下:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用異步event注意以下幾點:
- 如果event listener拋出了異常,不會通知給調(diào)用者疯暑,check
AsyncUncaughtExceptionHandler
了解更多忘渔。 - 這樣的event listener無法發(fā)送返回。如果需要缰儿,自己注入
ApplicationEventPublisher
手動發(fā)送畦粮。
如果有多個Listener,可以使用@Order
注解指定順序乖阵,如下:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
如果想用泛型定義event的結(jié)構(gòu)宣赔。可以使用EntityCreatedEvent<T>
瞪浸,<T>
是創(chuàng)建的實體類型儒将。You can create the following listener definition to only receive EntityCreatedEvent for a Person:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
可能會遇到類型擦除的問題,具體可以參考使用ResolvableTypeProvider
.
Spring AOP
讓我們從中心AOP和術(shù)語開始对蒲。這些術(shù)語不是Spring專屬的钩蚊,而AOP術(shù)語也不是很直觀。如果Spring使用它的術(shù)語可能更加令人疑惑蹈矮。
Aspect: 切面砰逻,關(guān)注跨越多個類的模塊。事務(wù)管理就是一個好例子泛鸟。在Spring AOP里蝠咆,aspects是通過被
@Aspect
注解的類實現(xiàn)马僻,或者基于schema實現(xiàn)的未辆。Joint Point:一個程序執(zhí)行期間的點。比如方法處理或者異常處理。在Spring AOP里屈张,一個Joint Point總是代表一個方法的執(zhí)行瞭恰。
Advice:切面里特定的Joint Point采取的行動奠衔。不同類型的advice包含
before
脊另,around
,after
等。許多AOP框架里鉴逞,包含Spring遗菠,將advice作為攔截器的模型。圍繞著Joint Point維護一系列的攔截器华蜒。Pointcuts: 切入點辙纬。匹配到j(luò)oint point的。Advice是與切入點表達(dá)式相關(guān)聯(lián)叭喜,并且運行任何匹配到j(luò)oint point的切入點(比如贺拣,指定名字的方法的執(zhí)行)。與切入點表達(dá)式匹配的連接點的概念是aop的核心捂蕴,Spring默認(rèn)使用AspectJ表達(dá)式語言譬涡。
Introduction: 為類型定義其他的方法或者字段。Spring AOP允許你為任意advised對象引入新的接口(包括相應(yīng)的實現(xiàn))啥辨。比如涡匀,你可以使用一個introduction讓bean實現(xiàn)
IsModified
接口。Target Object: 被一個或者多個切面advised的對象溉知。也被稱為advised對象陨瘩。由于spring aop是使用運行時代理實現(xiàn)的,因此該對象將始終是被代理對象级乍。
AOP proxy: 有AOP框架創(chuàng)建的對象舌劳,用于實現(xiàn)aspect contract。在Spring框架里玫荣,AOP proxy是JDK動態(tài)代理或者CGLIB代理甚淡。
Weaving: 將aspect于其他應(yīng)用的類型或者對象連接起來,來創(chuàng)建一個advised對象捅厂。這個可能在編譯期(比如AspectJ編譯器)贯卦、加載期或者運行期完成。Spring AOP焙贷, 想起他純java AOP框架一樣撵割,在運行期執(zhí)行weaving。
advice的類型
- Before advice: 在joint point前執(zhí)行的advice
- After return advice: 在joint point執(zhí)行完成并返回后執(zhí)行advice盈厘。比如睁枕,方法返回了并且沒有拋異常
- After throwing advice: 在方法拋出異常后執(zhí)行官边。
- After (finally) advice: 不過joint point正常返回或異常都會執(zhí)行沸手。
- Around advice: 方法調(diào)用時所有的情況都執(zhí)行外遇。around advice很全面。around advice可以在方法調(diào)用前和后執(zhí)行自定義行為契吉。也會在正常返回和拋出異常時執(zhí)行跳仿。
雖然around advice包含了所有的joint point, 但Spring建議使用指定類型的advice。而不是直接使用around advice.
從Spring 2.0開始捐晶,所有的advice參數(shù)都可以是合適的類型菲语,而不是一個Objects
數(shù)組。
Spring AOP的能力和目標(biāo)
Spring AOP使用純Java實現(xiàn)的惑灵。所以不需要特別的復(fù)雜的處理山上。Spring AOP不需要控制類加載器層,因此適用于Servelt容器或者應(yīng)用服務(wù)器英支。
Spring AOP當(dāng)前僅支持方法執(zhí)行joint point(Spring beans上的advising的方法執(zhí)行)佩憾。字段攔截沒有被實現(xiàn),如果想攔截字段干花,可以考慮使用AspectJ妄帘。
Spring AOP的目標(biāo)和其他AOP框架不同,Spring AOP的目標(biāo)不是提供一個完整的AOP實現(xiàn)池凄。而是想要提供一個整合AOP和Spring IoC的方式來解決企業(yè)通過用的問題抡驼。
所以Spring AOP的功能是和Spring IoC容器一般是一起使用的。Aspect用正常的bean定義語法配置:這是與其他AOP框架最關(guān)鍵的區(qū)別肿仑。所以用Spring AOP致盟,不能簡單或者高效的處理細(xì)粒度的類:AspectJ在這種情況下很合適。Spring AOP永遠(yuǎn)不會與AspectJ競爭來提供一個全面的AOP解決方案尤慰。我們相信基于代理的框架勾邦,比如Spring AOP和成熟的AspectJ框架都是有價值的,兩者相互補充割择,而不是競爭眷篇。Spring的Spring AOP和Spring IoC無縫整合AspectJ,以便照顧到所有基于Spring應(yīng)用結(jié)構(gòu)的AOP使用荔泳。
AOP 代理
Spring AOP默認(rèn)為AOP代理使用Java動態(tài)代理蕉饼。這讓所有的接口都可以被代理。
Spring AOP也可以使用CGLIB代理玛歌。當(dāng)需要代理類而不是接口的時候昧港。如果一個對象類沒有實現(xiàn)一個接口,Spring AOP會使用CGLIB支子。但更好的是基于接口編程而不是類创肥。業(yè)務(wù)類通常會實現(xiàn)一個或者多個業(yè)務(wù)接口。
Spring組件掃面可能掃不到@Aspect
注解,建議可以再加上個@Component
注解