动态代理

1.动态代理

1.1jdk的动态代理

public interface Movable{
	void move();
}

public class Tank implements Movable{
    @Override
    public void move(){
    	System.out.println("a tank ClaClaCla...");
        try{
            Thread.sleep(new Random().netInt(1000));
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args){
    	Tank tank = new Tank();
        Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
        	new Class[]{Movable.class},
                new InvocationHandler(){
                @Override
    		public Object invoke(Object proxy, Method method, Object[] args) 
                                                              throws Throwable {
                    System.out.println(method.getName+"a tank start moving...");
                    Object o = method.invoke(tank, args);
                    System.out.println(method.getName+"stop!");
                    return o;
            });
        m.move();
	}
}

1.2 jdk动态代理执行流程

1.3 jdk动态代理原理解析

  • 调用Proxy的newProxyInstance方法,传递三个参数。
    第一个是类加载器,一般是代理对象的加载器;
    第二个是代理对象实现的接口 Movable.class;(jdk动态代理的局限就在于此,其代理对象必须实现某个接口)
    第三个是调用处理器invocationHandler的实现类,一般都是用匿名内部类实现

  • 调用Proxy的newProxyInstance方法返回一个代理对象,我们用接口来接收,即Movable m = (Movable)Proxy.newProxyInstance

    • 为什么可以用Movable接收
    • 因为返回的代理对象实现了Movable接口
  • 之后用户可以通过代理对象来执行被代理对象的功能,即 m.move();
    这个方法的实质是 proxy$0.move()。

  • proxy$0是Proxy生成的代理对象,也就是m。proxy$0没有源码,它的来源是ASM,ASM直接增删.class文件中的字节生成了proxy$0。
    proxy$0一般只会存在于内存,不会再生成.class文件(不过也有特殊情况,见2.1.4)

    ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有的class文件或动态生成class文件。
    
  • 以上说明了m.move() 其实就是 proxy$0.move(),而proxy$0.move()中除了异常处理只有一句关键代码,
    那就是调用InvocationHandler实现类中的invoke()方法

    • 为什么可以调用?
    • 调用newProxyInstance的时候给Proxy传递了三个参数,其中就有InvocationHandler实现类,而proxy$0继承了Proxy,proxy$0可以通过父类调用InvocationHandler实现类中的invoke()方法
  • 来看看invoke方法 -- public Object invoke(Object proxy, Method method, Object[] args)

    • 有三个参数分别为 调用该方法的代理实例、调用的接口方法对应的Method实例、参数
  • 再来看看invoke方法中的内容

    System.out.println(method.getName+"a tank start moving...");
    Object o = method.invoke(tank, args);
    System.out.println(method.getName+"stop!");
    return o;
    

    两个输出语句作为日志功能增强,不作解析,重点看一下其他两行代码,

    • 一个是method.invoke(tank, args),它其实就是调用tank的move方法
    • 另一个是这个return,可能有人像我一样纠结这里的返回是给谁的,其实这里的返回值是m.move()的返回值
    • tip:有一个好玩的地方
      • tank的move方法返回值是void
      • 但m.move()方法返回值Object,那么我们是可以修改return,让它返回一些数据的
      • 那么就会出现
      • 调用被代理对象的方法的返回值 和 调用代理实例的方法的返回值 不一样的情况
      • 而我们也就可以根据这一情况来实现某些功能了,具体实现大家可以想想或者去网上找找

1.4 proxy$0的代码

  • 前面有提过,proxy$0一般只会存在于内存,不会再生成.class文件

  • 但是如果我们在main方法中添加下列代码,就可以让java虚拟机生成.class文件

    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
    
  • 不知道为什么ta名字变了,大家知道$proxy0和proxy$0是同一个就好,[狗头][狗头][狗头]

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

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

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

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void move() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", 
                                      Class.forName("java.lang.Object"));
            m3 = Class.forName("proxy.movable").getMethod("move");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

2.动态代理——cglib

​ 前面提到了,jdk动态代理的局限性——被代理对象必须实现一个以上的接口。那么有没有一种没有这个局限的动态代理呢?当然是有的,那就是cglib,全名为Code Generation Library。

​ 但是博主只是想提一下,不打算写了[狗头][狗头][狗头],大家有兴趣的话,可以自己去找一找。溜溜球~~~

posted @ 2021-05-31 17:34  金鱼同学  阅读(46)  评论(0编辑  收藏  举报