關於java的動態代理,首先我們需要了解與之相匹配的設計模式—代理模式。而對於創建代理類的時間點,又可以分爲靜態代理和動態代理。
一、代理模式
代理模式是常用的java設計模式,它的特徵是代理類與委託類有同樣的接口,代理類負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類並不真正實現服務,而是具有委託類的實例對象,通過委託類對應的實例對象調用委託類的相關方法,來提供特定的服務。通俗的說,代理類就相當於一箇中間商,負責對兩邊的對象的操作進行響應。
二、靜態代理
- 靜態代理是在程序員編程時由程序員創建,也就是在編譯時就已經把代理類、接口等確定下來,在程序運行,代理類的.class文件已經生成。
下面舉個簡單的例子,Person實現輸出一句話,而我們通過代理類的方法來調用對應Person類的方法,達到輸出結果。
爲什麼不直接訪問Person類的方法,還需要多次一舉的建一個代理類呢?因爲這種間接性可以帶來其他用途,我們可以在調用對應方法之前或之後處理一些需要操作,與之聯繫的就是AOP編程,我們能在一個切點之前或之後執行一些操作,切點就是方法。
Person 接口:
public interface Person {
void sayPerson();
}
PersonImpl 實現類:
public class PersonImpl implements Person{
@Override
public void sayPerson() {
System.out.println("這是Person類的實現。。。");
}
}
Person靜態代理類:
public class StaticProxy implements Person{
Person person;
public StaticProxy(Person person) {
this.person = person;
}
@Override
public void sayPerson() {
System.out.println("代理類...");
person.sayPerson();
}
}
Main方法:
public class ProxyMain {
public static void main(String[] args) {
Person person = new PersonImpl();
//將person類傳到靜態代理類中
StaticProxy staticProxy = new StaticProxy(person);
//通過訪問靜態代理類的sayPerson方法調用person類的方法
staticProxy.sayPerson();
}
}
運行結果:
三、JDK Proxy動態代理
- 動態代理是在運行時進行創建,和靜態代理不同,動態代理創建的代理類存在於java虛擬機中,即在內存中。上面的靜態代理例子中,代理類是自己定義的,在程序運行之前已經編譯完成,我們可以到具體的編譯輸出目錄查找到.class文件。然而動態代理,代理類不是在代碼中定義好的,而是在運行時動態生成的。
- 相比於靜態代理,動態代理的優勢在於很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。
- 動態代理我們需要使用到java.lang.reflect.Proxy類 和 InvocationHandler接口,通過這個類和接口可以生成JDK動態代理類和代理對象。
下面舉個簡單例子:
Person 接口:
public interface Person {
void sayPerson();
}
Student類,實現Person接口:
public class Student implements Person{
@Override
public void sayPerson() {
System.out.println("我是學生,實現了Person類。。。");
}
}
自定義一個類實現InvocationHandler接口,重寫invoke方法,invoke方法中定義我們具體需要的操作和代理方法的調用。
public class MyInvocationHandler<T> implements InvocationHandler {
T target;
public MyInvocationHandler(T target) {
this.target = target;
}
/**
*
* @param proxy 代表動態代理對象
* @param method 代表執行的方法
* @param args 代表執行方法的參數
* @return java.lang.Object
* @exception
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執行"+method.getName()+"方法");
Object result = method.invoke(target, args);
return result;
}
}
Main方法:定義代理對象,並將該對象傳入自定義的invocationHandler構造器,創建一個與代理對象相關聯的invocationHandler,接下來使用Proxy類,創建一個代理對象。
public class DynamicMain {
public static void main(String[] args) {
//創建一個學生實例
Person student = new Student();
//創建一個與代理對象相關聯的invocationHandler
MyInvocationHandler<Person> myInvocationHandler = new MyInvocationHandler<>(student);
//Proxy創建一個代理對象來代理student,代理對象的每個執行方法都會替換執行invocationHandler中的invoke方法
Person o = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, myInvocationHandler);
o.sayPerson();
}
}
運行結果:
四、JDK Proxy動態代理原理分析
上述寫了一個簡單的動態代理例子,下面進行原理分析,首先從main方法的創建代理對象開始入手。代碼中是利用Proxy.newProxyInstance來進行創建代理對象,我們查看該方法的源碼。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
其中,重要的關鍵步驟有如下幾步,通過main方法中對應newProxyInstance方法的傳入參數(代理類的類加載器、代理對象需要實現的接口、InvocationHandler),在運行時動態生成代理對象。
//接口類的克隆
final Class<?>[] intfs = interfaces.clone();
/*
* 創建和查找代理類
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
//獲得代理類的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
//通過代理類的構造器創建實例對象
return cons.newInstance(new Object[]{h});
因爲是運行時動態生成的,.class文件緩存在java虛擬機中,這個文件中的內容就很重要,負責代理我們定義好的內容。具體查看的方法,可以通過ProxyGenerator.generateProxyClass
打印到文件中查看。另外,我再下面cglib分析中介紹另外一種方法,具體的分析過程也類似。
注意:newProxyInstance中的Class<?>[] interfaces參數必須是接口,如果寫成類,將出現如下錯誤,並且我們從這個參數名上就可以看出,它需要傳入的是接口數組
Exception in thread "main" java.lang.IllegalArgumentException: com.hcx.dynamic.Student is not an interface
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:590)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
五、cglib 動態代理
正是因爲jdk 的Proxy方式代理的參數中必須是實現對應接口,這樣沒有實現接口的類就無法使用JDK Proxy實現代理,所以另外一種代理出現了,cglib。
cglib(Code Generation Library)是一個基於ASM的字節碼生成庫。它允許我們在運行時對字節碼進行修改和動態生成。cglib是通過繼承來實現代理,具體後面會通過生成的代理類class文件分析。
下面也是通過一個例子來開始:
直接定義一個Person類,不定義接口
public class Person {
public void sayStudent(){
System.out.println("這是Person類中的學生...");
}
public void sayTeacher(){
System.out.println("這是Person類中的老師...");
}
}
自定義MethodInceptor方法,實現MethodInterceptor接口,重寫其中的intercept方法,方法中是具體代理的操作。intercept方法的四個參數:
- o 表示增強型的對象,即表示cglib代理的子類對象。不懂沒關係,繼續看後面的,看完回頭想一下就明白了。(後續會說到增強型和不增強的區別和用途)
- method 表示要攔截的方法;
- objects 表示被攔截方法的參數;
- methodProxy 表示要觸發父類的方法對象。
methodProxy.invokeSuper(o,objects)調用對應的目標方法。
public class MyMethodInceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("動態代理方法進入。。。");
return methodProxy.invokeSuper(o,objects);
}
}
Main方法:
public class CglibMain {
public static void main(String[] args) {
//創建Enhancer對象
Enhancer enhancer = new Enhancer();
//設置父類,即要代理的類
enhancer.setSuperclass(Person.class);
//設置回調對象
enhancer.setCallback(new MyMethodInceptor());
//創建代理類
Person student = (Person) enhancer.create();
//通過代理類調用目標方法
student.sayStudent();
}
}
運行結果:代理類完成目標方法的代理,並在調用之前完成其他內容的操作,從這我們也可以看到AOP的操作。
六、cglib 動態代理原理分析
下面我們查看源碼,分析cglib具體的實現步驟。
首先是Enhancer類,cglib的Enhancer類用來指定要代理的目標對象(上述的Person類)和實際處理操作的對象(上述的MyMethodInceptor類),最後調用create()方法創建代理對象,對這個對象所以非final方法的調用都會轉發到intercept方法中。
下面的create方法主要完成的是對createHelper的調用。
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}
查看createHelper方法,代碼如下:
private Object createHelper() {
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
preValidate方法進行校驗;
KEY_FACTORY.newInstance 創建EnhancerKey對象,作爲super.create(key)參數,創建代理對象的參數。
查看super.create(key) 的 create方法,代碼如下:
protected Object create(Object key) {
try {
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
data = new ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
創建代理對象在nextInstance方法中,該方法是AbstractClassGenerator.java類的一個方法,具體實現是在子類Enhancer中。子類中方法源碼如下:
protected Object nextInstance(Object instance) {
EnhancerFactoryData data = (EnhancerFactoryData) instance;
if (classOnly) {
return data.generatedClass;
}
Class[] argumentTypes = this.argumentTypes;
Object[] arguments = this.arguments;
if (argumentTypes == null) {
argumentTypes = Constants.EMPTY_CLASS_ARRAY;
arguments = null;
}
return data.newInstance(argumentTypes, arguments, callbacks);
}
- argumentTypes 是代理對象的構造器類型
- arguments 是代理對象的構造方法參數
- callbacks 是對應回調對象
data.newInstance的方法源碼如下,通過ReflectUtils反射生成代理對象,往下查看源碼,最後會調用到Unsafe類的分配對象方法,最後將生成的代理對象返回,通過強轉轉換爲我們定義的Person代理對象。
return UnsafeFieldAccessorImpl.unsafe.allocateInstance(this.constructor.getDeclaringClass());
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
setThreadCallbacks(callbacks);
try {
// Explicit reference equality is added here just in case Arrays.equals does not have one
if (primaryConstructorArgTypes == argumentTypes ||
Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
// If we have relevant Constructor instance at hand, just call it
// This skips "get constructors" machinery
return ReflectUtils.newInstance(primaryConstructor, arguments);
}
// Take a slow path if observing unexpected argument types
return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
} finally {
// clear thread callbacks to allow them to be gc'd
setThreadCallbacks(null);
}
}
七、代理對象調用過程分析
上面我們對創建代理對象過程進行了分析,下面我們就對緩存在java虛擬機中的代理類調用過程進行分析,即我們調用具體目標方法是怎麼一個過程,還有就是上文留下來的問題,增強型對象和不增強的用途。
要分析調用過程,我們首先要知道在內存中的代理類中具體是什麼內容,我們需要看到裏面的內容,可以使用如下方法。
(1)進入我們上面cglib例子的工程目錄,打開cmd,輸入命令:(注意java環境變量和自己安裝jdk的位置)
java -classpath "C:\Program Files\Java\jdk1.8.0_231\lib\sa-jdi.jar" sun.jvm.hotspot.HSDB
(2)輸入完後,會跳出一個java工具,選擇File ----> Attach to HotSpot process 會要求輸入進程號,這時候我們再開一個cmd命令框,輸入jps -l 顯示在運行的java進程, 我們可以在剛纔cglib程序斷點進入調試狀態,不讓它運行完,運行完就沒有進程id了
(3)點ok後,我們點Tool ---->Class Browser,在輸入框輸入上文的Person類,可以看到具體下面搜索出來的內存中的代理類。點擊具體的類後單獨創建.class文件或點擊Create .class for all classes便可以在改工程目錄下創建.class文件。創建完成後,可以使用反編譯工具查看裏面的具體內容(工具有JD-GUI
、luyten
等)
(4)可以從下圖看到,main方法進入後,student對象的類名是com.hcx.com.hcx.cglib.Person$$EnhancerByCGLIB$$fa76b0ae@4667ae56
,並且CGLIB$CALLBACK_0
值爲我們上文自定義的MyMethodInceptor
類。
(5)知道這兩個後,再結合(3)這步生成的class文件,我們查看com.hcx.com.hcx.cglib.Person$$EnhancerByCGLIB$$fa76b0ae@4667ae56
這個class文件。可以看到上文我們說到的cglib是通過繼承的方式實現代理的,繼承我們定義的類,從而可以利用java繼承的特性完成目標方法的調用。因爲main方法中調用的是sayStudent方法,所以看下面的圖。看到步驟(4)中說到的參數CGLIB$CALLBACK_0
,如果這個參數不爲null,就調用MyMethodInceptor
的intercept方法,如果沒有設置這個回調參數,則直接調用父類的目標方法。
(6)因爲我們在main方法中setCallback設置了回調對象,所以接下來走到MyMethodInceptor的intercept方法中,在該方法中又調用了invokeSuper方法。那麼繼續走,走到invokeSuper中,如下圖,可以看到obj是我們剛纔調用目標方法的類名,經過init初始化後,fci.f2是Person$$EnhancerByCGLIB$$fa76b0ae$$FastClassByCGLIB$$
這個類,fci.i2值爲15。
(7)上一步驟知道後,我們可以在(3)步驟中找到並編譯對應的class文件,如下圖。因爲上一步調用的是fci.f2.invoke方法,所以在Person$$EnhancerByCGLIB$$fa76b0ae$$FastClassByCGLIB$$
類中找到invoke方法,根據上一步傳參可以知道fci.i2就是這裏的n,值爲15,根據n的值匹配,調用person$$EnhancerByCGLIB$$fa76b0ae.CGLIB$sayStudent$0();
,該方法是調用父類,也就是Person類的目標方法,具體的代碼實現如下兩圖中所示。
最後,返回結果,調用過程結束。
八、增強型和不增強的區別和用途
接下來,我們再繼續一個例子,如果我在sayStudent方法中調用一個sayTeacher方法,只需更改一下Person類中方法,改爲如下代碼:加一行方法調用。
public void sayStudent(){
System.out.println("這是Person類中的學生...");
sayTeacher();
}
運行結果:
運行結果顯示進入了兩次intercept方法,輸出了兩次intercept方法中內容。接下來繼續調試可以看到,走到Person類中,this所指向的是子類,也就是存在於內存中的代理類,所以這時調用的sayTeacher方法,其實是子類代理類中的方法,那麼根據上文判斷是否有回調對象,便會再進入intercept方法(這就是增強型對象的作用),然後再調用父類的目標方法輸出,所以結果打印如上圖所示。
這樣的場景在有時候是這樣需要的,但是有時候我們不需要方法調用都走一遍intercept,重複輸出一些共用的內容。那麼我們希望只走代理方法一遍,Invoke the original method, on a different object of the same type.
在相同類型的不同對象上調用原始方法。
這樣我們就要修改代碼,就不適用invokeSuper,改爲invoke,並且需要類似jdk 代理,在設置回調對象時,新建一個被代理對象以參數傳入,之後調用的方法也是調用的該對象的。注意invoke方法傳入的對象時入參新建的被代理對象,不能是增強型對象,否則會死循環,導致棧溢出
MyMethodIntercept代碼修改:
public class MyMethodInceptor implements MethodInterceptor {
private Person person;
public MyMethodInceptor(Person person) {
this.person = person;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("動態代理方法進入。。。");
return methodProxy.invoke(person,objects);
}
}
Main方法修改:新建一個被代理類,傳入MyMethodInceptor構造方法。
public class CglibMain {
public static void main(String[] args) {
Person person = new Person();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new MyMethodInceptor(person));
Person student = (Person) enhancer.create();
student.sayStudent();
}
}
運行結果:
8.1、非增強調用過程
(1)下面我們調試,並結合代理類的class文件分析調用過程。進入invoke方法,如下圖,我們可以看到fci.f1指向Person$$FastClassByCGLIB$$8d8414de
類,fci.i1的值爲0,obj是我們傳入的在main方法中創建的person對象。
(2)接下來我們去查看生成的代理類class文件,傳入的n,即0,匹配的是person.sayStudent,接下來的sayTeacher也是相同的對象,所以進入intercept方法就一次。
(3)我們再結合下圖可以再一次確認,此時的this對象就是在main方法中創建的被代理的對象。是非增強型,所以我們在調用同一個類下的其他方法時,是不會進入intercept方法中。
九、總結
- 靜態代理,class文件在文件系統可見,在編譯時。
- jdk 動態代理,class文件緩存在內存,在運行時動態生成代理類,只能基於接口進行代理。
- cglib 動態代理,通過繼承代理,可以代理類。
有了以上做基礎,我們在學習和用到AOP編程時就可以很快入門了。