Java反射(三):反射与动态代理

反射与动态代理

代理模式

代理模式定义:

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

为什么使用代理模式:

使用代理模式创建代表对象,让代表对象控制真实对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

简单来说,我们用代理对象来代替真实对象,这样就可以在不修改真实对象的前提下,提供额外的功能操作,拓展目标对象的功能。

以现实生活为例:被告人接受法官问话时,委托一个律师来代替他接受问话。这里的代表对象是律师,真实对象是被告人。律师依据他的专业知识会对法官的问题做出进一步的解释后,私底下来问被告人。嫌疑人私底下给律师说明情况后,律师依靠他的应答技巧再向法官说明解释。

代理模式.jpg

代理模式有静态代理动态代理两种实现方式。

静态代理

静态代理在程序运行前就将接口、委托类、代理类准备完成(即存在相应的字节码文件)。

以打印"Hello, world!"为例,静态代理实现步骤为:

  1. 定义一个接口
interface HelloWordService {
    void printHelloWorld();
}
复制代码
  1. 委托类实现此接口
class HelloWorldServiceImpl implements HelloWordService {
    @Override
    public void printHelloWorld() {
        System.out.println("Hello, world!");
    }
}
复制代码
  1. 代理类同样实现此接口
class HelloWorldProxy implements HelloWordService {
    private final HelloWordService helloWorldService;

    public HelloWorldProxy(HelloWordService helloWorldService) {
        this.helloWorldService = helloWorldService;
    }

    @Override
    public void printHelloWorld() {
        System.out.println("作为代理我先来做些事");
        helloWorldService.printHelloWorld();
        System.out.println("作为代理最后再做些事");
    }
}
复制代码
  1. 将委托对象注入进代理对象,在代理对象的对应方法中调用委托对象的对应方法
public class Main {
    public static void main(String[] args) {
        HelloWordService helloWordService = new HelloWorldServiceImpl();
        HelloWordService helloWordProxy = new HelloWorldProxy(helloWordService);
        helloWordProxy.printHelloWorld();
    }
}
复制代码

运行后,控制台打印输出

作为代理我先来做些事
Hello, world!
作为代理最后再做些事

静态代理中,需要对每一种委托类单独写一个代理类,且对委托对象的每个方法的增强都是手动完成的。一旦接口增加新的方法,委托类和代理类都要进行修改。因此我们需要更加灵活的动态代理。

JDK 动态代理

动态代理在程序运行时动态生成代理类字节码并加载进JVM中。

相比于静态代理,动态代理更为灵活。我们只需要定义接口和实现类,无需编写代理类。

JDK 为我们提供了InvocationHandler接口Proxy类去实现动态代理。

Proxy类

Proxy类中最常用的方法是:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
复制代码

这个静态方法的参数为:

  1. loader:类加载器,用于加载代理对象
  2. interfaces:委托类实现的一些接口,至少传入一个接口进去
  3. h:实现了InvocationHandler接口的实例来进行逻辑处理

InvocationHandler接口

由第3个参数知,我们必须要实现InvocationHandler接口。当我们调用动态代理对象一个方法时,JVM会调用InvocationHandler接口实现类的invoke方法来处理对应的逻辑。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
复制代码

该方法有的参数为:

  1. proxy:JVM动态生成的代理类实例
  2. method:与调用代理类实例的方法相对应的Method类实例
  3. args:调用代理类实例方法的参数

JDK 动态代理的使用

继续以打印"Hello, world!"为例,动态代理的实现步骤为:

  1. 定义接口
interface HelloWordService {
    void printHelloWorld();
}
复制代码
  1. 委托类实现该接口
class HelloWorldServiceImpl implements HelloWordService {
    @Override
    public void printHelloWorld() {
        System.out.println("Hello, world!");
    }
}
复制代码
  1. 定义一个 JDK 动态代理类,在invoke方法中调用原生方法并自定义逻辑处理
class SimpleInvocationHandler implements InvocationHandler {
    private final Object target;

    public SimpleInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("作为代理我先来做些事");
        Object result = method.invoke(target, args);
        System.out.println("作为代理最后再做些事");
        return result;
    }
}
复制代码
  1. 通过Proxy.newProxyInstance方法创建代理对象
// 定义一个生产代理对象的静态工厂类
class ProxyFactory {
    @SuppressWarnings("unchecked")
    public static <T> T getProxy(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new SimpleInvocationHandler(target)
        );
    }
}

public class Main {
    public static void main(String[] args) {
        // 通过代理工厂,生产代理类实例
        HelloWordService serviceProxy = 
                ProxyFactory.getProxy(new HelloWorldServiceImpl());
        
        serviceProxy.printHelloWorld();
    }
}
复制代码

运行后,控制台打印输出

作为代理我先来做些事
Hello, world!
作为代理最后再做些事

总结

  • 使用代理模式可以在不修改真实对象的前提下,提供额外的功能操作,拓展目标对象的功能
  • 静态代理在程序运行前就将接口、委托类、代理类准备完成(即存在相应的字节码文件)
  • 动态代理在程序运行时动态生成代理类字节码并加载进JVM中

Guess you like

Origin juejin.im/post/7035380544753893384