Spring AOP實現原理(三)動態代理

Spring AOP其實是基於動態代理實現的,只不過Spring 同時支持JDK Proxy和cglib,下面咱們來介紹一下這兩種實現動態代理的方式html

注:本示例中使用JDK1.8java

動態代理代碼示例

JDK Proxy方式

/** * 在代理的接口調用時的處理器類 */
class MyHandler implements InvocationHandler {
    private final Object target;
    public MyHandler(final Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("-----before----");
        final Object o = method.invoke(this.target, args);
        System.out.println("-----after----");
        return o;
    }
}

public static void main(final String[] args) {
        final BizAImpl bizAImpl = new BizAImpl();
        final IBizA newBizA = (IBizA) 		Proxy.newProxyInstance(MyHandler.class.getClassLoader(),
                bizAImpl.getClass().getInterfaces(),
                new MyHandler(bizAImpl));
        newBizA.doSomething();
}
複製代碼

cglib方式

class MyHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("----before----");
        proxy.invokeSuper(obj, args);
        System.out.println("----after----");
        return obj;
    }
}

public static void main(String[] args) {
    MyHandler myHandler = new MyHandler();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(BizA.class);
    enhancer.setCallback(myHandler);

    BizA bizA = (BizA) enhancer.create();
    bizA.doSomething();
}
複製代碼

對比JDK Proxy和 cglib的使用

從示例代碼中,咱們能夠得出如下結論git

JDK Proxy Cglib
要求代理類必須實現接口,參見IBizA和BizImpl 支持類的代理,對接口實現沒有要求
須要將代理目標對象(bizImpl)傳遞給InvocationHandler,在寫法上不夠靈活 能夠直接根據類(BizA.class)生成目標代理對象

動態代理的實現原理

實際上,這兩種代理的實現方式也大不同github

JDK Proxy的實現原理

JDK Proxy是經過複製原有代理類,而後生成一個新類,在生成新類的同時,將方法的調用轉給了InvocationHandler,在代理類執行方法時,其實是調用了InvocationHandler的invoke方法。spring

咱們看下JDK源碼,從newProxyInstance方法開始設計模式

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
    	/* * Look up or generate the designated proxy class. */
        Class<?> cl = getProxyClass0(loader, intfs);

        /* * Invoke its constructor with the designated invocation handler. */
    	final Constructor<?> cons = cl.getConstructor(constructorParams);
        return cons.newInstance(new Object[]{h});
    
}
複製代碼

newProxyInstance方法只幹了兩件事緩存

  • 獲取代理類
  • 調用代理類的構造方法,並將handler做爲參數傳進去(這一點能夠經過反編譯class文件看到)

那麼這裏的重點是若是獲取代理類呢,咱們接着往下看安全

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}
複製代碼

優先從緩存中獲取,若是緩存中沒有,會經過工廠(ProxyClassFactory)生成,緩存部分代碼略過,直接看生成代理類的流程bash

/**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
			
            //校驗接口,訪問權限,肯定package等,省略...
            
            /*
             * Choose a name for the proxy class to generate.
             */
            //生成代理類名字
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }
複製代碼

將核心方法提取出來app

//生成字節碼數據
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
//這是一個native方法
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
複製代碼

進入方法ProxyGenerator.generateProxyClass()

public static byte[] generateProxyClass(final String name,
                                            Class<?>[] interfaces,
                                            int accessFlags)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
        final byte[] classFile = gen.generateClassFile();
		
        //這個標誌是系統的一個配置,是否保存生成的文件,能夠經過配置項
        //sun.misc.ProxyGenerator.saveGeneratedFiles拿到
        if (saveGeneratedFiles) {
            //...保存文件,非討論重點
        }

        return classFile;
    }
複製代碼

生成類文件的核心方法

private byte[] generateClassFile() {

        /* ============================================================ * Step 1: Assemble ProxyMethod objects for all methods to * generate proxy dispatching code for. * 第一步:添加代理方法(ProxyMethod) */
        //object公用方法
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);

		//代理實現的接口方法
        for (Class<?> intf : interfaces) {
            for (Method m : intf.getMethods()) {
                addProxyMethod(m, intf);
            }
        }

        /* ============================================================ * Step 2: Assemble FieldInfo and MethodInfo structs for all of * fields and methods in the class we are generating. * 步驟2:將代理方法(MethodProxy)轉換爲方法(MethodInfo),MethodInfo實現了寫入字節碼 */
        try {
            methods.add(generateConstructor());
            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                for (ProxyMethod pm : sigmethods) {

                    // add static field for method's Method object
                    fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;",
                         ACC_PRIVATE | ACC_STATIC));
					
                    //代理方法(MethodProxy)轉換爲方法(MethodInfo)
                    // generate code for proxy method and add it
                    methods.add(pm.generateMethod());
                }
            }
            methods.add(generateStaticInitializer());
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }

        /* ============================================================ * Step 3: Write the final class file. * 步驟3:寫入類文件 */

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);

        try {
            /* * Write all the items of the "ClassFile" structure. * See JVMS section 4.1. */
            //...依照JVM規範生成字節碼,省略

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }

        return bout.toByteArray();
    }
複製代碼

再次進入ProxyMethod轉換爲MethodInfo的方法generateMethod(),這裏的內容較多,咱們只貼出來關鍵的一處,將InvocationHandler的調用加入了新方法中

out.writeByte(opc_invokeinterface);
    out.writeShort(cp.getInterfaceMethodRef(
    "java/lang/reflect/InvocationHandler",
    "invoke",
    "(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
    "[Ljava/lang/Object;)Ljava/lang/Object;"));
    out.writeByte(4);
    out.writeByte(0);
複製代碼

由此咱們瞭解了整個JDK Proxy的執行過程,最終能夠從生成的class文件($Proxy0.class)中反編譯,反編譯結果以下:

package com.sun.proxy;

import com.proxy.IBizA;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class Proxy0 extends Proxy implements IBizA {
  public Proxy0() throws {
    super(paramInvocationHandler);
  }

  public final void doSomething() throws {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
  }

  public final String toString() throws {
    try
    {
      return ((String)this.h.invoke(this, m2, null));
    }
  }
}
複製代碼

咱們從反編譯的文件中能夠看出來,實際上代理類調用了InvocationHandler的invoke方法

注:系統默認不輸出代理class文件,若是要實現新增代理class文件的輸出,須要在main方法中加上

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
複製代碼

注:Proxy相關的代碼能夠直接經過jdk源碼看到,ProxyGenerator的代碼請參考:

github.com/JetBrains/j…

cglib的實現原理

cglib是經過asm庫實現了代理class的生成,cglib會生成兩個類,一個類是做爲代理類使用,一個類是做爲方法調用時使用。在調用方法的時候,cglib作了一些優化,沒有采用反射調用方法,而是給每一個代理方法增長一個索引,而後根據索引找到方法,並直接調用。

注:asm是一個操做Java字節碼的庫
參考資料:
https://www.ibm.com/developerworks/cn/java/j-lo-asm30/index.html
https://asm.ow2.io/
複製代碼

代理類的生成

咱們首先看下cglib是如何生成代理類的,因爲cglib的代碼較爲複雜,咱們只貼出來關鍵部分.

cglib和JDK proxy有一點比較相似,他們都是優先從緩存中獲取,若是取不到纔會調用生成class的方法,咱們直接看AbstractClassGenerator類的generate方法

protected Class generate(ClassLoaderData data) {
        Class gen;
        Object save = CURRENT.get();
        CURRENT.set(this);
        try {
            ClassLoader classLoader = data.getClassLoader();
            synchronized (classLoader) {
              String name = generateClassName(data.getUniqueNamePredicate());              
              data.reserveName(name);
              this.setClassName(name);
            }
            if (attemptLoad) {
                try {
                    gen = classLoader.loadClass(getClassName());
                    return gen;
                } catch (ClassNotFoundException e) {
                    // ignore
                }
            }
            //關鍵代碼,調用了strategy的generate方法
            byte[] b = strategy.generate(this);
            String className = ClassNameReader.getClassName(new ClassReader(b));
            ProtectionDomain protectionDomain = getProtectionDomain();
            synchronized (classLoader) { // just in case
                if (protectionDomain == null) {
                    gen = ReflectUtils.defineClass(className, b, classLoader);
                } else {
                    gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
                }
            }
            return gen;
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        } finally {
            CURRENT.set(save);
        }
    }
複製代碼

調用了GeneratorStrategy的generate方法,默認策略是DefaultGeneratorStrategy,咱們繼續跟蹤

public byte[] generate(ClassGenerator cg) throws Exception {
    DebuggingClassWriter cw = getClassVisitor();
    transform(cg).generateClass(cw);
    return transform(cw.toByteArray());
}
protected ClassGenerator transform(ClassGenerator cg) throws Exception {
    return cg;
}
複製代碼

默認策略並無作什麼實際的工做,直接調用了ClassGenerator接口的generateClass方法。

咱們回到AbstractClassGenerator類,該類並無定義generateClass方法,但在他的實現類Enhancer中定義了generateClass方法,而實際上也是調用了Enhancer的方法。

public void generateClass(ClassVisitor v) throws Exception {
        Class sc = (superclass == null) ? Object.class : superclass;

        // Order is very important: must add superclass, then
        // its superclass chain, then each interface and
        // its superinterfaces.
        List actualMethods = new ArrayList();
        List interfaceMethods = new ArrayList();
        final Set forcePublic = new HashSet();
        getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic);

        List methods = CollectionUtils.transform(actualMethods, new Transformer() {
            public Object transform(Object value) {
                Method method = (Method)value;
				//...
                return ReflectUtils.getMethodInfo(method, modifiers);
            }
        });
		
    	//類的開始
        ClassEmitter e = new ClassEmitter(v);
        e.begin_class(Constants.V1_8,
                      Constants.ACC_PUBLIC,
                      getClassName(),
                      Type.getType(sc),
                      (useFactory ?
                       TypeUtils.add(TypeUtils.getTypes(interfaces), FACTORY) :
                       TypeUtils.getTypes(interfaces)),
                      Constants.SOURCE_FILE);
		
    	//聲明屬性
        e.declare_field(Constants.ACC_PRIVATE, BOUND_FIELD, Type.BOOLEAN_TYPE, null);
        e.declare_field(Constants.ACC_PUBLIC | Constants.ACC_STATIC, FACTORY_DATA_FIELD, OBJECT_TYPE, null);
        e.declare_field(Constants.PRIVATE_FINAL_STATIC, THREAD_CALLBACKS_FIELD, THREAD_LOCAL, null);
        e.declare_field(Constants.PRIVATE_FINAL_STATIC, STATIC_CALLBACKS_FIELD, CALLBACK_ARRAY, null);

        // This is declared private to avoid "public field" pollution
        e.declare_field(Constants.ACC_PRIVATE | Constants.ACC_STATIC, CALLBACK_FILTER_FIELD, OBJECT_TYPE, null);
		//構造函數
        emitDefaultConstructor(e);
    	
    	//實現回調
        emitSetThreadCallbacks(e);
        emitSetStaticCallbacks(e);
        emitBindCallbacks(e);

        //...
		
    	//類的結束
        e.end_class();
    }
複製代碼

本段代碼的核心是使用ClassEmitter(對asm ClassVisitor的封裝)構造出來一個類,這個類實現了代理目標類(BizA)的方法,而後也增長了方法攔截器的調用,從反編譯代碼中咱們能夠看出來

public final String doSomething() {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
      return ((String)tmp17_14.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy));
    return super.doSomething();
  }
複製代碼

注意這一段代碼

tmp17_14.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy));
複製代碼

實現了方法攔截器(MethodInterceptor)的調用。

在生成代理類的同時,cglib同時生成了另一個類FastClass,這個會在下面詳細介紹

代理類的調用

從cglib方式的代碼示例中,咱們看到,實際上在intercept方法中,咱們是這麼調用的

proxy.invokeSuper(obj, args);
複製代碼

咱們進一步看看這個方法作了什麼

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}
複製代碼

忽略掉init,其實是調用了FastClass的invoke方法

/** * Invoke the method with the specified index. * @see getIndex(name, Class[]) * @param index the method index * @param obj the object the underlying method is invoked from * @param args the arguments used for the method call * @throws java.lang.reflect.InvocationTargetException if the underlying method throws an exception */
    abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException;
複製代碼

這是一個抽象方法,方法的實現是在動態生成的FastClass子類中(稍後會貼出反編譯字節碼)。從方法描述中,咱們能看出來,FastClass根據不一樣的索引調用不一樣的方法(注意,這裏是直接調用而不是反射)。咱們看下反編譯代碼

public Object invoke(, Object paramObject, Object[] paramArrayOfObject) throws InvocationTargetException {
    // Byte code:
    // 0: aload_2
    // 1: checkcast 133 com/cglib/BizA$$EnhancerByCGLIB$$eca2fdc
    // 4: iload_1
    // 5: tableswitch default:+304 -> 309, 0:+99->104, 1:+114->119....
    
    //....
    
    // 198: invokevirtual 177 com/cglib/BizA$$EnhancerByCGLIB$$eca2fdc:doSomething ()Ljava/lang/String;
	
    
   	//...
    
    // 272: aconst_null
    // 273: areturn
    // 274: invokevirtual 199 com/cglib/BizA$$EnhancerByCGLIB$$eca2fdc:CGLIB$toString$2 ()Ljava/lang/String;
    // 277: areturn
    // 278: invokevirtual 201 com/cglib/BizA$$EnhancerByCGLIB$$eca2fdc:CGLIB$clone$4 ()Ljava/lang/Object;
    // 281: areturn
    // 330: athrow
    //
    // Exception table:
    // from to target type
    // 5 312 312 java/lang/Throwable
  }
複製代碼

咱們看到,這裏使用了tableswitch,能夠認爲是switch語句。根據不一樣的值調用不一樣的方法。從198行中咱們能夠看到調用了doSomething方法。

這是cglib的一大優點,經過建立索引,減小了反射的過程,也提高了動態代理的性能。另外cglib代碼中大量使用了緩存,懶加載等機制,這對提高性能也有很多幫助。

cglib的代碼比較複雜,因爲文章篇幅緣由,咱們只是從動態代理實現的角度簡單說明。cglib代碼質量很是高,其中運用了大量的設計模式,對於線程安全,緩存,懶加載等都有涉及,有興趣的同窗能夠直接閱讀下源碼,我在附錄中貼出了源碼的連接。

注:經過設置類輸出目錄,能夠將cglib生成的類輸出
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\classes");
複製代碼

對比JDK Proxy和cglib實現方式

再研究了各自實現原理以後,咱們再次作個對比

JDK Proxy cglib
經過操做字節碼實現,在生成的class中插入InvocationHandler的調用 在asm庫的基礎上進一步抽象封裝,進而生成代理類(Enhancer代理)和FastClass。固然cglib本質上也是操做字節碼。
代理接口方法調用時採用反射調用,反射性能較低 根據自建的索引直接調用方法,性能高

注:思考一下,爲何反射影響性能呢?

引用資料

cglib:github.com/cglib/cglib

Spring AOP:docs.spring.io/spring/docs…

Spring Source Code:github.com/spring-proj…

相關文章
相關標籤/搜索