要说动态代理,必须先聊聊静态代理。

静态代理

假设现在项目经理有一个需求:在项目现有所有类的方法前后打印日志。

你如何在不修改已有代码的前提下,完成这个需求?

我首先想到的是静态代理。具体做法是:

1.为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口(假设都有)

img

2.在创建代理对象时,通过构造器塞入一个目标对象,然后在代理对象的方法内部调用目标对象同名方法,并在调用前后打印日志。也就是说,代理对象 = 增强代码 + 目标对象(原对象)。有了代理对象后,就不用原对象了

img

静态代理的缺陷

程序员要手动为每一个目标类编写对应的代理类。如果当前系统已经有成百上千个类,工作量太大了。所以,现在我们的努力方向是:如何少写或者不写代理类,却能完成代理功能?

复习对象的创建

很多初学Java的朋友眼中创建对象的过程

img

实际上可以换个角度,也说得通

img

所谓的Class对象,是Class类的实例,而Class类是描述所有类的,比如Person类,Student类

img

可以看出,要创建一个实例,最关键的就是得到对应的Class对象。只不过对于初学者来说,new这个关键字配合构造方法,实在太好用了,底层隐藏了太多细节,一句 Person p = new Person();直接把对象返回给你了。我自己刚开始学Java时,也没意识到Class对象的存在。

分析到这里,貌似有了思路:

能否不写代理类,而直接得到代理Class对象,然后根据它创建代理实例(反射)。

Class对象包含了一个类的所有信息,比如构造器、方法、字段等。如果我们不写代理类,这些信息从哪获取呢?苦思冥想,突然灵光一现:代理类和目标类理应实现同一组接口。之所以实现相同接口,是为了尽可能保证代理对象的内部结构和目标对象一致,这样我们对代理对象的操作最终都可以转移到目标对象身上,代理对象只需专注于增强代码的编写。还是上面这幅图:

img

所以,可以这样说:接口拥有代理对象和目标对象共同的类信息。所以,我们可以从接口那得到理应由代理类提供的信息。但是别忘了,接口是无法创建对象的,怎么办?

动态代理

JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类,这两个类相互配合,入口是Proxy,所以我们先聊它。

Proxy有个静态方法:getProxyClass(ClassLoader, interfaces),只要你给它传入类加载器和一组接口,它就给你返回代理Class对象。

用通俗的话说,getProxyClass()这个方法,会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象的。打个比方,一个大内太监(接口Class),空有一身武艺(类信息),但是无法传给后人。现在江湖上有个妙手神医(Proxy类),发明了克隆大法(getProxyClass),不仅能克隆太监的一身武艺,还保留了小DD(构造器)…(这到底是道德の沦丧,还是人性的扭曲,欢迎走进动态代理)

所以,一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理Class,通过代理Class即可创建代理对象。

img

大体思路

img

静态代理

img

动态代理

所以,按我理解,Proxy.getProxyClass()这个方法的本质就是:以Class造Class。

有了Class对象,就很好办了,具体看代码:

img

完美。

根据代理Class的构造器创建对象时,需要传入InvocationHandler。每次调用代理对象的方法,最终都会调用InvocationHandler的invoke()方法:

img

怎么做到的呢?

上面不是说了吗,根据代理Class的构造器创建对象时,需要传入InvocationHandler。通过构造器传入一个引用,那么必然有个成员变量去接收。没错,代理对象的内部确实有个成员变量invocationHandler,而且代理对象的每个方法内部都会调用handler.invoke()!InvocationHandler对象成了代理对象和目标对象的桥梁,不像静态代理这么直接。

img

大家仔细看上图右侧的动态代理,我在invocationHandler的invoke()方法中并没有写目标对象。因为一开始invocationHandler的invoke()里确实没有目标对象,需要我们手动new。

img

但这种写法不够优雅,属于硬编码。我这次代理A对象,下次想代理B对象还要进来改invoke()方法,太差劲了。改进一下,让调用者把目标对象作为参数传进来:

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
public class ProxyTest {
public static void main(String[] args) throws Throwable {
CalculatorImpl target = new CalculatorImpl();
//传入目标对象
//目的:1.根据它实现的接口生成代理对象 2.代理对象调用目标对象方法
Calculator calculatorProxy = (Calculator) getProxy(target);
calculatorProxy.add(1, 2);
calculatorProxy.subtract(2, 1);
}

private static Object getProxy(final Object target) throws Exception {
//参数1:随便找个类加载器给它, 参数2:目标对象实现的接口,让代理对象实现相同接口
Class proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
Object proxy = constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行...");
Object result = method.invoke(target, args);
System.out.println(result);
System.out.println(method.getName() + "方法执行结束...");
return result;
}
});
return proxy;
}
}

这样就非常灵活,非常优雅了。无论现在系统有多少类,只要你把实例传进来,getProxy()都能给你返回对应的代理对象。就这样,我们完美地跳过了代理类,直接创建了代理对象!

不过实际编程中,一般不用getProxyClass(),而是使用Proxy类的另一个静态方法:Proxy.newProxyInstance(),直接返回代理实例,连中间得到代理Class对象的过程都帮你隐藏:

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 ProxyTest {
public static void main(String[] args) throws Throwable {
CalculatorImpl target = new CalculatorImpl();
Calculator calculatorProxy = (Calculator) getProxy(target);
calculatorProxy.add(1, 2);
calculatorProxy.subtract(2, 1);
}

private static Object getProxy(final Object target) throws Exception {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),/*类加载器*/
target.getClass().getInterfaces(),/*让代理对象和目标对象实现相同接口*/
new InvocationHandler(){/*代理对象的方法最终都会被JVM导向它的invoke方法*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行...");
Object result = method.invoke(target, args);
System.out.println(result);
System.out.println(method.getName() + "方法执行结束...");
return result;
}
}
);
return proxy;
}
}

现在,我想题主应该能看懂动态代理了。

img

CGLIB动态代理

上述JDK的动态代理只能代理实现了接口的类。CGLIB可以解决这个问题。

开源项目,需要自己添加依赖

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

流程是:

  • 自定义一个类A来实现MethodInterceptor接口,并在这个类中重写intercept函数,增加你想要增强的内容
  • 自定义一个类并实现一个静态函数,函数的输入参数是Class对象(即被代理的类)B,在函数中创建一个Enhancer类对象C,设置这个对象的
    • 类加载器:B的类加载器
    • 被代理类:B
    • 方法拦截器:A

例子:

这是一个被代理的类,我们需要给send方法增加功能(增强send方法)

1
2
3
4
5
6
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

自定义方法拦截器

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
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
//自定义一个类A来实现MethodInterceptor接口
public class DebugMethodInterceptor implements MethodInterceptor {

/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
//重写intercept函数
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());

Object object = methodProxy.invokeSuper(o, args);

//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}

}

获取代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import net.sf.cglib.proxy.Enhancer;
//自定义一个类并实现一个静态函数
public class CglibProxyFactory {

public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}

实际使用

1
2
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

运行结果

1
2
3
before method send
send message:java
after method send

注意: CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

JDK代理一般效率更高

小结

我想了个很骚的比喻,希望能解释清楚:

接口Class对象是大内太监,里面的方法和字段比做他的一身武艺,但是他没有小DD(构造器),所以不能new实例。一身武艺后继无人。

那怎么办呢?

正常途径(implements):

写一个类,实现该接口。这个就相当于大街上拉了一个人,认他做干爹。一身武艺传给他,只是比他干爹多了小DD,可以new实例。

非正常途径(动态代理):

通过妙手圣医Proxy的克隆大法(Proxy.getProxyClass()),克隆一个Class,但是有小DD。所以这个克隆人Class可以创建实例,也就是代理对象。

代理Class其实就是附有构造器的接口Class,一样的类结构信息,却能创建实例。关于类加载器

初学者可能对诸如“字节码文件”、Class对象比较陌生。所以这里花一点点篇幅介绍一下类加载器的部分原理。如果我们要定义类加载器,需要继承ClassLoader类,并覆盖findClass()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
/*自己另外写一个getClassData()
通过IO流从指定位置读取xxx.class文件得到字节数组*/
byte[] datas = getClassData(name);
if(datas == null) {
throw new ClassNotFoundException("类没有找到:" + name);
}
//调用类加载器本身的defineClass()方法,由字节码得到Class对象
return this.defineClass(name, datas, 0, datas.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类找不到:" + name);
}
}

所以,这就是类加载之所以能把xxx.class文件加载进内存,并创建对应Class对象的深层原因。