-
AOP基本概念及特點(diǎn)
一、什么是AOP及實(shí)現(xiàn)方式
AOP:Aspect ?Oriented ?Programming,面向切面編程,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。
主要功能:日志記錄,性能統(tǒng)計(jì),安全控制,事物處理,異常處理等等。(事務(wù)處理:任何操作數(shù)據(jù)庫(kù)的方法,尤其是增刪改,需要事物的處理,使用AOP時(shí),執(zhí)行到某種類型的方法,或者某一層的類,自動(dòng)開(kāi)啟事物和獲取連接、提交事物、關(guān)閉事物的步驟,簡(jiǎn)化了操作)
AOP實(shí)現(xiàn)方式:1——預(yù)編譯方式。(AspectJ)
? ? ? ? ? ? ? ? ? ? ? ? ?2——運(yùn)行期動(dòng)態(tài)代理。(JDK動(dòng)態(tài)代理、CGLib動(dòng)態(tài)代理)
? ? ? ? ? ? ? ? ? ? ? ?(SpringAOP、JbossAOP)
切面的理解:例如系統(tǒng)中有產(chǎn)品管理、訂單管理、服務(wù)管理等等模塊(子功能),任一模塊(子功能),都需要記錄它的日志,控制它的事物,以及做一些安全驗(yàn)證的功能。但是如果在每一個(gè)模塊(子功能)中去設(shè)計(jì)日志、事物、安全驗(yàn)證,會(huì)帶來(lái)大量工作量,尤其當(dāng)項(xiàng)目達(dá)到一定規(guī)模時(shí),比如修改日志記錄的方式,那么則需要修改每一個(gè)模塊的日志功能,通過(guò)切面方式對(duì)開(kāi)發(fā)人員是不可見(jiàn)的,當(dāng)執(zhí)行任一模塊時(shí),通過(guò)預(yù)編譯或者運(yùn)行期動(dòng)態(tài)代理方式,都會(huì)去記錄它的日志,實(shí)現(xiàn)了一處寫功能,處處可實(shí)現(xiàn)的方式,對(duì)于開(kāi)發(fā)人員允許不知道有這樣功能的存在,這樣就簡(jiǎn)化了操作(修改日志、編寫事物等),業(yè)務(wù)功能橫向走,切面垂直業(yè)務(wù)功能。
二、AOP基本概念(切面—>織入—>目標(biāo)對(duì)象—>切入點(diǎn)—>連接點(diǎn)—>通知—>引入—>AOP代理:“切面”要執(zhí)行的動(dòng)作,通過(guò)“織入”與所有功能模塊進(jìn)行聯(lián)系,又通過(guò)“目標(biāo)對(duì)象”找到具體功能模塊,通過(guò)“切入點(diǎn)”將要查找某個(gè)功能的某個(gè)方法,通過(guò)“連接點(diǎn)”找到該功能的特定方法的開(kāi)始,通過(guò)“通知”將要執(zhí)行何種切面的動(dòng)作,通過(guò)“引入”引入該動(dòng)作用到的對(duì)象和方法,通過(guò)“AOP代理”實(shí)現(xiàn)該方法。)
AOP相關(guān)概念:
1、切面(Aspect):一個(gè)關(guān)注點(diǎn)(事物),這個(gè)關(guān)注點(diǎn)可能會(huì)橫切多個(gè)對(duì)象(產(chǎn)品管理、訂單管理)。
2、連接點(diǎn)(Joinpoint):程序執(zhí)行過(guò)程中的某個(gè)特定的點(diǎn)。(一個(gè)類中執(zhí)行的某個(gè)方法的開(kāi)始)。
3、通知(Advice):在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作(方法執(zhí)行的時(shí)候,切面額外執(zhí)行的動(dòng)作,比如說(shuō)方法執(zhí)行時(shí),開(kāi)啟事物提交功能)。
4、切入點(diǎn)(Pointcut):匹配連接點(diǎn)的斷言,在AOP中通知和一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián)(在切面中匹配一個(gè)具體的連接點(diǎn)(某個(gè)功能的方法的開(kāi)始))。
5、引入(Introduction):在不修改類代碼的前提下,修改的是字節(jié)碼文件,為類添加新的方法和屬性。(類似于編譯期動(dòng)態(tài)的修改class文件去增加新的屬性和方法,源代碼中并沒(méi)有這樣的屬性和方法),取決于使用AspectJ和SpringAOP的實(shí)現(xiàn)方式,使用方式不同,為類添加屬性和方法的方式是有區(qū)別的。
6、目標(biāo)對(duì)象(Target Object):被一個(gè)或者多個(gè)切面所通知的對(duì)象。(例如存在一個(gè)訂單的service(對(duì)象)和一個(gè)商品的service(對(duì)象),執(zhí)行該模塊的功能時(shí),切面會(huì)通知相對(duì)應(yīng)的service,在執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),去加上事物的控制,這兩個(gè)service就是目標(biāo)對(duì)象)。
7、AOP代理(AOP ?Proxy):AOP框架創(chuàng)建的對(duì)象,用來(lái)實(shí)現(xiàn)切面契約(aspect contract),包括通知方法執(zhí)行等功能(這里執(zhí)行的方法指的是切面里的執(zhí)行相應(yīng)操作所用到的方法)開(kāi)發(fā)時(shí)不知道這個(gè)對(duì)象存在的,也不清楚會(huì)創(chuàng)建成什么樣子。
8、織入(Weaving):把切面連接到其他的應(yīng)用程序類型或者對(duì)象上,并創(chuàng)建一個(gè)被通知的對(duì)象,分為:編譯時(shí)織入、類加載時(shí)織入、執(zhí)行時(shí)織入。(使切面和對(duì)象(模塊功能)牽連起來(lái))
Spring官方文檔給出的解釋:
Advice(通知)的類型(在切面某個(gè)特定連接點(diǎn)執(zhí)行的動(dòng)作)
前置通知(Before advice):在某連接點(diǎn)(join point)(某個(gè)功能方法開(kāi)始執(zhí)行前)之前執(zhí)行的通知,但不能阻止連接點(diǎn)前的執(zhí)行(除該方法外的其他方法的執(zhí)行)(除非它拋出一個(gè)異常)。
返回后通知(After returning advice):在某連接點(diǎn)(方法)正常完成后執(zhí)行的通知(將要執(zhí)行的切面功能)。
拋出異常后通知(After throwing advice):在方法拋出異常退出時(shí)執(zhí)行的通知(將要執(zhí)行切面的功能)。
后通知(After(finally)advice):當(dāng)某連接點(diǎn)(方法)退出的時(shí)候執(zhí)行的通知(切面將要執(zhí)行的功能)(不論是正常返回還是異常退出都會(huì)執(zhí)行的通知)。
環(huán)繞通知(Around Advice):包圍一個(gè)連接點(diǎn)(join point)的通知(在整個(gè)方法的內(nèi)部都有切面要執(zhí)行的功能存在,不分前后)。
Spring框架中AOP的用途:
用途1:提供了聲明式的企業(yè)服務(wù)(也可以是其他服務(wù),例如互聯(lián)網(wǎng)服務(wù)),特別是EJB(重量級(jí)框架)的替代服務(wù)的聲明。
用途2:允許用戶定制自己的方面(切面),以完成OOP(面向?qū)ο缶幊蹋M世界中行為和方式,可以理解為實(shí)現(xiàn)一個(gè)功能的順序)與AOP(橫切的方式,可以理解為各個(gè)功能之間橫切的一種功能)的互補(bǔ)使用,可以實(shí)現(xiàn)自己橫切的功能。
三、Spring中的AOP(Spring中AOP的實(shí)現(xiàn)方式以及特點(diǎn))
特點(diǎn)1:純java實(shí)現(xiàn),無(wú)需特殊的編譯過(guò)程(普通代碼需要加載才能執(zhí)行),不需要控制類加載器層次??梢愿玫倪M(jìn)行控制(使用類加載器應(yīng)當(dāng)謹(jǐn)慎,容易方法生ClassNotFound異常這種情況)。
特點(diǎn)2:目前只支持方法執(zhí)行連接點(diǎn)(通知Spring Bean的方法執(zhí)行),(在執(zhí)行某方法時(shí),才去執(zhí)行切面的功能)。
特點(diǎn)3:SpringAOP不是為了提供最完整的AOP實(shí)現(xiàn)(盡管它非常強(qiáng)大);而是側(cè)重于提供一種AOP實(shí)現(xiàn)和Spring IoC容器之間的整合,用于幫助解決企業(yè)應(yīng)用中的常見(jiàn)問(wèn)題。
特點(diǎn)4:SpringAOP不會(huì)與AspectJ(完整的、全面的AOP解決方案)競(jìng)爭(zhēng),也不會(huì)提供綜合全面的AOP解決方案。
有接口和無(wú)接口的SpringAOP實(shí)現(xiàn)區(qū)別:(一個(gè)類是否實(shí)現(xiàn)了某個(gè)接口,例如一個(gè)ProductService接口,一個(gè)ProductServiceImpl實(shí)現(xiàn)了該接口,在這類上切入的切面就是有接口的AOP)
SpringAOP對(duì)于有接口的使用標(biāo)準(zhǔn)的JavaSE動(dòng)態(tài)代理作為AOP代理,這使得任何接口(或者接口集)都可以被代理。
SpringAOP對(duì)于沒(méi)有接口的實(shí)現(xiàn)類中使用CGLIB代理(如果一個(gè)業(yè)務(wù)對(duì)象并沒(méi)有實(shí)現(xiàn)一個(gè)接口)
四、Schema-based 實(shí)現(xiàn)AOP(基于配置文件實(shí)現(xiàn)AOP)
五、API實(shí)現(xiàn)AOP(基于API實(shí)現(xiàn)AOP)
六、AspectJ實(shí)現(xiàn)AOP
查看全部 -
基于Schema-based配置的AOP實(shí)現(xiàn)
Spring所有的切面和通知器都必須放在<aop:config>內(nèi)(可以配置多個(gè)<aop:config>元素),每一個(gè)<aop:config>可以包含aspect,pointcout和advisor元素(它們必須按照這個(gè)順序進(jìn)行聲明)。
<aop:config>的配置大量使用了Spring的自動(dòng)代理機(jī)制。
該配置的含義:一個(gè)類作為切面來(lái)聲明,切面Id是myAspect,也就是說(shuō)程序執(zhí)行某個(gè)功能模塊時(shí),通過(guò)pointcut和Joinpoint執(zhí)行該切面的功能。
案例:兩個(gè)類,一個(gè)類作為切面類,另一個(gè)作為業(yè)務(wù)類。(注意:<beans>中添加約束,xmlns:aop="http://www.springframework.org/schema/aop
)
代碼:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
? ? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
? ? ? ? xmlns:context="http://www.springframework.org/schema/context"
? ? ? ? xmlns:aop="http://www.springframework.org/schema/aop"
? ? xsi:schemaLocation="http://www.springframework.org/schema/beans
? ? ? ? http://www.springframework.org/schema/beans/spring-beans.xsd
? ? ? ? http://www.springframework.org/schema/context
? ? ? ? http://www.springframework.org/schema/context/spring-context.xsd ?
? ? ? ? http://www.springframework.org/schema/aop
? ? ? ? http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
? ? ? ? <bean id="aspectImooc" class="aspect.AspectImooc"></bean>
? ? ? ? <bean id="ServiceImooc" class="aspect.ServiceImooc"></bean>
? ? ? ? <aop:config>
? ? ? ? <aop:aspect id="aspectImoocAop" ref="aspectImooc"></aop:aspect>
? ? ? ? </aop:config>
查看全部 -
配置切入點(diǎn)Pointcut(找到某個(gè)功能模塊的具體方法)
execution用于匹配某個(gè)功能模塊的具體方法。
以下SpringAOP和Spring支持的AspectJ都支持。
圖一的1:執(zhí)行所有的public方法時(shí),進(jìn)行切入(執(zhí)行相應(yīng)切面的相應(yīng)功能)。
圖一的2:執(zhí)行所有的set方法時(shí),進(jìn)行切入。
圖一的3:執(zhí)行com.xy.service.AccountService類下的所有方法時(shí),進(jìn)行切入。
圖一的4:執(zhí)行com.xyz.service包下的所有方法時(shí),進(jìn)行切入。
圖一的5:執(zhí)行com.xyz.service包下以及子包下的所有方法時(shí),進(jìn)行切入。
以下為SpringAOP自己支持的。
SpringAOP其他的匹配。
圖一的1:匹配目標(biāo)對(duì)象。
圖一的2:匹配參數(shù)的。
圖一的3:按注解匹配的。
圖一的4:按注解匹配的。
圖一的5:按注解匹配的。
圖二的1:參數(shù)里采用了何種注解。
圖二的2:采用了何種bean。
圖二的3:bean是以何種形式進(jìn)行結(jié)尾的。(這些可以去Spring官方文檔和搜索引擎搜索,會(huì)有很全面的解釋,這些切入點(diǎn)會(huì)有很多類型,在具體使用時(shí),可以查找相應(yīng)文檔)
完整的AOP配置(根據(jù)不同業(yè)務(wù)需求,查找相關(guān)文檔,進(jìn)行不同的expression表達(dá)式配置)
案例1:表示執(zhí)行service包下的所有類的任何類型的方法時(shí),進(jìn)行切入。
案例2:表示執(zhí)行SystemArchitecture的businessService()方法時(shí),才進(jìn)行切入。
案例3:執(zhí)行AspectBiz中的所有方法,都會(huì)進(jìn)行切入。
配置以某個(gè)單詞結(jié)尾的類:例如上面Aspect編寫成*。
查看全部 -
Advice應(yīng)用(advice的配置)
Before advice的兩種配置方式
<aop:before ?pointcut-ref="切入點(diǎn)的Id" ?method="切面的方法"/>。直接設(shè)置切入點(diǎn)(就不用單獨(dú)設(shè)置<aop:point-cut/>標(biāo)簽了)
<aop:before ?pointcut="execution(* ?com.xyz.myapp.dao..(..))" ?method="doAccessCheck"/>
案例:(前置)
步驟1:編寫邏輯功能類、切面功能類、并編寫各自的方法。
代碼:
步驟2:編寫XML配置,實(shí)例化邏輯功能類的Bean和切面功能類的Bean,配置切面、切入點(diǎn)、通知。
案例:(After return advice返回后通知)
<aop:after-returning>returning屬性,限制方法的返回值,例如retVal。(arg-names為參數(shù)名稱,它是嚴(yán)格匹配的。)
步驟1:編寫邏輯功能類、切面功能類、并編寫各自的方法(和以上相似,只不過(guò)切面類多了一個(gè)方法)。
步驟2:編寫XML配置,實(shí)例化邏輯功能類的Bean和切面功能類的Bean,配置切面、切入點(diǎn)、通知。
案例:(拋出異常后通知After throwing advice,<aop:after-throwing>標(biāo)簽多了一個(gè)throwing屬性,它是限制可被傳遞的異常參數(shù)名稱)
步驟1:編寫邏輯功能類、切面功能類、并編寫各自的方法(編寫發(fā)生異常后切面要執(zhí)行的方法)
步驟2:編寫XML配置,實(shí)例化邏輯功能類的Bean和切面功能類的Bean,配置切面、切入點(diǎn)、拋出異常通知。
案例:(后置通知After (finally) advice)
步驟1:編寫邏輯功能類、切面功能類、并編寫各自的方法(編寫邏輯方法執(zhí)行后要執(zhí)行的切面方法)
步驟2:編寫XML配置,實(shí)例化邏輯功能類的Bean和切面功能類的Bean,配置切面、切入點(diǎn)、拋出異常通知。
案例:(環(huán)繞通知After (finally) advice)
步驟1:編寫邏輯功能類、切面功能類、并編寫各自的方法(編寫邏輯方法執(zhí)行后要執(zhí)行的切面方法)
步驟2:編寫XML配置,實(shí)例化邏輯功能類的Bean和切面功能類的Bean,配置切面、切入點(diǎn)、拋出異常通知。
查看全部 -
Introductions應(yīng)用(簡(jiǎn)介的advice,作用:在不改變對(duì)象和類的代碼的前提下,為對(duì)象和類添加新的方法。)
Introduction定義:簡(jiǎn)介允許一個(gè)切面聲明一個(gè)實(shí)現(xiàn)指定接口的通知對(duì)象,并且提供一個(gè)接口實(shí)現(xiàn)類(接口的實(shí)現(xiàn)類,來(lái)代表實(shí)例化好的Bean)來(lái)代表這些對(duì)象。
Introductions的實(shí)現(xiàn):由<aop:aspect>中的<aop:declare-parents>元素聲明,該元素用于聲明所有匹配的類型,擁有一個(gè)新的parent(接口聲明的對(duì)象可以指向getBean獲得的對(duì)象)。
<aop:declare-parents>屬性
type-matching="expression表達(dá)式(表示匹配什么樣的類型)。
implement-interface="接口的路徑"(切面類的接口)。
default-impl=“切面類的路徑”。
案例:
步驟1:編寫邏輯功能接口(限制邏輯功能),并提供它的實(shí)現(xiàn)類。
public interface ServiceInterfaceFunction {
public void print();
}
public class ServiceFunction implements ServiceInterfaceFunction {
@Override
public void print() {
System.out.println("邏輯功能接口實(shí)現(xiàn)類的方法執(zhí)行了");
}
}
public class ServiceGongNeng {
}
步驟2:配置<aspect:config>(有接口的SpringAOP實(shí)現(xiàn),使用JavaSE動(dòng)態(tài)代理,也就是JDK代理,JDK動(dòng)態(tài)代理只能對(duì)實(shí)現(xiàn)了接口的類生成代理,而不能針對(duì)類,所以要配置<aop:declare-parents>)。
步驟3:測(cè)試
@Test
public void testaop(){
ApplicationContext ac=new ClassPathXmlApplicationContext("spring-aop-schema-advice.xml");
ServiceInterfaceFunction sif=(ServiceInterfaceFunction)ac.getBean("serviceGongNeng",ServiceGongNeng.class);
sif.print();
}
結(jié)果:正??梢詧?zhí)行接口實(shí)現(xiàn)類里的方法。(注意:types-matching="springAOP.ServiceGongNeng"沒(méi)有execution)
重點(diǎn)擴(kuò)展:所有基于配置文件的aspects,只支持單例模式(Single Model)。
查看全部 -
Advisors
1、advisor就像一個(gè)小的自包含的方面,只有一個(gè)advice。
2、切面自身通過(guò)一個(gè)bean表示,并且必須實(shí)現(xiàn)某個(gè)advice接口,同時(shí),advisor也可以很好的利用AspectJ的切入點(diǎn)表達(dá)式(后面介紹AspectJ實(shí)現(xiàn)AOP時(shí)會(huì)介紹到AspectJ的切入點(diǎn)表達(dá)式)。
3、Spring通過(guò)配置文件中<aop:advisor>元素支持advisor,實(shí)際使用中,大多數(shù)情況下它會(huì)和transactional advice(事物相關(guān)的advice)配合使用(也可以有其他使用方式,根據(jù)項(xiàng)目或者工程的實(shí)際情況來(lái)決定)。
4、為了定義一個(gè)advisor的優(yōu)先級(jí)以便讓advice可以有序,可以使用order屬性來(lái)定義advisor的順序。
advisor的配置(<tx:advice>是對(duì)于事物的相關(guān)聲明,通過(guò)這種方式使用advisor,這種方式是在使用Spring事物的控制時(shí),經(jīng)常使用的方式)
案例:(沒(méi)有講解advisor的案例,講解了環(huán)繞通知的一個(gè)案例,適用場(chǎng)景:統(tǒng)計(jì)邏輯功能方法的調(diào)用次數(shù),例如統(tǒng)計(jì)某方法調(diào)用3次,就不讓調(diào)用了)
步驟1:
public class ServiceFunction {
public void print1(){
System.out.println("邏輯功能方法1執(zhí)行了");
}
public void print2(){
System.out.println("邏輯功能方法2執(zhí)行了");
throw new RuntimeException();
}
}
步驟2:
public class AspectFunction {
private int max_times;
public int getMax_times() {
return max_times;
}
public void setMax_times(int max_times) {
this.max_times = max_times;
}
public Object aspectFunction(ProceedingJoinPoint pjp) throws Throwable{
int num_times=0;
do{
num_times++;
try{
System.out.println(num_times+"次");
return pjp.proceed();
}catch(RuntimeException e){
}
}while(num_times<=max_times);
System.out.println("方法總共執(zhí)行了"+num_times+"次");
return null;
}
}
步驟3:
查看全部 -
Spring的API實(shí)現(xiàn)AOP功能
1、這是Spring1.2的歷史用法,現(xiàn)在Spring4.0仍然支持。
2、這是SpringAOP的基礎(chǔ)
使用Spring的API實(shí)現(xiàn)AOP的原因:Schema-based(配置文件)和AspectJ實(shí)現(xiàn)AOP完全可以滿足我們使用AOP的功能,使用API是因?yàn)檫@是SpringAOP的基礎(chǔ),可以加深我們應(yīng)用和了解SpringAOP的理解(無(wú)論是注解方式、還是XML配置文件的方式,最終實(shí)現(xiàn)的基礎(chǔ)都是和API密切關(guān)系的)。
3、現(xiàn)在SpringAOP的用法也是基于歷史的,只是更簡(jiǎn)便了(在Spring1.2時(shí)配置文件很多,還需要了解各種配置)。
Spring的API
一、Pointcut(作為一個(gè)接口,有幾個(gè)實(shí)現(xiàn)類)
1、Pointcut接口的實(shí)現(xiàn)類之一:NameMatchMethodPointcut,根據(jù)方法名字進(jìn)行匹配。
2、該類有一個(gè)成員變量:mappedNames(它是一個(gè)集合,存放用于匹配的方法的名稱),匹配方法的集合。
配置文件的例子:(sa開(kāi)頭的所有方法進(jìn)行切入)
二、Before advice(原理:?jiǎn)为?dú)寫一個(gè)前置通知的類,并實(shí)現(xiàn)MethodBeforeAdvice接口,并實(shí)現(xiàn)該接口的方法,)
1、一個(gè)簡(jiǎn)單的通知類型。
2、執(zhí)行邏輯方法前被調(diào)用,不需要MethodInvocation對(duì)象。
3、前置通知可以在連接點(diǎn)執(zhí)行之前插入自定義行為,但不能改變返回值。
案例:(會(huì)使用到后面的知識(shí),暫時(shí)編寫這些)
public class MoocBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] object, Object target) throws Throwable {
System.out.println("前置通知的方法:" + method.getName() + " ? " + target.getClass().getName());
}
}
三、Throws advice
1、如果連接點(diǎn)(執(zhí)行的業(yè)務(wù)方法)出現(xiàn)異常,throws advice在方法返回后被調(diào)用。
2、如果throws-advice的方法拋出異常,那么它將覆蓋原有異常。
3、接口org.springframework.aop.ThrowAdvice不包含任何方法,僅僅是一個(gè)聲明,其實(shí)現(xiàn)類一定要實(shí)現(xiàn)類似這樣的方法,void afterThrowing([Method, args, target],ThrowableSubclass);
Throws advice發(fā)生異常的說(shuō)明
public void afterThrowing(Exception ex);//只有一個(gè)異常。
public void afterThrowing(RemoteException ex);//另外一種類型的異常。
?public void afterThrowing(Method method,Object[] args(目標(biāo)參數(shù)),Object target(對(duì)象),Exception ex);
?public void afterThrowing(Method method,Object[] args(目標(biāo)參數(shù)),Object target(對(duì)象),ServletException ex(其他類型的異常));
結(jié)論:異常通知(如上)方法參數(shù)里必須有異常,其他參數(shù)可有可無(wú)的。
拋出異常通知案例:
public class MoocThrowsAdvice implements ThrowsAdvice{
public void afterThrows(Exception ex){
System.out.println("拋出異常通知執(zhí)行了");
}
public void afterThrows(Method method,Object[] object,Object target,Exception ex){
System.out.println("拋出異常通知執(zhí)行了");
}
}
四、After Returning advice
1、返回后通知必須實(shí)現(xiàn)org.springframework.aop.AfterReturningAdvice接口(它和基于配置文件的配置方式是一樣的)
2、可以訪問(wèn)返回值(但不能修改返回值)、被調(diào)用的方法、方法的參數(shù)和目標(biāo)。
3、如果拋出異常,將會(huì)拋出攔截器鏈,替代返回值。
返回后通知案例
public class MoocAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("返回后通知方法執(zhí)行了"+method.getName()+" ?"+target.getClass().getName()+" ?"+returnValue);
}
}
五、Interception around advice(環(huán)繞通知)
1、Spring的切入點(diǎn)模型使得切入點(diǎn)可以獨(dú)立與advice重用,以針對(duì)不同的advice可以使用相同的切入點(diǎn)(和之前基于XML配置文件的AOP實(shí)現(xiàn)方式是一樣的,切入點(diǎn)可以放在外面單獨(dú)地去定義,通過(guò)Point reference,在每一個(gè)業(yè)務(wù)邏輯方法中都可以引用相同的切入點(diǎn),當(dāng)然,每個(gè)Advice也可以定義自己的Point cut)。
案例:
public class MoocMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object obj = null;
System.out.println("環(huán)繞前通知實(shí)現(xiàn)了");
obj = invocation.proceed();
System.out.println("環(huán)繞后通知實(shí)現(xiàn)了");
return null;
}
}
六、Introduction advice(和XML配置中的Introduction advice一樣的功能)
1、Spring把引入通知作為一種特殊的攔截通知。
2、如果使用API實(shí)現(xiàn)AOP,則需要IntroductionAdvisor和IntroductionInterceptor這兩個(gè)接口。
3、Introduction advice僅適用于類,不能和任何其它切入點(diǎn)一起使用。
如下為基于XML配置的Introduction advice內(nèi)容
一個(gè)Spring test suite的例子
1、如果調(diào)用lock()方法,希望所有的setter方法拋出LockedException異常(使用場(chǎng)景:如使物體不可變,AOP典型例子)
2、需要完成繁重任務(wù)的IntroductionInterceptor,不是去實(shí)現(xiàn)該接口,而是使用org.springframework.aop.support.DelegatingIntroductionInterceptor
查看全部 -
ProxyFactoryBean及相關(guān)內(nèi)容(上)
1、通過(guò)Spring API創(chuàng)建Spring AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean
2、ProxyFactoryBean可以完全控制切入點(diǎn)和通知(advice)以及他們的順序。(和其它工廠Bean實(shí)現(xiàn)方式是一樣的,都引入了一個(gè)中間層)
假如有這樣一種情況:定義了一個(gè)Bean的Id為foo的ProxyFactoryBean,那么引用foo這個(gè)對(duì)象,看到的將不是ProxyFactoryBean本身,而是ProxyFactoryBean這個(gè)類通過(guò)getObject()方法創(chuàng)建的對(duì)象。
getObject():將創(chuàng)建一個(gè)AOP代理包裝一個(gè)目標(biāo)對(duì)象。(ProxyFactoryBean通過(guò)這種方式達(dá)到代理的目的)
3、使用ProxyFactoryBean或者其它IoC相關(guān)類來(lái)創(chuàng)建AOP代理的最重要好處是因?yàn)橥ㄖ颓腥朦c(diǎn)也可以由IoC來(lái)管理。
4、當(dāng)被代理類沒(méi)有實(shí)現(xiàn)任何接口,使用CGLIB代理,否則使用JDK代理。
5、通過(guò)設(shè)置proxyTargetClass為true,可強(qiáng)制使用CGLIB代理(無(wú)論代理類是否實(shí)現(xiàn)接口)
6、如果目標(biāo)類實(shí)現(xiàn)了一個(gè)(或者多個(gè))接口,那么創(chuàng)建代理的類型將依賴ProxyFactoryBean的配置。(因?yàn)镻roxyFactoryBean里有一個(gè)ProxyInterfaces屬性,該屬性可以查看該類實(shí)現(xiàn)了哪些接口)
7、如果ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個(gè)或者多個(gè)全限定接口名(包括包名、類名、完整的名稱),基于JDK的代理將被創(chuàng)建。
8、如果ProxyFactoryBean的proxyInterfaces屬性沒(méi)有被設(shè)置,但是目標(biāo)類實(shí)現(xiàn)了一個(gè)(或者更多)接口,那么ProxyFactoryBean將自動(dòng)檢測(cè)到這個(gè)目標(biāo)類已經(jīng)實(shí)現(xiàn)了至少一個(gè)接口,創(chuàng)建一個(gè)基于JDK的代理。
例子:創(chuàng)建基于接口的代理
ProxyFactoryBean工作原理:首先,定義了一個(gè)Id為personTarget的Bean,又定義了一個(gè)Id為myAdvisor的Bean,定義了一個(gè)Id為debugInterceptor的Bean,重點(diǎn)在接下來(lái)的定義,定義了一個(gè)Id為person的Bean,但是對(duì)應(yīng)的并不是Person類,而是Spring提供的ProxyFactoryBean這個(gè)類,并且這個(gè)Bean配置里有一個(gè)屬性名為proxyInterfaces的<property>配置,并且它的value值為Person的路徑,另外一個(gè)<property>標(biāo)簽屬性名稱為targer并且指向personTarget(這個(gè)類是Person的具體實(shí)現(xiàn)類),當(dāng)我們get()Id為person這個(gè)Bean時(shí),返回的并不是ProxyFactoryBean的對(duì)象,而是ProxyFactoryBean里的getObject()返回的對(duì)象,它返回的是屬性名為target指向的personTarget的對(duì)象,通過(guò)ProxyFactoryBean創(chuàng)建代理的時(shí)候,可以指定它的interceptor,interceptorNames屬性,是一個(gè)集合,可以通過(guò)List、Value指定它的具體內(nèi)容。
案例:(通過(guò)Spring API實(shí)現(xiàn)通知)
各種通知的類前面已經(jīng)寫好了,所以主要進(jìn)行XML配置。
public interface BizLogic {
String save();
}
public class BizLogicImpl implements BizLogic {
@Override
public String save() {
System.out.println("BizLogicImpl: BizLogicImpl save");
return "BizLogicImpl save";
}
}
XML配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
? ? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
? ? xsi:schemaLocation="http://www.springframework.org/schema/beans
? ? ? ? http://www.springframework.org/schema/beans/spring-beans.xsd"?
? ? ? ? default-autowire="constructor">
? ?<bean id="afterReturningAdvice" class="springAPI.MoocAfterReturningAdvice" ></bean>
? ? ? <bean id="beforeAdvice" class="springAPI.MoocBeforeAdvice" ></bean>
? ? ? <bean id="methodInterceptor" class="springAPI.MoocMethodInterceptor" ></bean>
? ? ? <bean id="throwsAdvice" class="springAPI.MoocThrowsAdvice" ></bean>
? ?<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
? ?<property name="mappedNames">
? ?<list>
? ?<value>sa*</value>
? ?</list>
? ?</property>
? ?</bean>
? ?<bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
? ?<property name="advice" ref="beforeAdvice"></property>
? ?<property name="pointcut" ref="pointcutBean"></property>
? ?</bean>
? ?<bean id="bizLogicImplTarget" class="springAPI.BizLogicImpl"></bean>
? ?<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
? ?<property name="target"><ref bean="bizLogicImplTarget"/></property>
? ?<property name="interceptorNames">
? ?<list>
? ?<value>afterReturningAdvice</value>
? ? ? <value>beforeAdvice</value>
? ? ? <value>methodInterceptor</value>
<!-- ? ? ? <value>throwsAdvice</value>
?--> ??
? ?</list>
? ?</property>
? ?</bean>?
? ?
?</beans>
測(cè)試:
@Test
public void testApi(){
ApplicationContext ac=new ClassPathXmlApplicationContext("spring-ioc.xml");
BizLogic bl=(BizLogic)ac.getBean("bizLogicImpl");
bl.save();
}
結(jié)果:
前置通知的方法:save ? springAPI.BizLogicImpl
環(huán)繞前通知實(shí)現(xiàn)了
BizLogicImpl: BizLogicImpl save
環(huán)繞后通知實(shí)現(xiàn)了
返回后通知方法執(zhí)行了save ?springAPI.BizLogicImpl ?null
查看全部 -
AspectJ介紹及Pointcut注解應(yīng)用
1、@AspectJ的風(fēng)格類似純java注解的普通java類。
2、Spring可以使用AspectJ來(lái)做切入點(diǎn)解析。
3、AOP的運(yùn)行時(shí)仍舊是純的SpringAOP,對(duì)AspectJ的編譯器或者織入無(wú)依賴性。
Spring中配置@AspectJ
1、對(duì)@AspectJ支持可以使用XML或者Java注解類型的配置(@EnableAspectJAutoProxy使用AspectJ進(jìn)行自動(dòng)代理)
2、前提:確保AspectJ的aspectjweaver.jar庫(kù)包含在應(yīng)用程序(版本1.6.8或更高版本)的classpath中,以Spring4.0.5為基準(zhǔn)的,其他版本可能不一樣(可以根據(jù)具體情況查找Spring具體文檔)。
aspect注解的具體使用
1、@AspectJ切面使用@Aspect注解配置,擁有@Aspect的任何bean將被Spring自動(dòng)識(shí)別并應(yīng)用。
2、@Aspect注解的類可以有方法和字段,他們也可能包括切入點(diǎn)(pointcut),通知(Advice)和引入(introduction)聲明。
3、@Aspect注解是不能夠通過(guò)類路徑自動(dòng)檢測(cè)發(fā)現(xiàn)的,所以需要配合使用@Component注釋或者在xml配置bean(@Aspect注解的類配置在xml中)。
4、一個(gè)類中的@Aspect注解標(biāo)識(shí)它為一個(gè)切面,并且將自己從自動(dòng)代理中排出。
如何定義一個(gè)切入點(diǎn)
1、一個(gè)切入點(diǎn)通過(guò)一個(gè)普通的方法定義來(lái)提供,并且切入點(diǎn)表達(dá)式使用@Pointcut注解,方法返回類型必須為void
2、定義一個(gè)名為'anyOldTransfer',這個(gè)切入點(diǎn)將匹配任何名為“transfer”的方法的執(zhí)行。
切入點(diǎn)支持哪些定義方式
組合Pointcut
1、切入點(diǎn)表達(dá)式可以通過(guò)&&、||和!進(jìn)行組合,也可以通過(guò)名字引用切入點(diǎn)表達(dá)式。
2、通過(guò)組合,可以建立更加復(fù)雜的切入點(diǎn)表達(dá)式。
如何定義良好的pointcuts
1、AspectJ是編譯期的AOP
2、檢查代碼并匹配連接點(diǎn)與切入點(diǎn)的代價(jià)是昂貴的。
3、一個(gè)好的切入點(diǎn)應(yīng)該包括以下幾點(diǎn)
【a】選擇特定類型的連接點(diǎn),如:execution,get,set,call,handler。
【b】確定連接點(diǎn)范圍,如:within,withincode。
【c】匹配上下文信息,如:this,target,@annotation。
查看全部 -
Advice定義及實(shí)例(AspectJ中advice的定義)
一、Before advice:對(duì)于切面類使用@Aspect修飾類,相當(dāng)于<aop:aspect>配置,在方法上添加@Before(“execution(* com.imooc.aop.aspectj.biz.*Biz.*(..))”),相當(dāng)于<aop:before>標(biāo)簽,@Component修飾類,省略了配置<bean>標(biāo)簽。
案例(前置通知):
步驟1:業(yè)務(wù)方法
public class MoocBiz {
public void print(){
System.out.println("業(yè)務(wù)方法執(zhí)行了");
}
}
步驟2:切面方法
@Aspect
public class MoocAspect {
@Before("execution(* *Biz.*(..))")
public void beforeAdvice(){
System.out.println("前置通知方法執(zhí)行了");
}
}
XML配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans?
? ? ? ? http://www.springframework.org/schema/beans/spring-beans.xsd ?
? ? ? ? http://www.springframework.org/schema/context
? ? ? ? http://www.springframework.org/schema/context/spring-context.xsd
? ? ? ? http://www.springframework.org/schema/aop?
? ? ? ? http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="src"></context:component-scan>
<bean id="moocBiz" class="MoocBiz"></bean>
<bean id="moocAspect" class="MoocAspect"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
測(cè)試:
@Test
public void testMooc(){
ApplicationContext ac=new ClassPathXmlApplicationContext("spring-ioc2.xml");
MoocBiz mb=(MoocBiz) ac.getBean("moocBiz");
mb.print();
}
結(jié)果:
前置通知方法執(zhí)行了
業(yè)務(wù)方法執(zhí)行了
可以定義共享的pointcut(一個(gè)方法上使用@Pointcut("execution(* com.imooc.aop.aspectj.biz.*Biz.*(..))"),然后@Before("pointcut()")修飾另一個(gè)方法上
@Aspect
public class MoocAspect {
@Pointcut("execution(* *Biz.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void beforeAdvice(){
System.out.println("前置通知方法執(zhí)行了");
}
}
二、After returning advice
和前置通知相似。
有時(shí)候需要在通知體內(nèi)得到返回的實(shí)際值,可以使用@AfterReturning綁定返回值的形式,如果知道返回值類型可以寫類型,如果不知道,可以寫Object。
后置通知案例:
public class MoocBiz {
public void print(){
System.out.println("業(yè)務(wù)方法執(zhí)行了");
}
}
@Aspect
public class MoocAspect {
@Pointcut("execution(* *Biz.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void beforeAdvice(){
System.out.println("前置通知方法執(zhí)行了");
}
@AfterReturning(pointcut="pointcut()",returning="o")
public void afterAdvice(Object o){
System.out.println("AfterReturning:"+o);
}
}
<context:component-scan base-package="src"></context:component-scan>
<bean id="moocBiz" class="MoocBiz"></bean>
<bean id="moocAspect" class="MoocAspect"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
結(jié)果:
前置通知方法執(zhí)行了
業(yè)務(wù)方法執(zhí)行了
AfterReturning:null
三、After throwing advice
有時(shí)候需要在通知體內(nèi)得到異常的實(shí)際值,可以使用@AfterReturning綁定返回值的形式
案例:
@AfterThrowing(pointcut="pointcut()",throwing="t")
public void afterThrowAdvice(RuntimeException t){
System.out.println("異常:"+t.getMessage());
}
public class MoocBiz {
public void print(){
System.out.println("業(yè)務(wù)方法執(zhí)行了");
throw new RuntimeException("運(yùn)行時(shí)異常");
}
}
結(jié)果:
前置通知方法執(zhí)行了
業(yè)務(wù)方法執(zhí)行了
異常:運(yùn)行時(shí)異常
四、After(finally)advice
最終通知必須準(zhǔn)備處理正常和異常兩種返回情況,它通常用于釋放資源。(類似于try-catch中finally的功能)
@After("pointcut()")
public void afterAdvice(){
System.out.println("后置通知執(zhí)行了");
}
前置通知方法執(zhí)行了
業(yè)務(wù)方法執(zhí)行了
后置通知執(zhí)行了
異常:運(yùn)行時(shí)異常
五、環(huán)繞通知(Around Advice)
1、環(huán)繞通知使用@Around注解來(lái)聲明,通知方法的第一個(gè)參數(shù)必須是ProceedingJoinPoint類型
2、在通知方法內(nèi)部會(huì)調(diào)用ProceedingJoinPoint的proceed()方法會(huì)導(dǎo)致執(zhí)行真正的方法,同時(shí)可以傳入一個(gè)Object[]對(duì)象,數(shù)組中的值將被作為參數(shù)傳遞給方法。
案例:
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("環(huán)繞前置通知方法執(zhí)行了========");
Object o=pjp.proceed();
System.out.println("環(huán)繞后置通知方法執(zhí)行了========");
return o;
}
結(jié)果:
環(huán)繞前置通知方法執(zhí)行了========
前置通知方法執(zhí)行了
業(yè)務(wù)方法執(zhí)行了
環(huán)繞后置通知方法執(zhí)行了========
后置通知執(zhí)行了
AfterReturning:null
查看全部 -
Advice擴(kuò)展
一、給advice傳遞參數(shù)
【a】方式一:在通知注解里寫參數(shù),通過(guò)&& args(參數(shù)名,..)傳入?yún)?shù),該參數(shù)可以是自己定義的類型,也可以為String等類型。(通常獲取參數(shù),進(jìn)行一些判斷或者日志的記錄)
【a】方式二:切入點(diǎn)和通知分開(kāi)寫的方式,在切入點(diǎn)注解里寫參數(shù),在通知注解里引用切入點(diǎn)注解的方法名。
【b】運(yùn)行時(shí),生效的一個(gè)注解,這兩個(gè)注解修飾在方法上,在通知注解里通過(guò)&& @annotation(運(yùn)行時(shí)注解修飾的方法名,首字母小寫),如果某個(gè)方法使用了運(yùn)行時(shí)注解,則一定會(huì)給value()附一個(gè)值,在通知方法里可以通過(guò)方法參數(shù),調(diào)用該方法得到該值(一般記錄該注解用了哪些方法,或者用在判斷該方法上是否加了某注解,后者判斷獲取到的值是哪值)
查看全部 -
Bean的生命周期:
分為4個(gè)階段
定義
初始化
使用
銷毀
查看全部 -
Bean的作用域
singleton:?jiǎn)卫?,指一個(gè)Bean容器中只存在一份。
prototype:每次請(qǐng)求(每次使用)創(chuàng)建新的實(shí)例,destroy方式不生效。
request:每次http請(qǐng)求創(chuàng)建一個(gè)實(shí)例且僅在當(dāng)前request內(nèi)有效。
session:同上,每次http請(qǐng)求擦黃健,當(dāng)前session內(nèi)有效。
global session:基于portlet的web中有效(portlet定義了global session),如果是在web中,同session
查看全部 -
Bean配置項(xiàng)
Id:在整個(gè)IoC容器中的唯一標(biāo)識(shí)。
Class:具體要實(shí)例化的哪個(gè)類。
Scope:作用域。
Constructor arguments:構(gòu)造器的參數(shù)。
Properties:屬性。
Autowiring mode:自動(dòng)裝配的模式。
lazy-initialization mode:懶加載模式。
Initialzation/destruction method:初始化/銷毀的方法。
理論上,處理class之外其他都可以不配置。但想要從Bean容器中得到某一個(gè)實(shí)例,有兩種方式,一種是通過(guò)Id,另一種是通過(guò)Bean 的類型。
查看全部 -
設(shè)值注入例子
調(diào)用set方法
查看全部
舉報(bào)