Spring的兩大特性是IOC和AOP IOC負責將物件動態的注入到容器,從而達到一種需要誰就注入誰,什麼時候需要就什麼時候注入的效果。理解spring的ioc也很重要。 但是今天主要來和大家講講aop。 AOP 廣泛應用於處理一些具有橫切性質的系統級服務,AOP 的出現是對 OOP 的良好補充,用於處理系統中分佈於各個模組的橫切關注點,比如事務管理、日誌、快取等等。
AOP實現的關鍵在於AOP框架自動建立的AOP代理。
AOP代理主要分為靜態代理和動態代理,
- 靜態代理的代表為AspectJ;
- 動態代理則以Spring AOP為代表
1,AspectJ
AspectJ是靜態代理的增強,採用編譯時生成 AOP 代理類,因此也稱為編譯時增強,具有更好的效能。 缺點:但需要使用特定的編譯器進行處理
2,Spring AOP
Spring AOP使用的動態代理,執行時生成 AOP 代理類,所謂的動態代理就是說AOP框架不會去修改位元組碼,而是在記憶體中臨時為方法生成一個AOP物件,這個AOP物件包含了目標物件的全部方法,並且在特定的切點做了增強處理,並回撥原物件的方法。 缺點:由於 Spring AOP 需要在每次執行時生成 AOP 代理,因此效能略差一些。
由於aspectj的使用還需要使用特定的編譯器進行處理,處理起來有點麻煩。今天重要來講解Spring AOP
Spring AOP動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。
- JDK動態代理通過反射來接收被代理的類,並且要求被代理的類必須實現一個介面。JDK動態代理的核心是InvocationHandler介面和Proxy類。
- 如果目標類沒有實現介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個程式碼生成的類庫,可以在執行時動態的生成某個類的子類(通過修改位元組碼來實現代理)。 注意,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。 jdk和cglib動態代理來共同實現我們的aop面向切面的功能。
下面就來用簡單的程式碼來演示下jdk和cglib動態代理的實現原理。
一,jdk動態代理實現AOP攔截
- 1,為target目標類定義一個介面JdkInterface,這是jdk動態代理實現的前提
/**
* Created by qcl on 2018/11/29
* desc: jdk動態aop代理需要實現的介面
*/
public interface JdkInterface {
public void add();
}
複製程式碼
- 2,用我們要代理的目標類JdkClass實現上面我們定義的介面,我們的實驗目標就是在不改變JdkClass目標類的前提下,在目標類的add方法的前後實現攔截,加入自定義切面邏輯。這就是aop的魅力所在:程式碼與程式碼之間沒有耦合。
/**
* Created by qcl on 2018/11/29
* desc: 被代理的類,即目標類target
*/
public class JdkClass implements JdkInterface {
@Override
public void add() {
System.out.println("目標類的add方法");
}
}
複製程式碼
-3 ,到了關鍵的一步,用我們的MyInvocationHandler,實現InvocationHandler介面,並且實現介面中的invoke方法。仔細看invoke方法,就是在該方法中加入切面邏輯的。目標類方法的執行是由mehod.invoke(target,args)這條語句完成。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Created by qcl on 2018/11/29
* desc:這裡加入切面邏輯
*/
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before-------切面加入邏輯");
Object invoke = method.invoke(target, args);//通過反射執行,目標類的方法
System.out.println("after-------切面加入邏輯");
return invoke;
}
}
複製程式碼
- 4,測試結果
/**
* Created by qcl on 2018/11/29
* desc:測試
*/
public class JdkTest {
public static void main(String[] args) {
JdkClass jdkClass = new JdkClass();
MyInvocationHandler handler = new MyInvocationHandler(jdkClass);
// Proxy為InvocationHandler實現類動態建立一個符合某一介面的代理例項
//這裡的proxyInstance就是我們目標類的增強代理類
JdkInterface proxyInstance = (JdkInterface) Proxy.newProxyInstance(jdkClass.getClass().getClassLoader(),
jdkClass.getClass()
.getInterfaces(), handler);
proxyInstance.add();
//列印增強過的類型別
System.out.println("=============" + proxyInstance.getClass());
}
}
複製程式碼
執行上面測試類可以得到如下結果
可以看到,目標類的add方法前後已經加入了自定義的切面邏輯,AOP攔截機制生效了。再看class com.sun.proxy.$Proxy0。這裡進一步證明__JDK動態代理的核心是InvocationHandler介面和Proxy類__二,cglib動態代理實現AOP攔截
- 1,定義一個要被代理的Base目標類(cglib不需要定義介面)
/**
* Created by qcl on 2018/11/29
* desc:要被代理的類
*/
public class Base {
public void add(){
System.out.println("目標類的add方法");
}
}
複製程式碼
- 2,定義CglibProxy類,實現MethodInterceptor介面,實現intercept方法。該代理的目的也是在add方法前後加入了自定義的切面邏輯,目標類add方法執行語句為proxy.invokeSuper(object, args)
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Created by qcl on 2018/11/29
* desc:這裡加入切面邏輯
*/
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
System.out.println("before-------切面加入邏輯");
methodProxy.invokeSuper(object, args);
System.out.println("after-------切面加入邏輯");
return null;
}
}
複製程式碼
- 3,測試類
/**
* Created by qcl on 2018/11/29
* desc:測試類
*/
public class CglibTest {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Base.class);
//回撥方法的引數為代理類物件CglibProxy,最後增強目標類呼叫的是代理類物件CglibProxy中的intercept方法
enhancer.setCallback(proxy);
//此刻,base不是單車的目標類,而是增強過的目標類
Base base = (Base) enhancer.create();
base.add();
Class<? extends Base> baseClass = base.getClass();
//檢視增強過的類的父類是不是未增強的Base類
System.out.println("增強過的類的父類:"+baseClass.getSuperclass().getName());
System.out.println("============列印增強過的類的所有方法==============");
FanSheUtils.printMethods(baseClass);
//沒有被增強過的base類
Base base2 = new Base();
System.out.println("未增強過的類的父類:"+base2.getClass().getSuperclass().getName());
System.out.println("=============列印增未強過的目標類的方法===============");
FanSheUtils.printMethods(base2.getClass());//列印沒有增強過的類的所有方法
}
}
複製程式碼
下面是列印結果
通過列印結果可以看到- cglib動態的攔截切入成功了
- cglib動態代理的方式是在執行時動態的生成目標類(Base)的子類,並且在目標類現有方法的基礎上新增了很多cglib特有的方法。 下面貼出用反射列印類所有方法的工具類
public class FanSheUtils {
//列印該類的所有方法
public static void printMethods(Class cl) {
System.out.println();
//獲得包含該類所有其他方法的陣列
Method[] methods = cl.getDeclaredMethods();
//遍歷陣列
for (Method method : methods) {
System.out.print(" ");
//獲得該方法的修飾符並列印
String modifiers = Modifier.toString(method.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
//列印方法名
System.out.print(method.getName() + "(");
//獲得該方法包含所有引數型別的Class物件的陣列
Class[] paramTypes = method.getParameterTypes();
//遍歷陣列
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) {
System.out.print(",");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
}
複製程式碼
注意:上面用到了cglib.jar和asm.jar。在我們的maven的pom.xml裡引入下面類庫即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
複製程式碼
by年糕媽媽qcl