【注解与反射】7 - 3 动态代理

§7-3 动态代理

7-3.1 动态代理的思想

动态代理可无侵入式地为对象添加额外的功能。调用者借助代理在调用原对象功能的同时,可以在不修改原对象源码的情况下调用额外的功能。调用过程为:

调用者 -> 代理 -> 对象

代理类含有对象中需要被代理的方法,这些方法的签名一致,这样外部调用者可以通过代理中的代理方法调用对象方法,同时还能实现额外的功能。为了使得代理能够 “清楚” 有哪些方法需要被代理,这些方法应当由一个统一的接口来定义。这样,代理只需实现该接口并重写该方法即可。

Java 提供了用于创建代理类的 API,java.lang.reflect.Proxy。该类中的静态方法 newProxyInstance(...) 可用于创建代理类对象。

Proxy 的静态方法

静态方法 描述
static InvocationHandler(Object proxy) 返回代理类对象的调用处理器
static boolean isProxyClass(Class<?> cl) 测试给定类是否为代理类
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 为指定的接口返回将方法调用分配给指定调用处理器的代理类对象

但在开始使用动态代理前,我们需要确定以下内容:

  1. 什么类需要被代理;
  2. 类中哪些方法需要被代理,使用接口定义该方法,原始类和代理类都需要实现接口并重写上述方法。

7-3.2 准备工作

实现动态代理的前提是明确被代理对象和代理方法。这里以 BigStar 类为被代理类,其中 String sing(String)void dance() 方法需要被代理。被代理的方法由接口 Star 定义,且由 BigStar 实现并重写。

Star 接口声明:

public interface Star {
    String sing(String songName);
    void dance();
}

BigStar 类声明:

public class BigStar implements Star {
    private String name;
    
    public BigStar() {}
    
    public BigStar(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "BigStar{name = " + name + "}";
    }

    @Override
    public String sing(String songName) {
        System.out.println(this.name + "唱了" + songName + "。");
        return "谢谢大家。";
    }
    
    // 省略 Getters & Setters 及 toString() 方法
}

这些准备工作做好之后,接下来就是定义一个用于创建指定代理类的工具类。这里该工具类声明为 StarProxy

7-3.3 声明代理工具类

在本例中,代理工具类有且仅有一个静态公共方法 newProxy,该方法内部调用 Proxy.newProxyInstance(...) 方法以返回一个代理类对象。该方法的参数较多,参数的说明如下说明。

newProxyInstance() 方法形参说明

参数 类型 说明 解释
ClassLoader loader 类加载器 定义代理的类的加载器 谁定义代理类,填入谁的加载器。本例为 StarProxy.class.getClassLoader()
Class<?>[] interfaces 接口 Class 对象数组 代理类所实现的接口的数组 一个存放了定义需要被代理的方法的接口的 Class 数组。接口可以有多个。本例中仅有 Star
InvocationHandler h 调用处理器 真正执行代理方法的接口 这是一个函数式接口,仅有一个抽象方法 Object invoke(Object proxy, Method method, Object[] args)
被代理的方法由此方法执行,通过反射获取代理方法并传入形参。

该方法返回一个 Object 类型的代理对象,可使用强制类型转换将对象转换成指定类型。

本例中,我们需要定义一个代理工具类 StarProxy 创建代理对象,该类有且仅有一个方法 Star。声明如下:

public class StarProxy {
    /**
     * 创建一个代理对象。
     * 
     * @param bigStar 被代理的对象
     * @return 		  代理对象
     */
    public static Star newProxy(BigStar bigStar) {
        // 调用 Proxy 的静态方法创建代理对象并返回,使用强转转换类型
        return (Star) Proxy.newProxyInstance(
                StarProxy.class.getClassLoader(),	// 定义代理的类,这里由 StarProxy 定义代理,填入它的加载器
                new Class<?>[]{Star.class},			// 所有定义了希望被代理的方法的接口,可以有多个
                new InvocationHandler() {			// 使用匿名内部类重写接口的方法,可改写为 Lambda 表达式
                    @Override
                    public Object invoke
                        (Object proxy,		// 代理
                         Method method,		// 代理方法,调用 method.invoke() 以调用原对象的方法
                         Object[] args)		// 方法可能有的形参
                        throws Throwable {
                        // 非侵入式地为原对象添加功能,由代理实现
                        if ("sing".equals(method.getName())) {
                            System.out.println("代理正在准备场地、售票、收钱。");
                        } else if ("dance".equals(method.getName())) {
                            System.out.println("代理正在准备场地。");
                        }
						
                        // 由原对象调用原方法,并传入希望的方法形参(如有的话),并返回方法返回值。
                        return method.invoke(bigStar, args);
                    }
                }
        );
    }
}

这样,一个代理就定义完毕。

7-3.4 测试代理

在测试类中获取上述声明的代理,并依次调用被代理的方法:

public class DynamicProxyDemo {
    public static void main(String[] args) {
        Star star = StarProxy.newProxy(new BigStar("鸡哥"));
        System.out.println(star.sing("只因你太美"));
        star.dance();
    }
}

得到结果:

代理正在准备场地、售票、收钱。
鸡哥唱了只因你太美。
谢谢大家。
代理正在准备场地。
鸡哥正在跳舞。

可以看到,被代理的方法在原基础上添加了额外的功能,而这并不涉及对原方法的侵入式修改。

通过代理调用方法时,会最终调用创建代理时传入的 IncocationHandlerinvoke() 方法。

posted @ 2024-01-28 00:46  Zebt  阅读(17)  评论(0)    收藏  举报