设计模式之动态代理

213 阅读5分钟

既然有动态代理,那么肯定有静态代理,静态代理请参考设计模式之静态代理

作用: 与静态代理一样,但动态代理解决了 静态代理中 1个静态代理,只代理1种类型的目标对象 的问题

在 JDK 动态代理中涉及如下角色:

  • 目标类的接口 Interface
  • 目标类 target
  • 处理模版 Handler
  • 在内存中生成的动态代理类
  • java.lang.reflect.Proxy

废话少说先上代码

实例

目标类的接口

要有jdk动态代理,你的类必须实现接口

/**
 * UserService 是 目标类的接口
 * login() 就是要被代理的方法
 */
public interface UserService {
    boolean login(String userName, String password);
}

目标类

注意,要代理的方法,必须是从接口中实现的

/**
 * UserServiceImpl 是目标类,或者叫被代理的类
 * login() 就是要被代理的方法
 */
public class UserServiceImpl implements UserService {

    @Override
    public boolean login(String userName, String password) {
        System.out.println("校验账号");
        return false;
    }
}

处理模版类

这是一个InvocationHandler的实现类

作用: 定义一种对目标方法做某种操作的模版(这里运用了面向抽象编程的思想) 如下,定义了一个在目标方法前后输出log的模版。

说明: LogHandler 有一个 属性 —— 目标对象,可以看到我特意把它定义成 Object 类型,而不是 UserService 类型,也就是说 LogHandler 的目标对象可以是任意类型,而不局限在 UserService。这样一来 LogHandler 就可以在 任意类型的任意方法 前后输出log,所以称它为处理模版

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

/**
 * 这是一个自定义的处理模版,它将在 目标方法 的前后输出log
 */
public class LogHandler implements InvocationHandler {

    // 目标对象
    //private UserService target;
    private Object target;

    // 初始化时,将目标对象传进来
    public LogHandler(Object target) {
        this.target = target;
    }

    /**
     * 定义一个在目标方法执行前后输出log的处理模版
     *
     * @param proxy  动态代理对象
     * @param method 目标方法
     * @param args   目标的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        //调用目标对象的目标方法,target 是初始化时传人的目标对象
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before() {
        System.out.println("调用目标方法之前输出的log");
    }

    private void after() {
        System.out.println("调用目标方法之后输出的log");
    }
}

这里留一个疑问,谁去调用invoke()方法,方法中的三个参数是什么意思(虽然我已经写上去了)?

测试类

上面三个类构成了jdk动态代理的最小组成单位,接下来,编写测试类使用jdk动态代理

import java.lang.reflect.Proxy;

public static void main(String[] args) {
    //创建一个目标对象
    UserService userServiceImpl = new UserServiceImpl();
    // 创建 目标对象的 处理模版
    LogHandler userServiceLogHandler = new LogHandler(userServiceImpl);
    /**
     * Proxy.newProxyInstance 会根据 目标对象的 类加载器、接口的Class、目标对象的处理模版,动态生成一个代理类,并返回代理对象
     * userServiceProxy 就是 动态创建出的代理对象,它实现了 UserService 接口 的login方法
     */
    UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userServiceImpl.getClass().getClassLoader(),
            new Class[]{UserService.class}, userServiceLogHandler);
    userServiceProxy.login("wqlm", "123");
    System.out.println("\n代理类的名称为" + userServiceProxy.getClass().getName());
}

输出结果

/Library/Java/jdk1.8.0_201/Contents/Home/bin/java...
调用目标方法之前输出的log
校验账号
调用目标方法之后输出的log

代理类的名称为com.sun.proxy.$Proxy0

Process finished with exit code 0

java.lang.reflect.Proxy

测试方法中就是使用该类的方法来生成代理对象的!

这是JDK动态代理中最为重要的一个类,通过该类的 newProxyInstance 方法,我们可以生成一个代理类

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

如上,newProxyInstance 需要三个参数

  • ClassLoader ,目标类target 的类加载器
  • Class<?>[] ,目标类的接口 Interface 的Class
  • InvocationHandler , 处理模版类 Handler

生成代理类的过程如下

  1. Proxy.newProxyInstance 方法, 通过传递给它的参数 Class<?>[] interfaces, InvocationHandler h 生成代理类的字节码
  2. Proxy 通过传递给它的参数(ClassLoader)来加载生成的代理类的字节码
  3. 返回加载完成的代理类的对象

动态生成的代理类

由于代理类是在运行时动态生成的,没有.java文件,也没有.class文件。但是根据上面的运行结果我们知道,生成的动态代理类叫 com.sun.proxy.$Proxy0,可以通过

byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class<?>[]{userServiceProxy.getClass()});
String pathDir = "/Users/wqlm/Desktop";
String path = "/$Proxy0.class";
File f = new File(pathDir);
path = f.getAbsolutePath() + path;
f = new File(path);
if (f.exists()) {
    f.delete();
}

try {
    f.createNewFile();
} catch (IOException e) {
    e.printStackTrace();
}

try (FileOutputStream fos = new FileOutputStream(path)) {
    fos.write(bytes, 0, bytes.length);
} catch (Exception e) {
    e.printStackTrace();
} 

获取字节码流,在经过反编译,就可以得到大概的.java文件。以下是处理过的 (去掉了不需要关注掉内容)代理类的.java文件

import com.example.demo.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

/**
 * 代理类 继承了 Proxy,并实现了 目标接口 UserService
 */
public final class $Proxy0 extends Proxy implements UserService {

    private static Method m3;

    static {
        try {
            m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("login",
                    new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    // 初始化时,将处理模版 传递给父类
    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    /**
     * 这就是代理方法,可以看到,它是同过调用父类的 处理模版 的invoke 方法,然后把自己,以及目标方法和参数传递进去
     */
    public final boolean login(String var1, String var2) {
        try {
            return ((Boolean) super.h.invoke(this, m3, new Object[]{var1, var2})).booleanValue();
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }
}
  • 动态代理类实现了目标类的接口,并实现了接口中的方法,该方法的实现逻辑是, 调用父类 —— Proxy 的 h 的 invoke()方法
  • 其中h 是 在创建动态代理实例时 newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) 传入的第3个参数InvocationHandler对象
  • 在 InvocationHandler.invoke()中通过反射,调用目标对象

还记得在 处理模版那节留的疑问吗—— “留一个疑问,谁去调用invoke()方法,方法中的三个参数是什么意思” 现在我们知道了,是代理类去调用invoke(),它会传3个参数,自己(代理对象自己)、目标方法、参数列表

总结

JDK的动态代理

特点: 不需要显式实现与目标类相同的接口,而是将这种实现推迟到程序调用时由 JVM来实现,

  • 即:在使用时再创建动态代理类 和 实例
  • 静态代理则是在代理类实现时就指定与目标相同的接口

优点:一个代理类就可以代理多个目标类,避免重复、多余代码

缺点

  • 相比与静态代理,效率低。 静态代理是直接调用目标对象方法,而动态代理则需要先生成类和对象,在通过Java反射机制间接调用目标对象的方法
  • 应用场景局限, 由于每个代理类都继承了 java.lang.reflect.Proxy 类,而Java又只能支持单继承,导致不能针对类 创建代理类,只能针对接口 创建 代理类。即,动态代理只能代理实现了接口的类。

应用场景

  • 需要代理的对象数量较多的情况下。数量较少时,推荐直接使用静态代理
  • 面向切面编程时

与静态代理的区别

设计模式 代理类创建时机 原理 效率 能代理的目标类的数量
动态代理 运行时动态创建 反射 能代理多个目标类
静态代理 运行前,先显示创建好 / 只能代理一个目标类