轉載請注明出處,否則侵權必究
-
Q:什么是AOP?
AOP:Aspect Oriented Programming,面向切面編程
還有POP面向過程(C,C++),OOP面向對象(JAVA),IOP面向接口
-
Q:什么是切面?
把業務主流程比成一個縱面的話,那么一些與主業務無關的功能作為橫切面插入系統各處,稱之為切面。
我們需要知道的還有:
連接點(Joinpoint):程序執行的某個特殊位置,如類初始化前,方法調用前。即切面可以切入的點
Spring僅支持方法的連接點,既僅能在方法調用前,方法調用后,方法拋出異常時等這些程序執行點進行織入增強。切點(Pointcut):就spring來說,每個方法都有很多連接點,但是只有某個連接點滿足指定要求時候,才能成為切點,即切面真正切入的地方。
如何使用表達式定義切入點,是AOP的核心,Spring默認使用AspectJ切入點語法。增強(Advice):即在切點做的一些事情,有“around”,“before”,“after”等類型
目標對象(Target) :被AOP框架進行增強處理的對象
-
Q:AOP的工作流程是什么樣的?
AOP是把已經編譯好的一段代碼拿過來,在前面或后面執行一些操作,然后合并成一個文件,編譯出來。
-
Q:AOP是如何配置的?
spring的配置文件
<!-- 第三方接口日志記錄 -->
<bean id="thirdBean" class="net.jqsoft.phimp.log.ThirdLogAspect" />
<!-- 配置切入點 -->
<aop:config>
<aop:aspect ref="thirdBean">
<aop:pointcut id="aspect-pointcut" expression="execution(* net.jqsoft.phimp.log.util.ThirdInterfaceServiceUtil.*(..))"/>
<!-- 增強 -->
<aop:around method="doAround" pointcut-ref="aspect-pointcut"/>
</aop:aspect>
</aop:config>
-
Q:execution表達式?
例: execution (* com.sample.service..*. *(..))
1、execution()::表達式主體。
2、第一個*號:表示返回類型, *號表示所有的類型。
3、包名:表示需要攔截的包名,后面的兩個句點表示當前包和當前包的所有子包,com.sample.service包、子孫包下所有類的方法。
4、第二個*號:表示類名,*號表示所有的類。
5、*(..):最后這個星號表示方法名,*號表示所有的方法,后面括弧里面表示方法的參數,兩個句點表示任何參數
-
Q:AOP有幾種通知方式?
前置通知:在我們執行目標方法之前運行(@Before)
<aop:before method="beginTransaction" pointcut-ref="perform"/>
后置通知:在我們目標方法運行結束之后,不管有沒有異常(@After)
<aop:after method="finallyMethod" pointcut-ref="perform"/>
返回通知:在我們的目標方法正常返回值后運行(@AfterReturning)
<aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>
異常通知:在我們的目標方法出現異常后運行(@AfterThrowing)
<aop:after-throwing method="throwingMethod" throwing="ex" pointcut-ref="perform"/>
環繞通知:環繞通知圍繞在連接點前后,比如一個方法調用的前后。這是最強大的通知類型,能在方法調用前后自定義一些操作。
<aop:around method="aroundMethod" pointcut-ref="perform"/>
-
Q:當一個方法被多個切面切入時,執行順序?
aspect1 和 aspect2 的執行順序也是未定的
如何指定每個 aspect 的執行順序
- 實現org.springframework.core.Ordered接口,實現它的getOrder()方法
- 給aspect添加@Order注解,該注解全稱為:org.springframework.core.annotation.Order
不管采用上面的哪種方法,都是值越小的 aspect 越先執行。
@Component
@Aspect
@Order(1)
public class FirstAspect{
@Before(value="execution(* com.xj.mvc.service..*.*(..))")
public void befor(){
System.out.println("firstAspect");
}
}
@Component
@Aspect
public class FirstAspect implements Ordered{
@Before(value="execution(* com.xj.mvc.service..*.*(..))")
public void befor(){
System.out.println("firstAspect");
}
public int getOrder() {
return 1;
}
-
Q:AOP的實現原理?
動態代理
-
Q:什么叫代理?
Proxy
通俗的說,讓別人幫助你做你并不關心的事情,叫做代理,生活中常見的如中介
代理模式的定義:給某一個對象提供一個代理,并由代理對象控制對原對象的引用
代理模式的通用類圖
Subject:抽象主題角色,可以是抽象類,也可以是接口
RealSubject:具體主題角色,也叫做被委托角色或被代理角色,是業務邏輯的具體執行者
Proxy:代理主題角色,也叫做委托類或代理類,負責對真實角色的應用,把所有抽象主題定義的方法委托給真實主題角色實現
(太拗口了!)
舉個例子:我們通過手機APP買火車票,我們知道APP并不賣票,只有火車站才真正賣票,APP只是代理,APP買的票其實是到火車站買的
那么,我們就是客戶,APP就是代理角色,火車站是具體主題角色,賣票稱為抽象主題角色
/**
* 抽象主題角色
*/
public interface ISubject {
//買票
void buyTicket();
}
/**
* 具體主題角色
*/
public class RealSubjectImpl implements ISubject {
//去火車站售票點買
@Override
public void buyTicket() {
System.out.println("買火車票!");
}
}
/**
* 代理角色 APP
*/
public class ProxySubjectImpl implements ISubject {
private ISubject real;
//構造注入,注入具體主題類,構造函數傳入的是接口類型的參數,是因為不知道具體的具體主題類是哪一個,所以傳一個接口類型的
public ProxySubjectImpl(ISubject real) {
this.real = real;
}
public void before(){
System.out.println("app付錢!");
}
@Override
public void buyTicket() {
before();
this.real.buyTicket();
}
}
public class ProxyTest {
/**代理模式
* @param args
*/
public static void main(String[] args) {
//實現了APP代理買火車票
ISubject sub = new ProxySubjectImpl(new RealSubjectImpl());
sub.buyTicket();
}
}
打印結果:
app付錢!
買火車票!
-
Q:什么叫靜態代理?與動態代理的區別
動態代理和靜態代理都是對目標方法進行增強,而且讓增強的動作和目標動作分開,達到解耦的目的
靜態代理:
顯示聲明代理對象,在編譯期間就生成了代理類
由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行之前,代理類的類文件就已經創建好了
優點:簡單,實用,高效
缺點:
1.由于靜態代理中的代理類是針對某一個類去做代理的,假設系統中有一百個service,則需要創建100個代理類
2.如果一個service中有很多方法需要事務,發現代理對象中的方法有很多重復代碼。
即重用性不強
動態代理:
在程序運行時通過反射機制動態的創建代理類
優點:靈活,減少代碼冗余
缺點:效率較低
-
Q:動態代理的實現
-
基于JDK實現:
jdk動態代理是JRE提供給我們的類庫,可以直接使用,不依賴第三方
利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理
中間主要使用到了一個接口InvocationHandler與Proxy.newProxyInstance靜態方法
//接口 抽象主題角色
public interface PersonService {
public String savePerson();
public void upodatePerson();
public void deletePerson();
}
//具體主題角色
public class PersonServiceImpl implements PersonService{
@Override
public String savePerson() {
System.out.println("添加");
return "保存成功!";
}
@Override
public void upodatePerson() {
System.out.println("修改");
}
@Override
public void deletePerson() {
System.out.println("刪除");
}
}
//增強類
public class MyTransaction {
public void beginTransaction(){
System.out.println("開啟事務");
}
public void commit(){
System.out.println("提交事務");
}
}
//動態代理類
public class PersonServiceInterceptor implements InvocationHandler {
//目標類
private Object target;
//增強類
private MyTransaction myTransaction;
//構造函數注入目標類和增強類
public PersonServiceInterceptor(Object target, MyTransaction myTransaction) {
this.target = target;
this.myTransaction = myTransaction;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.myTransaction.beginTransaction();
Object returnValue = method.invoke(this.target,args);
this.myTransaction.commit();
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
//目標類 具體主題角色
Object target = new PersonServiceImpl();
//增強類
MyTransaction myTransaction = new MyTransaction();
//組裝
PersonServiceInterceptor interceptor = new PersonServiceInterceptor(target,myTransaction);
PersonService personService = (PersonService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),interceptor);
personService.upodatePerson();
System.out.println(personService instanceof Proxy);
}
}
//運行結果
開啟事務
修改
提交事務
true
使用內置的Proxy實現動態代理有一個問題:被代理的類必須實現接口,未實現接口則沒辦法完成動態代理
如果項目中有些類沒有實現接口,則不應該為了實現動態代理而刻意去抽出一些沒有實例意義的接口,通過cglib可以解決該問題。
-
CGLIB代理
強制使用CGlib
<!-- proxy-target-class="false"默認使用JDK動態代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
是cglib的jar包實現的
cglib動態代理產生的代理對象是目標對象的子類,并覆蓋其中的方法,這種通過繼承類的實現方式,不能代理final修飾的類。
代碼就不貼了,就是Proxy動態代理類實現MethodInterceptor接口(net.sf.cglib.proxy.MethodInterceptor),重寫intercept方法調用methodProxy.invoke(targetObject, args)方法
-
總結
1.JDK代理使用的是反射機制實現aop的動態代理,CGLIB代理使用字節碼處理框架asm,通過修改字節碼生成子類。所以jdk動態代理的方式創建代理對象效率較高,執行效率較低,cglib創建效率較低,執行效率高;
2.JDK動態代理機制是委托機制,具體說動態實現接口類,在動態生成的實現類里面委托hanlder去調用原始實現類方法,CGLIB則使用的繼承機制,具體說被代理類和代理類是繼承關系,所以代理類是可以賦值給被代理類的,如果被代理類有接口,那么代理類也可以賦值給接口。