爲了理解spring的AOP。 理解JDK動態代理與CGLB動態代理,很是重要。html
講動態代理必講靜態代理。java
靜態代理與動態代理是一種設計思想git
靜態代理是在編譯期將擴展代碼織入代理對象github
實現方式spring
1.代理模式:能夠理解爲硬編碼模式。就是代理類裏持有被代理對象的引用。 經過調用代理類的實例。達到間接調用被代理對象的目的。ide
public interface UserInterface {
void doSomething();
}
public class User implements UserInterface {
@Override
public void doSomething() {
System.out.println("I'm doing something");
}
}
public class UserProxy implements UserInterface {
private UserInterface userInterface = null; // 持有被代理對象的引用
@Override
public void doSomething() {
beforeDoSomething();
if(operator == null){
userInterface = new User();
}
operator.doSomething();
afterDoSomething();
}
private void beforeDoSomething() {
System.out.println("before doing something");
}
private void afterDoSomething() {
System.out.println("after doing something");
}
}
複製代碼
2.AspectJ編譯織入 AspectJ是一種面前切面的技術,經過在編譯期把AspectJ內容編譯到字節碼文件.class (這裏不作過多解析推薦文章關於 Spring AOP (AspectJ) 你該知曉的一切)this
靜態代理小結:編碼
咱們熟知的動態代理:JDK動態代理和CGLB動態代理 他們都有一個共同點, 在內存中生成了新的字節碼,這點很重要。這節咱們從生成的新字節碼的角度來看看動態代理的兩種方式spa
由於新生成的字節碼在內存中,爲了看到這些字節碼咱們須要其以文件的形式展示出來。咱們在main方法里加下面兩行代碼就能夠把內存的字節碼拿出來了.net
//輸出JDK動態代理
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//輸出CGLIB動態代理產生的類
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglbproxy");
複製代碼
咱們仍是以User類爲例看看JDK動態代理生成的字節碼文件與CGLB生成的字碼文件 詳情代碼參考springlean項目
1.JDK動態代理:
//------------------------------------接口
interface UserInterface {
void doSomething();
}
//------------------------------------被代理類
class User implements UserInterface {
@Override
public void doSomething() {
System.out.println("我是實現了接口的User");
}
}
//------------------------------------代理類(也是攔截器)
class JDKProxy implements InvocationHandler {
// 要被代理的目標對象
private UserInterface target;
public JDKProxy(UserInterface target){
this.target=target;
}
public UserInterface createProxy(){
return (UserInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK前置攔截");
return method.invoke(target,args);
}
}
//------------------------------------內存生成的新代理類。
final class $Proxy0 extends Proxy implements UserInterface {
private static Method m3;
//省略其餘代碼
.....
public final void doSomething() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m3 = Class.forName("cn.wqd.UserInterface").getMethod("doSomething", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
....
}
//------------------------------------調用。
public static void main(String[] args) {
User a=new User();
//建立JDK代理
JDKProxy jdkProxy=new JDKProxy(a);
//建立代理對象
UserInterface proxy=jdkProxy.createProxy();
System.out.println(proxy.getClass().getName());
//執行代理對象方法
proxy.doSomething();
}
複製代碼
System.out.println(proxy.getClass().getName()); 輸出的是cn.wqd.$Proxy0。 咱們沒有定義過cn.wqd.$Proxy0類,因此 cn.wqd.$Proxy0就是新生成的代理類。
對比咱們User類與$Proxy0類總結下新的代理類的特徵
當咱們調用proxy.doSomething()的時候,指的是cn.wqd.$Proxy0.doSomething() ,其內部調用時父類Proxy的.InvocationHandler的invoke()方法,傳入的參數(1)代理類對象 ,(2)目標對象的方法。
InvocationHandler屬性參數其實就是咱們定義的JDKProxy,其invoke方法最終調用目標對象方法 method.invoke(target,args)。以此達到代理的目的。
小結一下:
2.CGLB動態代理:
//====================user
class UserNoInterface{
public void doSomething() {
System.out.println("我是沒有實現接口的User");
}
}
//====================攔截器
class CglibProxyIntercepter implements MethodInterceptor {
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("執行前...");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("執行後...");
return object;
}
}
//------------------------------------調用。
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserNoInterface.class);
enhancer.setCallback(new CglibProxyIntercepter());
UserNoInterface proxyCGLB= (UserNoInterface) enhancer.create();
System.out.println(proxyCGLB.getClass().getName());
proxyCGLB.doSomething();
}
複製代碼
CGLB動態代理不要求被代理類實現一個接口,就能夠進行代理。 CGLB生成三個新的字碼碼文件,是否是很詭異。
System.out.println(proxyCGLB.getClass().getName()); 打印cn.wqd.UserNoInterface$$EnhancerByCGLIB$$b3361405
看來三個文件中。 cn.wqd.UserNoInterface$$EnhancerByCGLIB$$b3361405
纔是對應生成的代理類。 咱們分析下代理類代碼。(爲了便於理解,我只貼出有助於理解的代碼具體查看詳情代碼參考springlean項目)
public class UserNoInterface$$EnhancerByCGLIB$$b3361405 extends UserNoInterface implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$doSomething$0$Method;
private static final MethodProxy CGLIB$doSomething$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("cn.wqd.UserNoInterface$$EnhancerByCGLIB$$b3361405");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
CGLIB$doSomething$0$Method = ReflectUtils.findMethods(new String[]{"doSomething", "()V"}, (var1 = Class.forName("cn.wqd.UserNoInterface")).getDeclaredMethods())[0];
CGLIB$doSomething$0$Proxy = MethodProxy.create(var1, var0, "()V", "doSomething", "CGLIB$doSomething$0");
}
final void CGLIB$doSomething$0() {
super.doSomething();
}
public final void doSomething() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if(var10000 != null) {
var10000.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy);
} else {
super.doSomething();
}
}
.....
}
複製代碼
總結下特徵
CGLIB$doSomething$0()
,CGLIB$doSomething$0()
方法內部調用的是super.doSomething()也就是目標方法的doSomething()方法CGLIB$doSomething$0$Method
獲取的父類方法,CGLIB$doSomething$0$Proxy
與之對應的MethodProxyCGLIB$doSomething$0$Method = ReflectUtils.findMethods(new String[]{"doSomething", "()V"}, (var1 = Class.forName("cn.wqd.UserNoInterface")).getDeclaredMethods())[0];
CGLIB$doSomething$0$Proxy = MethodProxy.create(var1, var0, "()V", "doSomething", "CGLIB$doSomething$0");
複製代碼
由於拿到的proxyCGLB是代理類對象實例,因此proxyCGLB.doSomething()調用的就是
public final void doSomething() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if(var10000 != null) {
var10000.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy);
} else {
super.doSomething();
}
}
複製代碼
咱們看看這段邏輯:
首先,檢查(MethodInterceptor)CGLIB$CALLBACK_0
是否爲null,看到MethodInterceptor咱們應該明白了這個地方的CGLIB$CALLBACK_0
應該就是上文定義的CglibProxyIntercepter 。
不爲null。則執行CglibProxyIntercepter.intercept()方法,入參分別爲:
CGLIB$doSomething$0$Method
目標對象方法CGLIB$emptyArgs
方法入參CGLIB$doSomething$0$Proxy
代理方法。CglibProxyIntercepter.intercept()方法內部經過調用MethodProxy.invokeSuper(sub, objects)。
咱們看看MethodProxy的內部:
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
} catch (IllegalArgumentException var5) {
if(this.fastClassInfo.i1 < 0) {
throw new IllegalArgumentException("Protected method: " + this.sig1);
} else {
throw var5;
}
}
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
///fastclass
private static class FastClassInfo {
FastClass f1;
FastClass f2;
int i1;
int i2;
private FastClassInfo() {
}
}
複製代碼
這裏有invoke()與invokeSuper()。爲啥上面要調用invokeSuper()不調用invoke()呢?
這裏就要涉及到CGLB的FastClass機制。 FastClass.機制:爲代理類和被代理類各生成一個Class。這個Class會爲代理類或被代理類的方法分配一個index(int類型)索引。經過這個索引能夠直接定位到要調用的方法,省去了反射。 具體推薦閱讀 參考1 參考2
這裏咱們只需知道結果:MethodProxy.FastClassInfo 屬性的最終值會是
private static class FastClassInfo {
FastClass f1;//被代理類UserNoInterface的FastClass
FastClass f2;//代理類UserNoInterface$$EnhancerByCGLIB$$b3361405的FastClass
int i1;//被代理類的doSomething()的索引
int i2;//代理類CGLIB$doSomething$0()(內部調用被代理對象的方法)的索引。
private FastClassInfo() {
}
}
複製代碼
三個文件分別爲:
UserNoInterface$$EnhancerByCGLIB$$b3361405$$FastClassByCGLIB$$fc90c93c
《代理類的FastClass》UserNoInterface$$EnhancerByCGLIB$$b3361405
《 cglb生成的代理類》UserNoInterface$$FastClassByCGLIB$$29e52466
《被代理類的FastClass》咱們在回頭看看invoke(),invokeSuper()方法
UserNoInterface$$EnhancerByCGLIB$$b3361405的FastClass
的invoke方法.//=================被代理對象的fastclass的invoke方法
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
UserNoInterface var10000 = (UserNoInterface)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.doSomething();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
//=================代理對象的fastclass的invoke方法
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
b3361405 var10000 = (b3361405)var2;
int var10001 = var1;
try {
switch(var10001) {
case 7:
var10000.doSomething();
return null;
case 15:
var10000.CGLIB$doSomething$0();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
複製代碼
咱們看到若是調用了MethodProxy.invoke() 會調用被代理對象的fastclass的invoke方法,會調用var10000.doSomething()。而var10000其實就是代理對象,這樣就出現了死循環。 這也是網上爲啥說的不能在此處使用MethodProxy.invoke()的緣由。
看看最終的調用鏈:
代理對象.doSomething()--->攔截器.intercept()--->MethodProxy.invokeSuper(sub, objects)--->代理類的FastClass類對象的.invoke()--->代理對象的CGLIB$doSomething$0()
方法--->目標對象的方法doSomething()
小結:
重點:
1.JDK動態是經過反射來實現的。兩個要素是Proxy+InvocationHandler
2.CGLB動態代理:兩要素Enhancer + MethodInterceptor(CallBack)
3.JDK代理與CGLB動態代理都在內存中生成新的字節碼,最終仍是落在Class對象上,
要素是手段,字節碼纔是目的。JVM並不關心你java代碼,他關心的是在內存中可被使用的字節碼。
歡迎你們關注個人公衆號【源碼行動】,最新我的理解及時奉送。