0%

代理模式之动态代理

代理模式简单来说就是一个对象 A 代替另一个对象 B 去完成对象 B 本该自己做的事,在代理的过程中,对象 A 可以对被代理的事情进行加强。静态代理与动态代理的最大区别是静态代理是程序员在代码中实现了代理类,而动态代理则是在程序运行时动态地生成代理类。

在看动态代理之前,我们首先要理解什么是静态代理,如果还不明白什么是静态代理可以先去看看我之前的文章代理模式之静态代理

背景

假设我们已经实现了一个项目的很多业务逻辑代码,现在客户要求我们在所有的业务逻辑的执行前后都添加日志记录,这时候我们应该怎么办?

  • 在每个业务逻辑实现的方法前后添加日志代码——太麻烦且会修改源代码,不符合开闭原则
  • 使用静态代理——在实现代理类时需要实现接口中的所有方法,并在方法中都添加日志,同样很麻烦,优点是不用修改源代码
  • 使用动态代理——在程序运行时自动实现代理类

那么动态代理是如何实现的呢?我们接着往下看。

三个角色

  • 代理接口: 要被代理的行为在这个接口中被声明,委托类与代理类都实现了这个接口
  • 委托类: 在这里是指实现业务逻辑的那些类
  • 代理类: 代理委托类,并可增强委托类方法的功能,在这里指在执行委托类的方法时为其添加日志

关系图

这三个角色及其作用与静态代理是完全一致的,差别就在于动态代理中的代理类并不是程序员写好在程序中的而是在运行时自动生成的

实现过程

原理

明白了代理模式的结构与关系后,我们就不难理解动态代理实现的步骤了。我们知道动态代理是在运行时根据代理接口和委托类生成代理类,那么我们首先肯定得先写好代理接口和委托类,接着我们需要再写一个“工具”,在运行时这个工具能获取到代理接口和委托类的信息,并根据这些信息自动生成代理类,最后用户使用生成的代理类创建代理对象,这样用户就能通过代理对象调用方法了。

那么,我们要实现的这个“工具”中都干了什么呢?

伪代码过程

  1. 实现一个名为 InvocationHandler 的接口的类,这个接口中只有一个方法需要实现,叫做 invoke,而 invoke 方法就是对委托类中方法的代理及增强

  2. 通过代理接口生成代理类,代理类中包含一个成员对象,它的类型是第1步提到的 InvocationHandler。代理类中的方法是根据接口中声明的方法生成的,在每个方法中都会调用到 第1步中的方法 invoke。代理类中还会生成一个构造器,它的参数类型是 InvocationHandler,用于初始化代理类中的那个成员变量。

    Each proxy instance has an associated invocation handler object, the one that was passed to its constructor.

    ——Java官方文档中对Dynamic Proxy Classes的描述

  3. 获取调用第2步生成的以 InvocationHandler 为参数的代理类的构造器

  4. 调用第3步获取的代理类的构造器,传入第1步实现的 InvocationHandler 作为参数,最后返回实例化后的代理对象

Java官方文档中给了我们两个实现动态代理的示例:

To create a proxy for some interface Foo:

1
2
3
InvocationHandler handler = new MyInvocationHandler(...);
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);

or more simply:

1
2
3
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class },
handler);

这两段示例代码与上面说的 4 个步骤是一样的,第二个示例其实就是将第一个示例的实现方式封装在了方法里,接下来的代码实现中,我们将分别用这两种方式实现动态代理。

代码实现

背景回顾

回忆一下文章开始提到的背景,项目中已经实现了业务逻辑的代码了,该业务逻辑的代码是实现了某个业务接口。我们对其进行分析抽象,业务接口也就是我们的在代理模式中提到的三个角色中的代理接口,而业务实现代码就是委托类,现在我们要做的事情是在委托类中的所有方法中添加执行日志。

业务接口(代理接口):

1
2
3
4
5
6
/**
* 代理接口
*/
public interface Service {
boolean add(String obj);
}

业务实现代码(委托类):

1
2
3
4
5
6
7
8
9
10
/**
* 委托类
*/
public class ServiceImpl implements Service {
@Override
public boolean add(String obj) {
System.out.println("添加了新的对象:" + obj);
return true;
}
}

动态代理:

注:这部分代码可能需要有一些反射相关的知识才能帮助我们更好的理解代码

方法一:深入

  1. 创建一个类实现 InvocationHandler 接口

    这个类的作用就是实现代理方法去代理委托类中的方法,并对其进行增强。而这个代理的方法就是 InvocationHandler 接口中声明的 invoke 方法。我们可以理解为 invoke 方法就是一个模板,它可以为委托类中的方法都套上一个壳,作为代理对象中的一个方法,也就是委托类中被代理后的方法,程序运行时能根据这个模板生成委托类中要被代理的方法。

    MyInvocationHandler.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;

    public class MyInvocationHandler implements InvocationHandler {

    Object target; // 委托对象

    public MyInvocationHandler(){}

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

    /**
    * 该方法可以理解为被代理的方法的一个模板,程序运行时能根据这个模板生成委托类中要被代理的方法
    * @param proxy 动态代理中运行时生成的代理对象
    * @param method 被代理的方法
    * @param args 被代理方法的参数
    * @return 返回值的类型与被代理方法的返回值类型有关
    * @throws Throwable
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result;
    System.out.println("[日志]执行方法 " + method.getName() + " 前"); // 方法增强
    result = method.invoke(target, args); // 执行被代理的方法
    System.out.println("[日志]执行方法 " + method.getName() + " 后"); // 方法增强
    return result; // 代理方法的返回值
    }
    }

    invoke 方法中的返回值:

    • 当代理接口声明的返回值是基本数据类型,那么 invoke 返回的值必须是对应的包装类的实例
    • 如果 invoke 返回的值为 null 且接口方法的返回类型为基本数据类型,则代理实例上的方法调用将抛出 NullPointerException
    • 如果 invoke 返回的值与接口中方法声明的返回类型不兼容,则代理实例将抛出 ClassCastException。
  2. 通过代理接口生成代理类

  3. 获取调用第2步生成的以 InvocationHandler 为参数的代理类的构造器

  4. 调用第3步获取的代理类的构造器,传入第1步实现的 InvocationHandler 作为参数,最后返回实例化后的代理对象

    2、3、4步代码就一起展示了:

    Main.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Proxy;

    public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    ServiceImpl serviceImpl = new ServiceImpl(); // 委托实例

    /***** 第1步 *****/
    InvocationHandler myInvocationHandler = new MyInvocationHandler(serviceImpl);

    /***** 第2步 *****
    * 通过类加载器与代理接口生成代理类 proxyClass
    * 其中类加载器 serviceImpl.getClass().getClassLoader()
    * 代理接口 serviceImpl.getClass().getInterfaces()
    */
    Class proxyClass = Proxy.getProxyClass(serviceImpl.getClass().getClassLoader(),serviceImpl.getClass().getInterfaces());

    /***** 第3步 *****
    * 获取代理类的指定构造器(以 InvocationHandler 为参数的构造器)
    */
    Constructor proxyConstructor = proxyClass.getConstructor(InvocationHandler.class);

    /***** 第4步 *****
    * 通过构造器实例化代理对象
    */
    Service proxyInstance = (Service) proxyConstructor.newInstance(myInvocationHandler);

    // 调用代理方法,测试方法增强并测试返回值
    System.out.println("返回值:" + proxyInstance.add("MakerHu"));
    }
    }

    运行结果:

    1
    2
    3
    4
    [日志]执行方法 add 前
    添加了新的对象:MakerHu
    [日志]执行方法 add 后
    返回值:true

方法二:浅出

除了上述的这种方式实现动态代理,Java官方文档中还给了我们另一个简单的实现方式的示例(在前面也提到了):

or more simply:

1
2
3
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class },
handler);

这种方法就是将方法一中的步骤全都放到了方法 newProxyInstance 中帮我们实现好了,因此我们只要直接调用就能直接获取到代理对象了。这种方法其他部分的代码都不用变,只要修改 Main.java 为如下:

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
ServiceImpl serviceImpl = new ServiceImpl(); // 委托实例

/***** 第1步 *****
* 创建 InvocationHandler 的实例
*/
InvocationHandler myInvocationHandler = new MyInvocationHandler(serviceImpl);

/**
* 第 2、3、4 步整合为了一个方法
*/
Service proxyInstance = (Service) Proxy.newProxyInstance(serviceImpl.getClass().getClassLoader(),
serviceImpl.getClass().getInterfaces(),
myInvocationHandler);

// 调用代理方法,测试方法增强并测试返回值
System.out.println("返回值:" + proxyInstance.add("MakerHu"));
}
}

方法三:完善

为了更方便使用,我们还能将方法二中的获取代理实例的代码作为静态方法整合到 MyInvocationHandler 中去,Java官方文档中也给我们了一个更加完整的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public interface Foo {
Object bar(Object obj) throws BazException;
}

public class FooImpl implements Foo {
Object bar(Object obj) throws BazException {
// ...
}
}

public class DebugProxy implements java.lang.reflect.InvocationHandler {

private Object obj;

public static Object newInstance(Object obj) {
return java.lang.reflect.Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new DebugProxy(obj));
}

private DebugProxy(Object obj) {
this.obj = obj;
}

public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable
{
Object result;
try {
System.out.println("before method " + m.getName());
result = m.invoke(obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: " +
e.getMessage());
} finally {
System.out.println("after method " + m.getName());
}
return result;
}
}

获取代理实例时只需要:

1
2
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl());
foo.bar(null);

仔细观察我们就能发现,这种方法与第二种方法没有本质区别,只是将第二种方法中的 Proxy.newProxyInstance 方法移动到了实现了 InvocationHandler 接口的类中作为一个静态方法被调用。

用这种方法实现我们的例子:

MyInvocationHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler {

private Object target;

public MyInvocationHandler() {
}

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

/** 与方法二的差别:静态方法调用 **/
public static Object newInstance(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHandler(target));
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
System.out.println("[日志]执行方法 " + method.getName() + " 前");
result = method.invoke(target, args);
System.out.println("[日志]执行方法 " + method.getName() + " 后");
return result;
}
}

Main.java

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
ServiceImpl student = new ServiceImpl();
Service proxyInstance = (Service) MyInvocationHandler.newInstance(student);
System.out.println("方法返回值:" + proxyInstance.add("MakerHu"));
}
}

总结

动态代理与静态代理的区别就是静态代理的代理类是写在程序中的,而动态代理的代理类是在程序运行时借助 InvocationHandler,Proxy.getProxyClass 等生成的,动态代理由于其实现方式更加灵活,因此可以同时代理不同接口的行为而不用修改代码。以上述背景为例,如果还有一部分业务逻辑实现了接口 Service02.java,领导要求我们也要为这部分业务方法添加日志,那么使用动态代理并不需要更改代码,只要将实现了 Service02 接口的对象传入方法中即可,而静态代理则需要再写一个代理类来完成同样的任务,使代码更加冗余了。

参考