48设计模式与范式之结构型-代理模式:代理在RPC、缓存、监控等场景中的应用

前面几节,我们学习了设计模式中的创建型模式。创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。

其中,单例模式用来创建全局唯一的对象。工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。

从今天起,我们开始学习另外一种类型的设计模式:结构型模式。结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。今天我们要讲其中的代理模式。它也是在实际开发中经常被用到的一种设计模式。

话不多说,让我们正式开始今天的学习吧!

代理模式的原理解析(静态代理)

代理模式(Proxy Design Pattern)的原理和代码实现都不难掌握。它在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。我们通过一个简单的例子来解释一下这段话。

这个例子来自我们在第25、26、39、40节中讲的性能计数器。当时我们开发了一个MetricsCollector类,用来收集接口请求的原始数据,比如访问时间、处理时长等。在业务系统中,我们采用如下方式来使用这个MetricsCollector类:

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
public class UserController {
//...省略其他属性和方法...
private MetricsCollector metricsCollector; // 依赖注入

public UserVo login(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();

// ... 省略login逻辑...

long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);

//...返回UserVo数据...
}

public UserVo register(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();

// ... 省略register逻辑...

long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);

//...返回UserVo数据...
}
}

很明显,上面的写法有两个问题。第一,性能计数器框架代码侵入到业务代码中,跟业务代码高度耦合。如果未来需要替换这个框架,那替换的成本会比较大。第二,收集接口请求的代码跟业务代码无关,本就不应该放到一个类中。业务类最好职责更加单一,只聚焦业务处理。

为了将框架代码和业务代码解耦,代理模式就派上用场了。代理类UserControllerProxy和原始类UserController实现相同的接口IUserController。UserController类只负责业务功能。代理类UserControllerProxy负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用原始类来执行业务代码。具体的代码实现如下所示:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public interface IUserController {
UserVo login(String telephone, String password);
UserVo register(String telephone, String password);
}

public class UserController implements IUserController {
//...省略其他属性和方法...

@Override
public UserVo login(String telephone, String password) {
//...省略login逻辑...
//...返回UserVo数据...
}

@Override
public UserVo register(String telephone, String password) {
//...省略register逻辑...
//...返回UserVo数据...
}
}

public class UserControllerProxy implements IUserController {
private MetricsCollector metricsCollector;
private UserController userController;

public UserControllerProxy(UserController userController) {
this.userController = userController;
this.metricsCollector = new MetricsCollector();
}

@Override
public UserVo login(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();

// 委托
UserVo userVo = userController.login(telephone, password);

long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);

return userVo;
}

@Override
public UserVo register(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();

UserVo userVo = userController.register(telephone, password);

long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);

return userVo;
}
}

//UserControllerProxy使用举例
//因为原始类和代理类实现相同的接口,是基于接口而非实现编程
//将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码
IUserController userController = new UserControllerProxy(new UserController());

参照基于接口而非实现编程的设计思想,将原始类对象替换为代理类对象的时候,为了让代码改动尽量少,在刚刚的代理模式的代码实现中,代理类和原始类需要实现相同的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的(比如它来自一个第三方的类库),我们也没办法直接修改原始类,给它重新定义一个接口。在这种情况下,我们该如何实现代理模式呢?

对于这种外部类的扩展,我们一般都是采用继承的方式。这里也不例外。我们让代理类继承原始类,然后扩展附加功能。原理很简单,不需要过多解释,你直接看代码就能明白。具体代码如下所示:

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
public class UserControllerProxy extends UserController {
private MetricsCollector metricsCollector;

public UserControllerProxy() {
this.metricsCollector = new MetricsCollector();
}

public UserVo login(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();

UserVo userVo = super.login(telephone, password);

long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);

return userVo;
}

public UserVo register(String telephone, String password) {
long startTimestamp = System.currentTimeMillis();

UserVo userVo = super.register(telephone, password);

long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);

return userVo;
}
}
//UserControllerProxy使用举例
UserController userController = new UserControllerProxy();

动态代理的原理解析

不过,刚刚的代码实现还是有点问题一方面,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面,如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。

如果有50个要添加附加功能的原始类,那我们就要创建50个对应的代理类。这会导致项目中类的个数成倍增加,增加了代码维护成本。并且,每个代理类中的代码都有点像模板式的“重复”代码,也增加了不必要的开发成本。那这个问题怎么解决呢?

我们可以使用动态代理来解决这个问题。所谓动态代理(Dynamic Proxy)就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。那如何实现动态代理呢?

如果你熟悉的是Java语言,实现动态代理就是件很简单的事情。因为Java语言本身就已经提供了动态代理的语法(实际上,动态代理底层依赖的就是Java的反射语法)。我们来看一下,如何用Java的动态代理来实现刚刚的功能。具体的代码如下所示。其中,MetricsCollectorProxy作为一个动态代理类,动态地给每个需要收集接口请求信息的类创建代理类。

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
public class MetricsCollectorProxy {
private MetricsCollector metricsCollector;

public MetricsCollectorProxy() {
this.metricsCollector = new MetricsCollector();
}

public Object createProxy(Object proxiedObject) {
Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
}

private class DynamicProxyHandler implements InvocationHandler {
private Object proxiedObject;

public DynamicProxyHandler(Object proxiedObject) {
this.proxiedObject = proxiedObject;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTimestamp = System.currentTimeMillis();
Object result = method.invoke(proxiedObject, args);
long endTimeStamp = System.currentTimeMillis();
long responseTime = endTimeStamp - startTimestamp;
String apiName = proxiedObject.getClass().getName() + ":" + method.getName();
RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp);
metricsCollector.recordRequest(requestInfo);
return result;
}
}
}

//MetricsCollectorProxy使用举例
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());

实际上,Spring AOP底层的实现原理就是基于动态代理。用户配置好需要给哪些类创建代理,并定义好在执行原始类的业务代码前后执行哪些附加功能。Spring为这些类创建动态代理对象,并在JVM中替代原始类对象。原本在代码中执行的原始类的方法,被换作执行代理类的方法,也就实现了给原始类添加附加功能的目的。

代理模式的应用场景

代理模式的应用场景非常多,我这里列举一些比较常见的用法,希望你能举一反三地应用在你的项目开发中。

1.业务系统的非功能性需求开发

代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发(这里有个问题,如何让多个非功能性需求共同生效,创建多个代理实例?还是生成代理的代理,一层层嵌套的代理?)。实际上,前面举的搜集接口请求信息的例子,就是这个应用场景的一个典型例子。

如果你熟悉Java语言和Spring开发框架,这部分工作都是可以在Spring AOP切面中完成的。前面我们也提到,Spring AOP底层的实现原理就是基于动态代理。

2.代理模式在RPC、缓存中的应用

实际上,RPC框架也可以看作一种代理模式,GoF的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用RPC服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。

关于远程代理的代码示例,我自己实现了一个简单的RPC框架Demo,放到了GitHub中,你可以点击这里的链接查看。

**我们再来看代理模式在缓存中的应用。**假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。比如,针对获取用户个人信息的需求,我们可以开发两个接口,一个支持缓存,一个支持实时查询。对于需要实时数据的需求,我们让其调用实时查询接口,对于不需要实时数据的需求,我们让其调用支持缓存的接口。那如何来实现接口请求的缓存功能呢?

最简单的实现方法就是刚刚我们讲到的,给每个需要支持缓存的查询需求都开发两个不同的接口,一个支持缓存,一个支持实时查询。但是,这样做显然增加了开发成本,而且会让代码看起来非常臃肿(接口个数成倍增加),也不方便缓存接口的集中管理(增加、删除缓存接口)、集中配置(比如配置每个接口缓存过期时间)。

针对这些问题,代理模式就能派上用场了,确切地说,应该是动态代理。如果是基于Spring框架来开发的话,那就可以在AOP切面中完成接口缓存的功能。在应用启动的时候,我们从配置文件中加载需要支持缓存的接口,以及相应的缓存策略(比如过期时间)等。当请求到来的时候,我们在AOP切面中拦截请求,如果请求中带有支持缓存的字段(比如http://…?..&cached=true),我们便从缓存(内存缓存或者Redis缓存等)中获取数据直接返回。

动态代理详细解析(扩展)

静态代理

代理模式 - 菜鸟

JDK动态代理

JDK中的动态代理是通过 反射类 Proxy 以及 InvocationHandler 回调接口 实现的,JDK中所有要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。

动态代理神奇的地方就是:

  • 代理对象是在程序运行时产生的,而不是编译期;
  • 对代理对象的所有接口方法调用都会转发到 InvocationHandler.invoke() 方法,在 invoke() 方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体。

注意:对于从Object中继承的方法,JDK Proxy会把 hashCode()equals()toString() 这三个非接口方法转发给 InvocationHandler,其余的Object方法则不会转发。详见JDK Proxy官方文档

通过秒表代理创建动态代理类,实现程序执行时间的计算:

接口

被代理类(原始类)必须要实现的接口

1
2
3
4
5
6
/**
* 需要动态代理的类必须要实现的接口
*/
public interface IUserService {
public String selectPasswdById(Integer userId);
}

原始类

被代理类(原始类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.concurrent.TimeUnit;

/**
* 需要动态代理的类
*/
public class UserServiceImpl implements IUserService {
@Override
public String selectPasswdById(Integer userId) {
System.out.println("通过用户ID查询: selectById");
try {
// 模拟查询耗时
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return String.valueOf(userId);
}
}

实现InvocationHandler接口

实现 java.lang.reflect.InvocationHandler 接口,定义一个调用处理程序

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
import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;

/**
* 调用处理程序,实现 {@link java.lang.reflect.InvocationHandler}
*/
public class CustomInvocationHandler implements java.lang.reflect.InvocationHandler {
// 被代理的对象(原始类对象)
private Object proxiedObject;
// 这里还可以委托其他对象,比如想记录程序执行时间就可以委托收集器对象

public CustomInvocationHandler(Object proxiedObject) {
this.proxiedObject = proxiedObject;
}

/**
* @param proxy 动态生成的代理类实例
* @param method 被代理的接口方法对应的Method实例,即原始类中方法的实例
* @param args 对象数组,被代理的方法参数,如果接口方法不带参数,则为null。接口中未声明的方法不被代理
* @return java.lang.Object
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
StopWatch stopWatch = StopWatch.createStarted();

System.out.println("原始方法调用前");
Object result = method.invoke(proxiedObject, args); // 调用原始类的原始方法
System.out.println("原始方法调用后");

stopWatch.stop();
String apiName = proxy.getClass().getName() + ":" + method.getName();
System.out.println(apiName + " 执行耗时: " + stopWatch.getTime() + "ms");

// 这里可以通过组合委托实现其他操作,比如记录程序执行时间

return StringUtils.join("代理修改了结果: ", "21218CCA77804D2BA1922C33E0151105");
}
}

创建动态代理类

可以称为 创建代理对象的工厂 吧。实际上将 InvocationHandler 的实现类作为 private 修饰的内部类更加合适,由于下文 CGLIB 会用到,所以放到外面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 代理类工厂
*/
public class CustomProxy {
/**
* 创建动态代理对象
*
* @param proxiedObject 被代理的对象(原始类对象)
* @return java.lang.Object
*/
public static Object createCustomProxy(Object proxiedObject) {
ClassLoader classLoader = proxiedObject.getClass().getClassLoader();
Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
CustomInvocationHandler handler = new CustomInvocationHandler(proxiedObject);
/*
创建一个代理类对象,接收三个参数,Proxy类提供了很多方法,但是我们最常用的是newProxyInstance方法。
loader:用于定义代理类的类加载器
interfaces:代理类要实现的接口列表。代理类实现了这些接口,就可以调用接口中声明的所有方法。
h:表示当通过动态代理对象调用方法时,将方法调用分发到InvocationHandler对象上,并最终由其调用。
*/
return java.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}

使用动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) {
// 将JDK动态代理生成的类保存为.class文件。默认在项目的根目录下
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // JDK8之前
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); // JDK8之后

IUserService userService = (IUserService) CustomProxy.createCustomProxy(new UserServiceImpl());
System.out.println(userService.selectPasswdById(123456));
System.out.println();

System.out.println("代理类 : " + userService.getClass()); // InvocationHandler.invoke的proxy参数就是代理类的实例
System.out.println("代理类的父类: " + userService.getClass().getSuperclass());
System.out.println("代理类的实现: " + Arrays.toString(userService.getClass().getInterfaces()));

// Proxy类的另外两个方法的使用
if (java.lang.reflect.Proxy.isProxyClass(userService.getClass())) {
java.lang.reflect.InvocationHandler handler = java.lang.reflect.Proxy.getInvocationHandler(userService);
System.out.println("调用处理程序: " + handler.getClass());
}
}
}

输出:

1
2
3
4
5
6
7
8
9
10
原始方法调用前
通过用户ID查询: selectById
原始方法调用后
com.sun.proxy.$Proxy0:selectPasswdById 执行耗时: 105ms
代理修改了结果: 21218CCA77804D2BA1922C33E0151105

代理类 : class com.sun.proxy.$Proxy0
代理类的父类: class java.lang.reflect.Proxy
代理类的实现: [interface com.zhaolq.mars.demo.a.IUserService]
调用处理程序: class com.zhaolq.mars.demo.a.CustomInvocationHandler

问题

InvocationHandlerinvoke 方法的第一个参数 proxy 好像从来没有用过

  • 为什么JDK的动态代理一定要基于接口实现
    查看代理类源码可知,其继承了 java.lang.reflect.Proxy 类,实现了被代理的接口。由于Java是单继承,不能再继承其他类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

  • proxy代表什么意思
    proxy是真实对象的真实代理对象,invoke方法可以返回调用代理对象方法的返回结果,也可以返回对象的真实代理对象(com.sun.proxy.$Proxy0)。

  • proxy参数怎么用及什么时候用
    当需要代理后的方法返回代理对象进行后续调用时使用。

  • proxy参数的类型是什么
    com.zhaolq.mars.demo.$Proxy0(被代理接口为非public)或 com.sun.proxy.$Proxy0(被代理接口为public)真实的代理对象。

  • 为什么不用 this 代替 proxy
    因为 this 代表的是 InvocationHandler 接口实现类本身,并不是真实的代理对象。

CGLIB(Code Generation Library)

惊喜!!!

将上文实现的 JDK 动态代理示例中所有的

java.lang.reflect.Proxy 、 java.lang.reflect.InvocationHandler

修改为

net.sf.cglib.proxy.Proxy 、 net.sf.cglib.proxy.InvocationHandler

代理一样可以运行,同样的,原始类必须实现接口。由此可见,CGLIB 实现了 JDK 动态代理 的相同功能,JDK 动态代理 是 CGLIB 的子集。

什么是CGLIB?

CGLIB 是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为 JDK 动态代理提供了很好的补充。通常可以使用 Java 的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

CGLIB 作为一个开源项目,其代码托管在 Github,地址为:https://github.com/cglib/cglib

CGLIB原理

CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

CGLIB 底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLIB缺点对于final方法,无法进行代理。

CGLIB的应用

广泛的被许多AOP的框架使用,例如Spring AOP和dynaop。Hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联。

为什么使用CGLIB?

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。

CGLIB组成结构

img

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解。

CGLIB的API

Maven依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

CGLIB类库

由于基本代码很少,学起来有一定的困难,主要是缺少文档和示例,这也是CGLIB的一个不足之处。

本系列使用的CGLIB版本是3.3.0。

  • net.sf.cglib.core: 底层字节码处理类,他们大部分与ASM有关系。
  • net.sf.cglib.transform: 编译期或运行期类和类文件的转换
  • net.sf.cglib.proxy: 实现创建代理和方法拦截器的类
  • net.sf.cglib.reflect: 实现快速反射和C#风格代理的类
  • net.sf.cglib.util: 集合排序等工具类
  • net.sf.cglib.beans: JavaBean相关的工具类

本篇介绍通过MethodInterceptor和Enhancer实现一个动态代理。

CGLIb实现动态代理

使用CGLIb实现动态代理,完全不受代理类必须实现接口的限制,而且CGLIb底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLIb不能对声明为final的方法进行代理,因为CGLIb原理是动态生成被代理类(原始类)的子类,final修饰的方法不能被重写。

下面,将通过一个实例介绍使用CGLIb实现动态代理。

原始类

被代理类(原始类),没有实现任何接口

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
import java.util.concurrent.TimeUnit;

/**
* 没有实现接口,需要CGLIB动态代理的类
*/
public class UserServiceImpl {
public String selectPasswdById(Integer userId) {
System.out.println("通过用户ID查询: selectById");
try {
// 模拟查询耗时
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return String.valueOf(userId);
}

public int methodOne(int count) {
return count;
}

public String methodTwo(String paramName) {
return paramName;
}
}

实现MethodInterceptor接口

创建 Callback 接口的实现,可以实现子接口 net.sf.cglib.proxy.MethodInterceptor、net.sf.cglib.proxy.InvocationHandler,更多子接口见源码。

定义一个方法拦截器。在调用原始方法时,CGLIb 会回调 MethodInterceptor 接口方法拦截,来实现自己的代理逻辑,类似于 JDK 中的 InvocationHandler 接口。

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.Method;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import net.sf.cglib.proxy.MethodProxy;

/**
* 方法拦截器,实现 {@link net.sf.cglib.proxy.MethodInterceptor}
*/
public class CustomInterceptor implements net.sf.cglib.proxy.MethodInterceptor {
/**
* @param obj CGLIb动态生成的代理类实例
* @param method 被代理的方法引用,即原始类中方法的实例
* @param params 对象数组,被代理的方法参数,如果接口方法不带参数,则为null。final声明的方法不被代理
* @param proxy 方法代理对象,即代理类中方法的实例
* @return java.lang.Object 代理类实例的方法返回值
*/
@Override
public Object intercept(Object obj, Method method, Object[] params, MethodProxy proxy) throws Throwable {
StopWatch stopWatch = StopWatch.createStarted();

System.out.println("原始方法调用前");
Object result = proxy.invokeSuper(obj, params); // method和proxy有什么区别?
System.out.println("原始方法调用后");

stopWatch.stop();
String apiName = obj.getClass().getName() + ":" + method.getName();
System.out.println(apiName + " 执行耗时: " + stopWatch.getTime() + "ms");

// 这里可以通过组合委托实现其他操作,比如记录程序执行时间

return StringUtils.join("代理修改了结果: ", "21218CCA77804D2BA1922C33E0151105");
}
}

定义一个调用处理程序,**注意不是 JDK 中的 InvocationHandler 接口,这里复用上文的 CustomInvocationHandler,除了实现的接口来自不同包以外,代码完全相同。**见 惊喜!!! 目录。

创建和使用动态代理类

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
public class Test {
public static void main(String[] args) {
/**
* 这里Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展。
* 首先将原始类(被代理类)UserServiceImpl设置成父类,
* 然后设置拦截器CustomInterceptor,
* 最后执行enhancer.create()动态生成一个代理类,并从Object强制转型成父类型UserServiceImpl。
* 最后,在代理类上调用方法。
*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new CustomInterceptor());
// enhancer.setCallback(new CustomInvocationHandler(new UserServiceImpl()));
UserServiceImpl userService = (UserServiceImpl) enhancer.create();
System.out.println(userService.selectPasswdById(123456));
System.out.println();

System.out.println("代理类 : " + userService.getClass()); // MethodInterceptor.intercept的obj参数就是代理类的实例
System.out.println("代理类的父类: " + userService.getClass().getSuperclass());
System.out.println("代理类的实现: " + Arrays.toString(userService.getClass().getInterfaces()));

// Proxy类的另外两个方法的使用
if (net.sf.cglib.proxy.Proxy.isProxyClass(userService.getClass())) {
net.sf.cglib.proxy.InvocationHandler handler = net.sf.cglib.proxy.Proxy.getInvocationHandler(userService);
System.out.println("调用处理程序: " + handler.getClass());
// 思考:为什么没有进来呢?--isProxyClass貌似只能用来判断回调接口为net.sf.cglib.proxy.InvocationHandler的代理类。--
}
}
}

输出:

1
2
3
4
5
6
7
8
9
原始方法调用前
通过用户ID查询: selectById
原始方法调用后
com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$fddb78dd:selectPasswdById 执行耗时: 122ms
代理修改了结果: 21218CCA77804D2BA1922C33E0151105

代理类 : class com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$fddb78dd
代理类的父类: class com.zhaolq.mars.demo.a.UserServiceImpl
代理类的实现: [interface net.sf.cglib.proxy.Factory]

Callback[]和CallbackFilter

前面的动态代理类代理了原始类中的所有方法,那么如何让不同方法执行不同的回调(代理)逻辑呢?

Enhancer 类的 public void setCallbacks(Callback[] callbacks)public void setCallbackFilter(CallbackFilter filter) 方法可以设置对不同方法执行不同的回调(代理)逻辑,或者根本不执行回调。

在 JDK 动态代理中并没有类似的功能,对 InvocationHandler 接口方法的调用对代理类内的所以方法都有效。

实现FixedValue接口

FixedValue 也是 Callback 的子接口,表示返回固定值的回调。

注意:loadObject() 方法的返回值类型必须与代理方法的返回值类型兼容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import net.sf.cglib.proxy.FixedValue;

/**
* 表示锁定方法返回值,无论被代理类的方法返回什么值,回调方法都返回固定值。
* <p>
* 该回调无法获得正在调用的方法信息(因为loadObject()方法参数中并没有Method对象),并且返回对象的类型必须与代理方法的返回类型兼容。
* 这使得此回调主要用于强制执行特定方法(通过使用CallbackFilter以很少的开销返回固定值)。
*/
public class CustomResultFixedValue implements FixedValue {
/**
* 该类实现FixedValue接口,同时锁定回调值为整型999
*
* @return java.lang.Object 与此回调映射到的每个方法的返回值类型匹配的对象
*/
@Override
public Object loadObject() throws Exception {
Object obj = 999;
return obj;
}
}

实现CallbackFilter接口

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
import java.lang.reflect.Method;

/**
* 将Enhancer生成的子类(代理类)的方法映射到特定的回调(代理)数组Callback[],为每个方法选择的回调类型会影响子类(代理类)中为该方法生成的字节码,并且在类的生命周期内不能更改。
* <p>
* 注意: CallbackFilter实现应该是轻量级的,因为 cglib 可能会保持CallbackFilter对象处于活动状态以启用生成类的缓存。更喜欢使用static类来实现CallbackFilter。
*/
public class CustomMethodCallbackFilter implements net.sf.cglib.proxy.CallbackFilter {
/**
* 将方法映射到回调
*
* @param method method
* @return int 回调数组Callback[]的索引值。
*/
@Override
public int accept(Method method) {
if (method.getName().equals("toString")) {
System.out.println("filter toString == 0");
return 0; // 表示当调用代理类的toString方法时,执行Callback[]数组中索引为0的回调
}
if (method.getName().equals("selectPasswdById")) {
System.out.println("filter selectPasswdById == 1");
return 1;
}
if (method.getName().equals("methodOne")) {
System.out.println("filter method1 == 2");
return 2;
}
return 0; // 表示当调用代理类的其他方法时,执行Callback[]数组中索引为0的回调
}
}

测试

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 net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

public class Test {
public static void main(String[] args) {
Callback noOpCallback = NoOp.INSTANCE; // 这个NoOp表示no operator,即什么操作也不做,代理类直接调用原始类的方法不进行拦截。
Callback interceptor = new CustomInterceptor();
Callback fixedValue = new CustomResultFixedValue();
// 回调数组Callback[]
Callback[] callbackArray = new Callback[]{noOpCallback, interceptor, fixedValue};
// 代理类各方法映射到特定回调数组Callback[]的CallbackFilter
CallbackFilter callbackFilter = new CustomMethodCallbackFilter();

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallbackFilter(callbackFilter);
enhancer.setCallbacks(callbackArray);

UserServiceImpl userService = (UserServiceImpl) enhancer.create();
ConsoleKeyValue.create()
.addKeyValue("selectPasswdById", userService.selectPasswdById(123456)) // 索引为1的回调,interceptor
.addKeyValue("methodOne", String.valueOf(userService.methodOne(100))) // 索引为2的回调,fixedValue
.addKeyValue("methodTwo", userService.methodTwo("m2")) // 索引为0的回调,noOpCallback
.addKeyValue("toString", userService.toString())
.print(); // 索引为0的回调,noOpCallback
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
filter method1 == 2
filter selectPasswdById == 1
filter toString == 0
原始方法调用前
通过用户ID查询: selectById
原始方法调用后
com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$675f4824:selectPasswdById 执行耗时: 112ms
+----+--------------------+------------------------------------------------------------------------------+
selectPasswdById: 代理修改了结果: 21218CCA77804D2BA1922C33E0151105
methodOne: 999
methodTwo: m2
toString: com.zhaolq.mars.demo.a.UserServiceImpl$$EnhancerByCGLIB$$675f4824@25a65b77
+----+--------------------+------------------------------------------------------------------------------+

延迟加载对象(懒加载)

说到延迟加载,应该经常接触到,尤其是使用Hibernate的时候,本篇将通过一个实例分析延迟加载的实现方式。

两种延迟加载接口可用于创建延迟加载对象,都继承了Callback,所以都是Callback类型(回调类型):

  • LazyLoader:只在第一次使用延迟加载对象时触发代理类回调方法
  • Dispatcher:每次使用延迟加载对象时都会触发代理类回调方法

**示例:**定义一个实体类LoaderBean,该Bean内有一个需要延迟加载的属性PropertyBean。

原始类

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
public class PropertyBean {
private String key;
private Object value;

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public Object getValue() {
return value;
}

public void setValue(Object value) {
this.value = value;
}

@Override
public String toString() {
return "PropertyBean [key=" + key + ", value=" + value + "]";
}
}

实现LazyLoader接口

1
2
3
4
5
6
7
8
9
10
11
public class ConcreteClassLazyLoader implements net.sf.cglib.proxy.LazyLoader {
@Override
public Object loadObject() throws Exception {
System.out.println("before lazyLoader...");
PropertyBean propertyBean = new PropertyBean();
propertyBean.setKey("objectFromLazyLoader");
propertyBean.setValue(new Object());
System.out.println("after lazyLoader...");
return propertyBean;
}
}

实现Dispatcher接口

1
2
3
4
5
6
7
8
9
10
11
public class ConcreteClassDispatcher implements net.sf.cglib.proxy.Dispatcher {
@Override
public Object loadObject() throws Exception {
System.out.println("before Dispatcher...");
PropertyBean propertyBean = new PropertyBean();
propertyBean.setKey("objectFromDispatcher");
propertyBean.setValue(new Object());
System.out.println("after Dispatcher...");
return propertyBean;
}
}

创建延迟加载对象

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import net.sf.cglib.proxy.Enhancer;

public class LazyBean {
private String name;
private int age;
private PropertyBean propertyBeanLazyLoader;
private PropertyBean propertyBeanDispatcher;

public LazyBean(String name, int age) {
System.out.println("lazy bean init");
this.name = name;
this.age = age;
this.propertyBeanLazyLoader = createPropertyBeanLazyLoader();
this.propertyBeanDispatcher = createPropertyBeanDispatcher();
}

/**
* 使用LazyLoader回调类型创建延迟加载对象,只在第一次使用延迟加载对象时初始化(只第一次懒加载)
*
* @return
*/
private PropertyBean createPropertyBeanLazyLoader() {
/**
* 使用cglib进行懒加载 对需要延迟加载的对象添加代理。
* 当使用延迟加载对象时,先通过代理类回调方法进行对象初始化。
* 在不需要延迟加载对象时,只要不去使用它,该对象就不会被初始化了(在CGLib的实现中只要使用延迟加载对象,就会自动触发代理类回调)。
*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PropertyBean.class);
PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class, new ConcreteClassLazyLoader());
return pb;
}

/**
* 使用Dispatcher回调类型创建延迟加载对象,每次使用延迟加载对象时都会初始化(每次都懒加载)
*
* @return
*/
private PropertyBean createPropertyBeanDispatcher() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PropertyBean.class);
PropertyBean pb = (PropertyBean) enhancer.create(PropertyBean.class, new ConcreteClassDispatcher());
return pb;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public PropertyBean getPropertyBeanLazyLoader() {
return propertyBeanLazyLoader;
}

public void setPropertyBeanLazyLoader(PropertyBean propertyBeanLazyLoader) {
this.propertyBeanLazyLoader = propertyBeanLazyLoader;
}

public PropertyBean getPropertyBeanDispatcher() {
return propertyBeanDispatcher;
}

public void setPropertyBeanDispatcher(PropertyBean propertyBeanDispatcher) {
this.propertyBeanDispatcher = propertyBeanDispatcher;
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args) {
LazyBean lazyBean = new LazyBean("zhangsan", 18);

// 不会触发延迟加载对象的初始化
System.out.println(lazyBean.getName() + ": " + lazyBean.getAge());
System.out.println(lazyBean.getName() + ": " + lazyBean.getAge());

// 只在第一次使用延迟加载对象时初始化(只第一次懒加载)
System.out.println();
System.out.println(lazyBean.getPropertyBeanLazyLoader());
System.out.println(lazyBean.getPropertyBeanLazyLoader());

// 每次使用延迟加载对象时都会初始化(每次都懒加载)。现象:1、Dispatcher回调每次都执行;2、延迟加载对象的内存地址不同
System.out.println();
System.out.println(lazyBean.getPropertyBeanDispatcher());
System.out.println(lazyBean.getPropertyBeanDispatcher());
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
zhangsan: 18
zhangsan: 18

before lazyLoader...
after lazyLoader...
PropertyBean [key=objectFromLazyLoader, value=java.lang.Object@58c1c010]
PropertyBean [key=objectFromLazyLoader, value=java.lang.Object@58c1c010]

before Dispatcher...
after Dispatcher...
PropertyBean [key=objectFromDispatcher, value=java.lang.Object@b7f23d9]
before Dispatcher...
after Dispatcher...
PropertyBean [key=objectFromDispatcher, value=java.lang.Object@61d47554]

接口生成器InterfaceMaker

InterfaceMaker 会动态生成一个接口,该接口包含指定类定义的所有方法

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
44
45
46
47
48
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InterfaceMaker;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class Test {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
InterfaceMaker interfaceMaker = new InterfaceMaker();
// 抽取某个类的方法生成接口方法
interfaceMaker.add(UserServiceImpl.class);
Class<?> userServiceInterface = interfaceMaker.create();
for (Method method : userServiceInterface.getMethods()) {
System.out.println(method.getName());
}
System.out.println();

// 接口代理并设置代理接口方法拦截
Object object = Enhancer.create(Object.class, new Class[]{userServiceInterface}, new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("selectPasswdById")) {
System.out.println("filter selectPasswdById ");
return "aaa";
}
if (method.getName().equals("methodOne")) {
System.out.println("filter methodOne ");
return 123456;
}
if (method.getName().equals("methodTwo")) {
System.out.println("filter methodTwo ");
return "ccc";
}
return "default";
}
});
Method targetMethod1 = object.getClass().getMethod("methodOne", new Class[]{int.class});
int i = (int) targetMethod1.invoke(object, new Object[]{666666});
System.out.println(i);

Method targetMethod = object.getClass().getMethod("methodTwo", new Class[]{String.class});
String str = (String) targetMethod.invoke(object, new Object[]{"abc"});
System.out.println(str);
}
}

输出:

1
2
3
4
5
6
7
8
selectPasswdById
methodOne
methodTwo

filter methodOne
123456
filter methodTwo
ccc

SpringBoot中的AOP使用哪种代理?

Spring 和 SpringBoot 在动态代理的策略上是否相同,具体还要看哪个版本。

SpringBoot 中可以查看 AopAutoConfiguration 类,这个自动化配置主要是在讨论 application.properties 配置文件中的 spring.aop.proxy-target-class 属性的值。

具体起作用的是 @ConditionalOnProperty 注解,关于这个注解中的几个属性,松哥也来稍微说下:

  • prefix:配置文件的前缀。
  • name:配置文件的名字,和 prefix 共同组成配置的 key。
  • havingValue:期待配置的值,如果实际的配置和 having 的值相同,则这个配置类就会生效,否则不生效。
  • matchIfMissing:如果开发者没有在 application.properties 中进行配置,那么这个配置类是否生效。

基于如上介绍,我们很容易看出:

  • 如果开发者设置了 spring.aop.proxy-target-class 为 false,则使用 JDK 代理。
  • 如果开发者设置了 spring.aop.proxy-target-class 为 true,则使用 Cglib 代理。
  • 如果开发者一开始就没配置 spring.aop.proxy-target-class 属性,则使用 Cglib 代理(SpringBoot2.0)。

在 Controller 中 IUserService 的调用处打断点,DEBUG 运行一下,查看代理实例的变量信息(userService),会有大大的标志。

重点回顾

好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要掌握的重点内容。

1.代理模式的原理与实现

在不改变原始类(或叫被代理类)的情况下,通过引入代理类来给原始类附加功能。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。

2.动态代理的原理与实现

静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了维护成本和开发成本。对于静态代理存在的问题,我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

3.代理模式的应用场景

代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在RPC、缓存等应用场景中。

课堂讨论

  1. 除了Java语言之外,在你熟悉的其他语言中,如何实现动态代理呢?
  2. 我们今天讲了两种代理模式的实现方法,一种是基于组合,一种基于继承,请对比一下两者的优缺点。

欢迎留言和我分享你的思考,如果有收获,也欢迎你把这篇文章分享给你的朋友。