在我另一篇部落格SSM之Spring 02 —— 動态代理、AOP、Spring-MyBatis、聲明式事務中有簡單描述代理模式和AOP原理,當時自己也還是有些沒明白,後面在面試過程中被問到,這裡詳細說明一下。
AOP是什麼就不再說明了,主要解釋下AOP的原理——動态代理。
動态代理
和靜态代理不同,動态代理是動态生成代理對象,克服了靜态代理的問題——不同目标對象都需要寫一個代理類。
動态代理主要有兩種方式:JDK的動态代理和cglib代理。
JDK的動态代理
它是對對象進行代理,底層利用反射機制實作,同時需要我們自定義一個實作了InvocationHandler接口的類,這個接口中的invoke方法就是AOP的本質。
下面我們通過代碼來進行了解。
1、定義好UserDao以及實作類,這是我們常見的方式。
//為了友善就不寫參數了
public interface UserDao {
boolean addUser();
boolean deleteUser();
}
public class UserDaoImpl implements UserDao {
@Override
public boolean addUser() {
System.out.println("addUser();");
return false;
}
@Override
public boolean deleteUser() {
System.out.println("deleteUser();");
return true;
}
}
2、寫一個測試,具體解釋看注解。可以看到Proxy和InvocationHandler都是reflect包下的,說明底層肯定是反射。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//建立好目标對象
UserDao ud = new UserDaoImpl();
//這個就是我們自定義的一個實作類,裡面invoke方法就是AOP的原理。
InvocationHandler ih = new MyInvocationHandler(ud);//将ud作為目标對象傳進去
/*
這個方法傳回一個代理對象,我們會在代理對象中做一些其他操作
newProxyInstance(ClassLoader, interfaces, InvocationHandler)
1、ClassLoader:目标對象的類加載器,代碼是固定的。
2、interfaces:目标對象的接口數組,代碼也是固定的,都是通過反射獲得。
3、InvocationHandler:這是關鍵點,AOP能實作方法的增強就在這個接口裡。需要自己定義。
*/
Object newProxyInstance = Proxy.newProxyInstance(ud.getClass().getClassLoader(),
ud.getClass().getInterfaces(), ih);
//執行代理對象被增強後的方法(增強的意思是在原有方法的基礎上添加了其他效果,同時不影響原有業務,同Spring AOP的事務)
UserDao proxyUserDao = (UserDao)newProxyInstance;
proxyUserDao.addUser();
System.out.println("--------------");
proxyUserDao.deleteUser();
}
}
3、自定義的實作類,這是實作AOP增強方法的關鍵。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
/**
*
* @param proxy 代理對象,就是我們從外部傳進來的,但一般不用這個,會用我們自定義的object成員。
* @param method 目标對象的方法對象,是JDK傳過來的。
* @param args 目标對象方法的參數對象。
* @return 對應于我們目标對象的方法的傳回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String before = "";
String after = "";
//這裡通過反射來判斷UserDao的兩個方法,如果不判斷,那麼就會針對目标對象的所有方法進行增強。
if(method.getName().equals("addUser")){
before = "前置通知:addUser()方法執行 之前";
after = "後置通知:addUser()方法執行 之後";
}
if(method.getName().equals("deleteUser")){
before = "前置通知:deleteUser()方法執行 之前";
after = "後置通知:deleteUser()方法執行 之後";
}
//下面這樣就實作了和Spring AOP 一樣的效果:方法的增強,即在方法執行前做一些其他事,且不影響到原有業務。
System.out.println(before);
Object result = method.invoke(object, args);//通過反射調用目标對象的方法
System.out.println(after);
return result;
}
}
4、運作結果:

為什麼說JDK是面向接口的?
這裡的接口不是指InvocationHandler,而是指UserDao,我們看看通過
newProxyInstance
得帶的代理類的父類。
顯然,隻要我們通過JDK的Proxy代理獲得的類,都會繼承Proxy,那麼我們是如何能夠讓這個類有UserDaoImpl的方法的呢?因為這個類繼承了UserDao。
這也就解釋了,為什麼我們說JDK動态代理是面向接口的。因為Java是單繼承,而我們通過JDK動态代理得到的代理都會繼承Proxy,同時我們要表明這個代理對象是UserDaoImpl的執行個體,那麼就隻能通過接口的方式。
是以,如果我們有實作類(例如UserDaoImpl),就可以考慮到JDK動态代理。這也是Spring AOP的預設實作方式,我們一般都是用實作類。
cglib
和JDK動态代理不同,cglib(code generator library 代碼生成庫)代理的是位元組碼對象(即Class),而JDK動态代理代理的是對象。
步驟:
- cglib生成一個位元組碼對象(即Class對象),内容是空的。
- 設定位元組碼對象的父類為目标對象,即UserDaoImpl。
- 通過位元組碼對象中繼承了目标對象的方法,即addUser,進行回調,調用父類的addUser,并在執行其方法前完成方法增強。
- 建立代理對象,通過代理對象執行Class的方法。
注意:cglib是Spring中的,是以需要導入Spring核心包才能使用。
導入進來
下面進行代碼說明:
這裡面CallBack就是AOP的原理,它通過子類調用父類UserDaoImpl的addUser方法,并在調用時完成增強。
package test02_AOP.cglib;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();//空的位元組碼對象
enhancer.setSuperclass(UserDaoImpl.class);//設定父類
Callback callback = new MyCallback();
enhancer.setCallback(callback);//設定回調
UserDao userDao = (UserDao) enhancer.create();//用代理類Enhancer 建立代理對象
userDao.addUser();
}
}
package test02_AOP.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//本來是繼承Callback接口,表示回調
//但Callback接口裡什麼都沒有,隻是起到一個标志作用,是以繼承其子類MethodInterceptor
//MethodInterceptor接口僅有一個方法,攔截我們待增強的方法
public class MyCallback implements MethodInterceptor {
/**
*
* @param proxy 代理對象,即前面圖裡面的 class XX extends UserDaoImpl的XX對象
* @param method 目标對象的方法
* @param args 目标對象中的 方法對象 的參數對象(相當于addUser方法的參數)
* @param methodProxy 代理對象中的 方法代理對象(相當于父類UserDaoImpl的addUser方法)
* @return 傳回代理對象方法的結果
* @throws Throwable
*/
@Override
public Object intercept(
Object proxy, Method method, Object[] args, MethodProxy methodProxy
) throws Throwable {
System.out.println("前置通知");
//執行原來的方法
Object object = methodProxy.invokeSuper(proxy, args);//這裡不能用invoke,相當于前面圖裡super.addUser()
System.out.println("後置通知");
return object;
}
}
結果:
總結
AOP在Spring中實作:
1、注解方式
- 有一個注解@EnableAspectJAutoProxy(ProxyTargetClass = false)
- 預設為false。如果是實作類(實作),就會用jdk,如果沒有實作接口 ,就會用 cglib。
- 如果是true。無論是否實作接口,都會使用 cglib。
Spring —— AOP原理(JDK、cglib動态代理)、為什麼JDK動态代理是面向接口?動态代理
2、XML方式
3、JDK和cglib小結
- JDK動态代理是面向接口的。
- cglib是通過位元組碼底層繼承被代理類,然後重寫父類方法(如果被代理類繼承final則會失敗)
- 如果被代理的對象是一個實作類(例如UserDaoImpl),那麼Spring AOP會預設用JDK動态代理,否則使用cglib。
- 性能比較:(參考部落格 https://blog.csdn.net/xlgen157387/article/details/82497594)
-
關于兩者之間的性能的話,JDK動态代理所建立的代理對象,在以前的JDK版本中,性能并不是很高,雖然在高版本中JDK動态代理對象的性能得到了很大的提升,但是他也并不是适用于所有的場景。主要展現在如下的兩個名額中:
1、CGLib所建立的動态代理對象在實際運作時候的性能要比JDK動态代理高不少,有研究表明,大概要高10倍;
2、但是CGLib在建立對象的時候所花費的時間卻比JDK動态代理要多很多,有研究表明,大概有8倍的差距;
3、是以,對于singleton的代理對象或者具有執行個體池的代理,因為無需頻繁的建立代理對象,是以比較适合采用CGLib動态代理,反正,則比較适用JDK動态代理。
-
經過原部落客的實驗後得出結果:(具體請參考原部落格)
最終的測試結果大緻是這樣的,在1.6和1.7的時候,JDK動态代理的速度要比CGLib動态代理的速度要慢,但是并沒有教科書上的10倍差距,在JDK1.8的時候,JDK動态代理的速度已經比CGLib動态代理的速度快很多了。
-
原部落客下的一個評論(曾經的随性的回複)
jdk的版本優化中主要是針對虛拟機對反射調用的優化,在jdk1.6中,我們采用JDK代理的方式來生成動态代理類,反射方法的調用在15次以内是調用本地方法,即是java到c++代碼轉換的方法,這種方式比直接生成位元組碼檔案要快的多,而在15次之後則開始使用java實作的方式。而在1.8的版本優化中,反射調用的次數達到門檻值[也就是發射調用的類成為熱點時]之後采用位元組碼的方式,因為位元組碼的 方式隻有在第一次生成位元組碼檔案時比較消耗時間。
-