今天是与JDK动态代理亲热的一天!(原理和源码深入分析)

   日期:2020-07-08     浏览:89    评论:0    
核心提示:一、先看一个动态代理的小例子大家对动态代理应该挺熟了,如果不熟,请绕行(开玩笑,不懂,这篇文章也让你懂)这是一个接口和一个实现类!public interface Target { int test(int i);}public class TargetImpl implements Target { @Override public int test(int i) { System.out.println(进入方法 ++++++++++++_public v get(k

一、先看一个动态代理的小例子

大家对动态代理应该挺熟了,如果不熟,请绕行(开玩笑,不懂,这篇文章也让你懂)

这是一个接口和一个实现类!

public interface Target {

    int test(int i);
}
public class TargetImpl implements Target {
    @Override
    public int test(int i) {
        System.out.println("进入方法 +++++++++++++++===");
        return i + 1;
    }
}

动态代理的中间类,这个类负责对原有的TargetImpl 对象进行增强。啥?听不懂?

好,比如我现在要给每一个类都增加一条日志信息,开始前记录时间,开始后记录时间,要统计最少时间的执行方法。如何按操作?每一个类都copy相同的代码?一万个类呢,就没办法copy了是吧。而且,要是这么干,就是非常高的耦合度!假设现在不统计时间了,要统计参数个数。每个类都得手动改吗?如何解决?

这个时候aop就上场了,跟大家说easy问题。那么aop如何解决?就是利用的动态代理。代理出的对象就是增强后的对象!!

说白了之前的TargetImpl只是一个普通对象,而通过代理出来的对象,就是一个吃了伟哥的对象。我们通过Proxy.newProxyInstance得到的对象就是一个代理对象。如下。

public class JdkDynamic implements InvocationHandler {
    private  Target target;

    public JdkDynamic(Target target){
        this.target = target;
    }

    public static Target getTarget(Target target){
        return (Target) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class<?>[]{Target.class},new JdkDynamic(target));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object result = method.invoke(target,args);
        System.out.println("after");
        return result;
    }
}

 实际上代理有两种方式:组合和继承。这里JdkDynamic和TargetImpl 采用的是组合,为啥不采用继承?之后我们能够知道为啥。我们将原对象当作参数放到了中间类中,在调用代理对象的方法时候会执行invoke方法。学过反射的同学们应该对Method的这个类比较熟悉,这是一个方法的类,当调用invoke方法的时候这个就是我们通过反射拿到的method作为参数传入到invoke中。proxy就是一个代理对象。args就是一个参数。method.invoke就是一个反射方法对象的调用方式。

如果看过SpringMVC源码的同学,就会发现我们通过uri调用@Controller的@RequestMapping的对应方法,实际上就是通过invoke实现的,在框架中动态代理和反射有着至关重要的作用。

public class Main {

    public static void main(String[] args) {
        Target target = JdkDynamic.getTarget(new TargetImpl());
        target.test(1);
    }
}

内容输出:

二、如果让我们来设计一个动态代理,怎么做?

我的思路是:

(1)用字符串拼接成一个代理类,这个字符串就像我们平时看到的类的字符串,用一个Method保存,作为属性,调用方法例如test方法,在这个类中也有InvocationHandler的实现类属性h。有一个方法就是test。

(2)我们将这个字符串保存到一个文件中,后缀名是.java。这就相当于生成了一个类,然后调用第三方编译器,编译成.class字节码文件。

(3)用类加载器去动态加载这个class。然后获得一个Class对象。

(4)然后通过反射创建一个Constructor对象,用这个调用构造器方法,将中间类传入进来。

(5)代理类的test方法,里面调用h.invoke(this,method,int)第三个属性是传入的参数,这样就可以完成动态代理的调用,达到目的了。

实际上源码也是这么做的,这块看不懂,等看到源码的反编译文件就可以理解了。

三、源码分析

点进Proxy.newProxyInstance方法:

public class Proxy implements java.io.Serializable{

        //第一个参数是指定代理类的类加载器(我们传入当前测试类的类加载器)
        //第二个参数是代理类需要实现的接口(我们传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口)
        //第三个参数是invocation handler,用来处理方法的调用。这里传入我们自己实现的handler
        public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {    

         //检验h不为空,h为空抛异常
        Objects.requireNonNull(h);

        //接口的类对象拷贝一份
        final Class<?>[] intfs = interfaces.clone();

        //进行一些安全性检查
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }


        
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            //得到代理类对象的构造函数,这个构造函数的参数由constructorParams指定
            //参数constructorParames为常量值:private static final Class<?>[] constructorParams = { InvocationHandler.class };
            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);
        }
    }
}

先重点看一下getProxyClass0(loader, intfs);这个方法,这个方法调用成功之后就会是:

这个时候c1实际上就是增长对象的字节码对象了。所以呢我们需要点进去看看如何形成的。

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        return proxyClassCache.get(loader, interfaces);
    }

载点进去get方法。这里只看value的那一块,因为这个字节码对象就是从这获取的。supplier.get();方法。

final class WeakCache<K, P, V> {
    public V get(K key, P parameter) {
      ............

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
          
        }

        .............
    }
}

再点进去:这里面重要的方法也是赋值给value的那个,key是类加载器,parameter是对应的接口class。

final class WeakCache<K, P, V>{
     public synchronized V get() { // serialize access
           ......

            // create new value
            V value = null;
            try {
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }
            ............
            return value;
        }
}

再点进去:proxyName 就是生成对应类的全限定类名,通过ProxyGenerator.generateProxyClass就能够获取了二进制流。这里相当于生成了一.$Proxy0.class文件,之后又获取这个文件的二进制流。

ProxyGenerator.generateProxyClass这个方法并未开源,所以我们看不到这个方法的逻辑。我们只需要知道获取了二进制流就可以了。实际上我们通过这个二进制流输出一个文件里,就是对应反编译的java类。我们最后会看!!

这个方法实际上就将我们的invoke方法的那些内容都放到了新的代理类里面,我们只需要传入一个封装普通类的InvocationHandler对象就可以了,就可以了。之后调用方法就是调用这个增强类的方法。也就是通过中间类调用普通类的方法。

public class Proxy implements java.io.Serializable{
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            ....

            
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
             
                throw new IllegalArgumentException(e.toString());
            }
        }
}
    private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);这是一个本地的方法,底层实际上是用c++来实现,我可以理解为将增强类的二进制流创建了一个Class对象,这个通过类加载器去加载二进制流,然后成为了一个Class,一直向上返回。

一直返回到newProxyInstance方法!

//得到代理类对象的构造函数,这个构造函数的参数由constructorParams指定
            //参数constructorParames为常量值:private static final Class<?>[] constructorParams = { InvocationHandler.class };
            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});

这里获取了增强类的构造器,通过调用构造器的newInstance方法,将对应参数传递进去,这其实是我们反射的知识!

四、我们来看生成的代理类

public static void main(String[] args) {
        Target target = new TargetImpl();
        Target targetest = JdkDynamic.getTarget(target);


        String path = "E:\\$Proxy18.class";
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy18", TargetImpl.class.getInterfaces());
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

生成class对象后再反编译。之前我们说jdk动态代理为什么不能用继承?因为生成的代理类默认继承Proxy,如果采用继承,而不是组合,那么就报错了,因为java是单继承

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy18
        extends Proxy
        implements Target
{
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy18(InvocationHandler paramInvocationHandler)
            throws
    {
        super(paramInvocationHandler);
    }

    public final boolean equals(Object paramObject)
            throws
    {
        try
        {
            return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
        }
        catch (Error|RuntimeException localError)
        {
            throw localError;
        }
        catch (Throwable localThrowable)
        {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    public final int test(int paramInt)
            throws
    {
        try
        {
            return ((Integer)this.h.invoke(this, m3, new Object[] { Integer.valueOf(paramInt) })).intValue();
        }
        catch (Error|RuntimeException localError)
        {
            throw localError;
        }
        catch (Throwable localThrowable)
        {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    public final String toString()
            throws
    {
        try
        {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error|RuntimeException localError)
        {
            throw localError;
        }
        catch (Throwable localThrowable)
        {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    public final int hashCode()
            throws
    {
        try
        {
            return ((Integer)this.h.invoke(this, m0, null)).intValue();
        }
        catch (Error|RuntimeException localError)
        {
            throw localError;
        }
        catch (Throwable localThrowable)
        {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    static
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
            m3 = Class.forName("Target").getMethod("test", new Class[] { Integer.TYPE });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException localNoSuchMethodException)
        {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

当我们执行test方法的时候实际上执行的是InvocationHandler的invoke方法。传入了对应的invoke所需要的代理对象、方法都西昂和参数。

 

 

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服