jdk动态代理简单使用和理解

427 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天。点击查看活动详情

一、前言

jdk动态代理一共有三种,静态代理、jdk动态代理、cglib动态代理。在spring aop中就使用了jdk动态代理和cglib动态代理。jdk动态代理作用于接口,通过实现接口并持有被代理对象的引用完成代理,是jdk原生功能。cglib是通过继承实现的,不是jdk的原生功能,需要引入第三方的包。今天主要介绍jdk动态代理。

二、使用

我们在日常的开发工作中避免不了打印日志,如果有一个需求是我们需要统计调用一个方法的耗时,可是如果我们把统计耗时的代码写在我们的业务逻辑中,难免使自己的代码显得很啰嗦,而且在代码中写了与业务逻辑无关的代码使人觉得差强人意。这时,代理的作用就体现出来了。 假设我们有一个接口UserService如下, 我门需要统计这个接口里的方法的耗时情况。

public interface UserService {
    /**
     * 说话
     *
     * @param info
     */
    void say(String info);

    /**
     * 吃饭
     *
     * @param food
     */
    void eat(String food);
}

实现类

public class UserServiceImpl implements UserService{
    @Override
    public void say(String info) {
        System.out.println("There's something I want to say to you " + info);
    }

    @Override
    public void eat(String food) {
        System.out.println("I'm eating " + food);
    }
}

如何使用jdk动态代理完成呢? 首先我们需要实现一个InvocationHandler。将我门需要代理的逻辑写进来

public class BasketballPlayerInvocationHandler implements InvocationHandler {
    private UserService userService;

    public BasketballPlayerInvocationHandler(UserService userService) {
        this.userService = userService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.nanoTime();
        Object invoke = method.invoke(userService, args);
        System.out.println("调用方法耗时:" + (System.nanoTime() - start));
        return invoke;
    }
}

然后在调用的时候使用Proxy.newProxyInstance创建代理对象。使用代理对象调用方法

public class ProxyMain {

    public static void main(String[] args) {
        UserService basketballPlayer = new BasketballPlayers();
        basketballPlayer.getClass();
        UserService proxyUserService = (UserService)Proxy.newProxyInstance(basketballPlayer.getClass().getClassLoader(), basketballPlayer.getClass().getInterfaces(),
                               new BasketballPlayerInvocationHandler(basketballPlayer));
        proxyUserService.work("playing basketball");
        proxyUserService.eat("pizza");
        proxyUserService.say("法克,吹扬");
    }
}

可以先看下效果

屏幕快照 2022-04-19 上午1.33.54.png 方法被调用,其中的耗时也被打印出来了。

三、Proxy

Proxy类提供了创建动态代理类及其实例的静态方法,该类也是动态代理类的超类。这个类来自于java.lang.reflect这个包下,从名字中就可以看出来jdk的动态代理是跟反射相关的。

代理类具有以下属性:

  • 代理类的名称以 “$Proxy” 开头,后面跟着一个数字序号。
  • 代理类继承了Proxy类。
  • 代理类实现了创建时指定的接口(JDK动态代理是面向接口的)。
  • 每个代理类都有一个公共构造函数,它接受一个参数,即接口InvocationHandler的实现,用于设置代理实例的调用处理器。

Proxy提供了两个静态方法,用于获取代理对象。
getProxyClass\

public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException

获取代理类的class对象,获取到对象可以再根据反射调用实例化出一个代理类。

newProxyInstance

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

用于创建一个代理实例,上面代码中用的就是这个。该方法需要三个参数\

  • loader: 类加载器,用于加载生成的代理类。一般使用被代理类的类加载器,因为被代理类是我们自己的代码,默认使用的是AppClassloader。代理类也就用AppClassloader去加载。如果我们在这个参数传入了其他类加载器,例如Proxy.class.getClassLoader(),那么代理对象会创建失败。
  • interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口。 代理会通过实现接口的方式拥有该接口的方法,从而可以对它进行调用。
  • h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。这是真正执行代理逻辑的地方。

四、InvocationHandler

InvocationHandler是Proxy实例处理调用实现的调用处理程序实现的一个接口。没有Proxy代理实例都关联一个InvocationHandler。每当通过代理对象调用一个方法时,调用就会被转发到与这个代理绑定了的实现了InvocationHandler接口的invoke中。

    /**
    * proxy:代理类代理的真实代理对象 com.sun.proxy.$Proxy0
    * method:我门要调用的方法的method对象
    * args:调用方法的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

InvocationHandler只有一个方法,该方法有三个参数

  • proxy: 代理类代理的真实代理对象 com.sun.proxy.$Proxy0
  • method: 我门要调用的方法的method对象
  • args: 调用方法的参数

五、代理类代码

简单的使用和方法说完这里我门不妨使用ProxyGenerator.generateProxyClass将代理类输出查看代理到底做了什么。
获取代理类class文件

static void save2File() throws IOException {
    try {
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", BasketballPlayers.class.getInterfaces());
FileOutputStream out = new FileOutputStream("/tmp/a.class");
out.write(classFile);
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
    }
}

结果

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m5;
    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 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 void say(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void eat(String var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void work(String var1) throws  {
        try {
            super.h.invoke(this, m5, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.myl.JdkProxy.UserService").getMethod("say", Class.forName("java.lang.String"));
            m4 = Class.forName("com.myl.JdkProxy.UserService").getMethod("eat", Class.forName("java.lang.String"));
            m5 = Class.forName("com.myl.JdkProxy.UserService").getMethod("work", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到,生成的代理类就是上面说的,实现了UserService接口(我们需要被代理的类的接口)和继承了Proxy类。其中的m1...m5这些method对象就是我门接口中定义的方法和基本的toString方法和equal方法。通过反射获取。我们调用say方法时,真正执行这些方法时调用了super.h.invoke(this, m3, new Object[]{var1})
h就是我门传入的InvocationHandler的实现类。最终调用到实现类的方法从而实现在执行真正的逻辑前后进行操作。
其中三个参数
this就是将当前代理类的对象传入(如果我们有的方法是处理完逻辑返回的是当前对象,我们在InvocationHandler中就可以返回这个proxy代理对象,否则返回的对象就成了我们的被代理对象,再使用返回的对象执行其他方法就不会走代理逻辑了)
m3就是我们要调用的method对象
new Object[]{var1}为该方法参数。