源码导读-5分钟看懂-JDK动态代理源码

代理模式是对象的结构模式,代理模式给一个对象提供了一个代理对象,并由代理对象控制原对象的引用。
反射、类加载器和动态代理的关系:
动态代理是生成目标对象的代理对象的class文件,再由用户传入的类加载器读入内存中,生成Class对象。通过反射生成代理对象。

文章总结(为什么JDK代理一定需要接口):

  1. 通过传入目标类的接口handler,然后生成代理类的class文件。handler就是关联代理类的处理程序。
  2. 因为即使是同一个class文件,使用不同的自定义类加载器加载的话(双亲委派模型中ApplicationClassLoader都无法加载,只能使用自定义加载器加载时),其对象也是不同。所以需要目标类将自己的类加载器传入。
  3. 将使用目标类的类加载器将代理类的class文件加载到JVM后,会在内存(一般是堆)生成Class对象,是反射的入口。
  4. 通过反射生成代理对象(类加载模型中的-初始化阶段)。
  5. 调用proxy时,就可以关联InvocationHandler实现类,完成动态代理。

代理模式中的角色:

  • 抽象对象角色:声明了目标对象和代理对象的共同接口,这样的话在任何使用目标对象的地方都可以使用代理对象。
  • 目标对象角色:定义了代理对象所代表的目标对象。
  • 代理对象角色:代理对象内部含有目标对象的引用,从而在任何地都可以操作目标对象;代理对象提供了一个与目标对象相同的接口,以便可以在任何时候代替目标对象。代理对象通常在客户端调用目标对象之前或之后,执行某个操作,而不是单纯的将调用传递给目标对象。

为什么需要jdk代理需要实现接口:
当你想给一个类动态代理而又不建立联系的时候,jdkcglib其实都不知道你要给什么类,类里面的什么方法实现代理。这就需要,继承或者实现的方法,来反推出你需要代理的类。

jdk代理的过程:

  1. 创建被代理的接口和类;
  2. 创建InvocationHandler接口的实现类,在invoke代码中实现代理逻辑;
  3. 通过Proxy的静态方法newProxyInstance(ClassLoader,Class[] interfaces,InvocationHandler h)创建代理对象;
  4. 使用代理对象;

InvocationHandler的作用:
在动态代理中,InvocationHandler是核心,每个代理对象都具有一个关联的调用处理程序InvocationHandler),对代理实例调用都是通过InvocationHandlerInvoke来实现的,而Invoke方法根据传入的代理对象、方法和参数来决定调用代理的哪个方法

目标类接口和类:

public interface StudentDao {
    public abstract void login(String name,String password);
    public abstract void regist();
}

public class StudentDaoImpl implements StudentDao {
    @Override
    public void login(String name, String password) {
        if (name == "tom" && password == "123") {
            System.out.println(name + " 登录成功");
        } else {
            System.out.println(name + " 登录失败");
        }
    }
    @Override
    public void regist() {
        System.out.println("注册功能");
    }
}

InvocationHandler接口实现类


public class MyInvocationHandler implements InvocationHandler {
    //目标对象
    private Object target;
    //构造方法
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    /**
     * @param proxy  代理对象
     * @param method 代理对象的某个方法
     * @param args   调用真实方法对象传递的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("权限验证");
        System.out.println("代理对象proxy:"+proxy.getClass());
        //执行target里面的方法
        Object invoke = method.invoke(target, args);
        System.out.println("method的方法类型:" + method);
        for (Object obj : args) {
            System.out.println(obj + " --参数类型--");
        }
        //返回是的代理对象(对目标对象加强的方法)
        System.out.println("日志记录");
        return invoke;
    }
}

使用代理对象:

//测试代理模式
public class TestProxy {

    public static void main(String[] args) {
        //目标类代码
        StudentDao studentDao = new StudentDaoImpl();
        //我们首先是需要准备一个代理类
        MyInvocationHandler handler = new MyInvocationHandler(studentDao);
        //第一个参数是:指定代理类的类加载器(一般都是双亲委托模式)
        //第二个参数是代理类需要实现的接口(我们传入的是目标类实现的接口,于是代理类和实现类拥有相同的接口)
        //第三个参数是invocation handler,用来处理方法的调用这里传入自己实现的handler
        Class[] interfaces = studentDao.getClass().getInterfaces();
        //打印接口
        for (Class inter : interfaces) {
            System.out.println("第二个参数:" + inter);
        }
        //其实他是一个对象,代理对象就是目标对象上加了解耦的逻辑。
        StudentDao stuProxy = (StudentDao) Proxy.newProxyInstance(studentDao.getClass().getClassLoader(),
                studentDao.getClass().getInterfaces(), handler);
        //需要注意的是:并没有像静态代理那样去自己定义一个代理类,并实例化代理对象。
        //实际上,动态代理对象是在内存中的,是我们JDK根据自己传入的参数生成好的。
        stuProxy.login("tom", "123");
        //生成代理对象的Class对象
        String path = "D://11/$Proxy0.class";
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", StudentDaoImpl.class.getInterfaces());
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

代理对象的部分源码:

//实现了目标对象的接口,继承了Proxy类
public final class Proxy0 extends Proxy
  implements StudentDao{
//静态代码块的初始化工作
 static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m4 = Class.forName("com.proxy.StudentDao").getMethod("regist", new Class[0]);
      m3 = Class.forName("com.proxy.StudentDao").getMethod("login", new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });
      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());
    }
  }
//login方法(其中的h就是invocationHander;m3就是login方法的method类型)
 public final void login(String paramString1, String paramString2)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString1, paramString2 });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
}

源码详细解读可以看这篇
JDK动态代理-超详细源码分析

但是咱们是5分钟梳理,那么就开始分析关键点吧

由咱们的上篇文章可以知道:即使对于同一个Class文件,使用不同的自定义类加载器加载,得到的对象也是不同的。

  • 我们需要将ClassLoader类加载器传入,以便我们生成代理对象目标对象是同一个接口下的对象。
  • 我们需要将目标类实现的接口传入,以保证生成的代理对象可以代替目标对象。
  1. 我们需要生成的是代理对象Proxy的实例,那么我们首先要获取代理对象Class对象,然后通过反射,生成代理对象
  2. 那么我们如何获取Proxy的Class对象呢?我们第一次获取后将其保存到proxyClassCache缓存中,之后每次在缓存中获取即可。
  3. 那么proxClassCache作为缓存应该是什么结构呢?缓存一般是map结构。所以这个也是一个map,是一个双重的map结构K代表的是类加载器(双亲委派模型,一般我们获取的是ApplicationClassLoader)。P是传入的interfaces类型,即目标接口类型;V就是最终得到的代理对象
  4. 通过K-P就可以得到sub-key,那么可以sub-key获取Supplier对象,里面保存了代理对象。本质上就是一个Factory工厂。
  5. 我们将Factory 赋值给了Supplier之后,调用supplier.get();获取代理对象。该方法生成代理对象的包名,类名。首先将代理对象生成class文件,使用ClassLoader读取class文件到内存中(类的加载),最终生成了代理对象的class对象
  6. 回到第一步,得到代理对象。

是不是发现InvocationHandler在源码里并没有提到?
不是的,咱们在第五步代理对象的class文件时,其实会将InvocationHandler引用传入到代理对象,我们可以在上面看代理对象的源码中的h就可以发现。InvocationHandler其实就是代理对象关联的处理程序。

你可能感兴趣的:(源码导读-5分钟看懂-JDK动态代理源码)