深入分析JDK動態代理

一、動態代理的使用

public class Test {
    //被代理的接口
    public interface IHello {
        void sayHello();
    }
    //接口的實現類
    static class Hello implements IHello {
        public void sayHello() {
            System.out.println("Hello world!!");
        }
    }

    //自定義InvocationHandler
    static class MyInvocationHandler implements InvocationHandler {
        //目標對象
        private Object target;

        public MyInvocationHandler(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------插入前置通知代碼-------------");
            //執行相應的目標方法
            Object rs = method.invoke(target, args);
            System.out.println("------插入後置處理代碼-------------");
            return rs;
        }
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //生成$Proxy0的class文件,需要在根目錄下創建com.sun.proxy路徑,否則會報錯
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        IHello ihello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(),  //加載接口的類加載器
                new Class[]{IHello.class},      //一組接口
                new MyInvocationHandler(new Hello())); //自定義的InvocationHandler
        ihello.sayHello();
    }
}

輸出結果

------插入前置通知代碼-------------
Hello world!!
------插入後置處理代碼-------------

二、源碼分析

1、以newProxyInstance方法爲切入點來剖析代理類的生成及代理方法的調用

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException
    {
        if (h == null) {   //如果h爲空直接拋出異常,所以InvocationHandler實例對象是必須的
            throw new NullPointerException();
        }
        final Class<?>[] intfs = interfaces.clone();
        //一些安全的權限檢查
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        //產生代理類
        Class<?> cl = getProxyClass0(loader, intfs);

        //獲取代理類的構造函數對象
        //參數constructorParames爲常量值:private static final Class<?>[] constructorParams = { InvocationHandler.class };
        final Constructor<?> cons = cl.getConstructor(constructorParames);
        final InvocationHandler ih = h;
        //根據代理類的構造函數對象來創建代理類對象
        return newInstance(cons, ih);
    }

  其中newInstance只是調用Constructor.newInstance來構造相應的代理類實例,這裏重點是看getProxyClass0這個方法的實現:

private static Class<?> getProxyClass0(ClassLoader loader,  
                                       Class<?>... interfaces) {  
    // 代理的接口數量不能超過65535(沒有這種變態吧)  
    if (interfaces.length > 65535) {  
        throw new IllegalArgumentException("interface limit exceeded");  
    }  
    // JDK對代理進行了緩存,如果已經存在相應的代理類,則直接返回,否則纔會通過ProxyClassFactory來創建代理  
    return proxyClassCache.get(loader, interfaces);  
}  

  其中代理緩存是使用WeakCache實現的,如下

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());  

  具體的緩存邏輯這裏暫不關心,只需要關心ProxyClassFactory是如何生成代理類的,ProxyClassFactory是Proxy的一個靜態內部類,實現了WeakCache的內部接口BiFunction的apply方法:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        //統一代理類的前綴名都以$Proxy開關
        private static final String proxyClassNamePrefix = "$Proxy";
        //使用唯一的編號給作爲代理類名的一部分,如$Proxy0,$Proxy1等
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                //驗證指定的類加載器(loader)加載接口所得到的Class對象(interfaceClass)是否與intf對象相同
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                //驗證該Class對象是不是接口
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                // 驗證該接口是否重複了
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }
                  //聲明代理類所在包
            String proxyPkg = null;  
            /*驗證你傳入的接口中是否有非public接口,只要有一個接口是非public的,那麼這些接口都必須在同一包中
            這裏的接口修飾符直接影響到System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")所生成
            的代理類的路徑,往下看!!*/
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    //截取完整包名
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                /*如果都是public接口,那麼生成的代理類就在com.sun.proxy包下*/            
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            //將當前nextUniqueNumber的值以原子的方式的加1,所以第一次生成代理類的名字爲$Proxy0.class
            long num = nextUniqueNumber.getAndIncrement();
            //代理類的完全限定名,如com.sun.proxy.$Proxy0.calss,
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            //生成代理類字節碼文件            
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

  而生成代理類字節碼文件又主要通過ProxyGenerate的generateProxyClass(proxyName,interfaces)

public static byte[] generateProxyClass(final String var0, Class[] var1) {
        ProxyGenerator var2 = new ProxyGenerator(var0, var1);
       //生成代理類字節碼文件的真正方法
        final byte[] var3 = var2.generateClassFile();
        //這裏根據參數配置,決定是否把生成的字節碼(.class文件)保存到本地磁盤
        if(saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    try {
                        FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
                        var1.write(var3);
                        var1.close();
                        return null;
                    } catch (IOException var2) {
                        throw new InternalError("I/O exception saving generated file: " + var2);
                    }
                }
            });
        }
        return var3;
    }

  層層調用後,最終generateClassFile纔是真正生成代理類字節碼文件的方法

private byte[] generateClassFile() {
    //第一步, addProxyMethod系列方法就是將接口的方法和Object的hashCode,equals,toString方法添加到代理方法容器(proxyMethods)
    //首先爲代理類生成toString, hashCode, equals等代理方法
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);
    //遍歷每一個接口的每一個方法, 並且爲其生成ProxyMethod對象
    for (int i = 0; i < interfaces.length; i++) {
        Method[] methods = interfaces[i].getMethods();
        for (int j = 0; j < methods.length; j++) {
            addProxyMethod(methods[j], interfaces[i]);
        }
    }
    //對於具有相同簽名的代理方法, 檢驗方法的返回值是否兼容
    //因爲不可能有兩個方法名相同,參數相同,而返回值卻不同的方法
    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
        checkReturnTypes(sigmethods);
    }

    //第二步, 組裝要生成的class文件的所有的字段信息和方法信息
    try {
        //添加構造器方法
        methods.add(generateConstructor());
        //遍歷緩存中的代理方法
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {
                //添加代理類的靜態字段, 例如:private static Method m1;
                fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
                //添加代理類的代理方法
                methods.add(pm.generateMethod());
            }
        }
        //添加代理類的靜態字段初始化方法
        methods.add(generateStaticInitializer());
    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception");
    }

    //驗證方法和字段集合不能大於65535
    if (methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    }
    if (fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    }

    //第三步, 寫入最終的class文件
    //驗證常量池中存在代理類的全限定名
    cp.getClass(dotToSlash(className));
    //驗證常量池中存在代理類父類的全限定名, 父類名爲:"java/lang/reflect/Proxy"
    cp.getClass(superclassName);
    //驗證常量池存在代理類接口的全限定名
    for (int i = 0; i < interfaces.length; i++) {
        cp.getClass(dotToSlash(interfaces[i].getName()));
    }
    //接下來要開始寫入文件了,設置常量池只讀
    cp.setReadOnly();

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);
    try {
        //1.寫入魔數
        dout.writeInt(0xCAFEBABE);
        //2.寫入次版本號
        dout.writeShort(CLASSFILE_MINOR_VERSION);
        //3.寫入主版本號
        dout.writeShort(CLASSFILE_MAJOR_VERSION);
        //4.寫入常量池
        cp.write(dout);
        //5.寫入訪問修飾符
        dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
        //6.寫入類索引
        dout.writeShort(cp.getClass(dotToSlash(className)));
        //7.寫入父類索引, 生成的代理類都繼承自Proxy
        dout.writeShort(cp.getClass(superclassName));
        //8.寫入接口計數值
        dout.writeShort(interfaces.length);
        //9.寫入接口集合
        for (int i = 0; i < interfaces.length; i++) {
            dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
        }
        //10.寫入字段計數值
        dout.writeShort(fields.size());
        //11.寫入字段集合 
        for (FieldInfo f : fields) {
            f.write(dout);
        }
        //12.寫入方法計數值
        dout.writeShort(methods.size());
        //13.寫入方法集合
        for (MethodInfo m : methods) {
            m.write(dout);
        }
        //14.寫入屬性計數值, 代理類class文件沒有屬性所以爲0
        dout.writeShort(0);
    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception");
    }
    //轉換成二進制數組輸出
    return bout.toByteArray();
}

  可以看到generateClassFile()方法是按照Class文件結構進行動態拼接的。Java程序的執行只依賴於Class文件,這個Class文件描述了一個類的信息,當我們需要使用到一個類時,Java虛擬機就會提前去加載這個類的Class文件並進行初始化和相關的檢驗工作,Java虛擬機能夠保證在你使用到這個類之前就會完成這些工作,我們只需要安心的去使用它就好了,而不必關心Java虛擬機是怎樣加載它的。當然,Class文件並不一定非得通過編譯Java文件而來,你甚至可以直接通過文本編輯器來編寫Class文件。在這裏,JDK動態代理就是通過程序來動態生成Class文件的。我們再次回到上面的代碼中,可以看到,生成Class文件主要分爲三步:
  第一步:收集所有要生成的代理方法,將其包裝成ProxyMethod對象並註冊到Map集合中。
  第二步:收集所有要爲Class文件生成的字段信息和方法信息。
  第三步:完成了上面的工作後,開始組裝Class文件。
  我們知道一個類的核心部分就是它的字段和方法。我們重點聚焦第二步,看看它爲代理類生成了哪些字段和方法。在第二步中,按順序做了下面四件事。
1.爲代理類生成一個帶參構造器,傳入InvocationHandler實例的引用並調用父類的帶參構造器。
2.遍歷代理方法Map集合,爲每個代理方法生成對應的Method類型靜態域,並將其添加到fields集合中。
3.遍歷代理方法Map集合,爲每個代理方法生成對應的MethodInfo對象,並將其添加到methods集合中。
4.爲代理類生成靜態初始化方法,該靜態初始化方法主要是將每個代理方法的引用賦值給對應的靜態字段。

  通過以上分析,我們可以大致知道JDK動態代理最終會爲我們生成如下結構的代理類:

public class Proxy0 extends Proxy implements UserDao {

    //第一步, 生成構造器
    protected Proxy0(InvocationHandler h) {
        super(h);
    }

    //第二步, 生成靜態域
    private static Method m1;   //hashCode方法
    private static Method m2;   //equals方法
    private static Method m3;   //toString方法
    private static Method m4;   //...

    //第三步, 生成代理方法
    @Override
    public int hashCode() {
        try {
            return (int) h.invoke(this, m1, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public boolean equals(Object obj) {
        try {
            Object[] args = new Object[] {obj};
            return (boolean) h.invoke(this, m2, args);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public String toString() {
        try {
            return (String) h.invoke(this, m3, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(User user) {
        try {
            //構造參數數組, 如果有多個參數往後面添加就行了
            Object[] args = new Object[] {user};
            h.invoke(this, m4, args);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    //第四步, 生成靜態初始化方法
    static {
        try {
            Class c1 = Class.forName(Object.class.getName());
            Class c2 = Class.forName(UserDao.class.getName());    
            m1 = c1.getMethod("hashCode", null);
            m2 = c1.getMethod("equals", new Class[]{Object.class});
            m3 = c1.getMethod("toString", null);
            m4 = c2.getMethod("save", new Class[]{User.class});
            //...
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

  至此,經過層層分析,深入探究JDK源碼,我們還原了動態生成的代理類的本來面目,之前心中存在的一些疑問也隨之得到了很好的解釋
  1.代理類默認繼承Porxy類,因爲Java中只支持單繼承,所以JDK動態代理只能去實現接口。
  2.代理方法都會去調用InvocationHandler的invoke()方法,因此我們需要重寫InvocationHandler的invoke()方法。
  3.調用invoke()方法時會傳入代理實例本身,目標方法和目標方法參數。

public static void main(String[] args) {
        //新建目標對象
        Object target = new UserDaoImpl();
        //創建事務處理器
        TransactionHandler handler = new TransactionHandler(target);
        //構造方法參數
        User user = new User();
        user.setName("小明");
        //生成代理類
        UserDao userDao = new Proxy0(handler);
        //針對接口進行方法調用
        userDao.save(user);
    }
    輸出結果:開啓事務控制
            保存用戶
            結束事務控制

  使用剛剛構造出來的Proxy0作爲代理類再次進行測試,可以看到最終的結果與使用JDK動態生成的代理類的效果是一樣的,再次驗證了我們的分析是可靠且準確的。

http://www.cnblogs.com/liuyun1995/p/8144706.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章