Java代理模式
# 1.代理模式
代理模式是常用的java设计模式,他的特征是代理类
与委托类
有同样的接口,代理类
主要负责为委托类
预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类
的对象本身并不真正实现服务
,而是通过调用委托类的对象的相关方法
,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
# 2.静态代理
简单来说代理模式就是将被代理类包装起来然后重新实现相同的方法,并且调用原来方法的同时可以在方法前后添加一个新的处理。而这种包装可以使用继承或者组合来使用。当我们调用的时候需要使用的是代理类的对象来调用而不是原来的被代理对象。
静态代理有两种实现方式:
- 通过继承实现
- 通过组合实现
# 2.1 通过继承实现静态代理
通过继承被代理对象,重写被代理方法,可以对其进行代理。
优点:被代理类无需实现接口
缺点:只能代理这个类,要想代理其他类,要想代理其他类需要写新的代理方法。
cglib动态代理就是采用这种方式对类进行代理。不过类是由cglib
帮我们在内存中动态生成的。
// 定义一个Tank类(被代理类)
public class Tank {
public void move() {
System.out.println("Tank moving cla....");
}
public static void main(String[] args) {
// 创建代理对象调用move方法
new ProxyTank().move();
}
}
// 通过继承Tank类来创建一个代理类ProxyTank
class ProxyTank extends Tank {
// 重写move方法以实现代理功能
@Override
public void move() {
System.out.println("方法执行前...");
// 调用父类(Tank类)的move方法,即被代理的方法
super.move();
System.out.println("方法执行后...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2.2 通过组合实现静态代理
定义一个 Movable
接口被代理类需要和代理类都需要实现该接口。(接口在这里的目的就是起一个规范作用保证被代理类和代理类都实现了move()
方法)。代理类需要将该接口作为属性,实例化时需要传入该接口的对象,这样该代理类就可以实现代理所有实现Movable
的类了。
优点:可以代理所有实现接口的类。
缺点:被代理的类必须实现接口。
JDK动态代理就是采用的这种方式实现的。同样的代理类是由JDK自动帮我们在内存生成的。
// 定义一个Movable接口,起到一个规范作用。
interface Movable {
void move();
}
// 定义一个Tank类(被代理类)
public class Tank implements Movable {
@Override
public void move() {
System.out.println("Tank moving cla....");
}
public static void main(String[] args) {
// 实例化被代理类对象
Tank tank = new Tank();
// 通过代理对象调用move方法
new LogProxy(tank).move();
}
}
// 定义一个LogProxy类,它也实现了Movable接口,用于代理所有实现了Movable接口的类。
class LogProxy implements Movable {
// 被代理类对象的引用
private Movable movable;
// 构造函数,实例化被代理对象
public LogProxy(Movable movable) {
this.movable = movable;
}
// 实现接口中的move方法
@Override
public void move() {
System.out.println("方法执行前....");
// 调用持有对象的move方法,即被代理的方法
movable.move();
System.out.println("方法执行后....");
}
}
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
# 3.动态代理
- 静态代理:在编码阶段就需要明确地定义代理类,每个代理类通常只服务于一种类型的被代理类,且每个需要增强的方法都必须在代理类中显式地编写。如果被代理类的接口有变化(如增加方法),代理类也需要相应地进行修改。这意味着静态代理的灵活性较低,且当需要代理的方法数量增加时,代理类的维护成本也随之增加。
- 动态代理:与静态代理相比,动态代理的创建不依赖于具体的类文件,而是在运行时根据需要动态地在内存中创建代理类。这意味着你不需要为每个被代理类编写一个对应的代理类。动态代理通过实现
InvocationHandler
接口(Java的Proxy
类)或其他机制(如CGLIB),可以在运行时动态地处理对任意方法的调用。这种方式大大提高了代码的复用性和灵活性,因为你可以在一个地方统一处理多个方法的调用逻辑,而无需为每个方法单独编写增强逻辑。
动态代理的主要优势在于其灵活性和减少了重复代码的需要,它允许开发者能够更加集中地管理和修改被代理对象的行为。
实现动态代理有几种方案:
- JDK动态代理
- CGLIB动态代理
- SpringAop
# 3.1 JDK动态代理
通过java提供的
Proxy
类帮我们创建代理对象。
优点:可以生成所有实现接口的代理对象
缺点:JDK动态代理创建的代理对象只能代理接口中定义的方法
。这意味着你的原始类必须实现至少一个接口
,而且你创建的代理对象只能调用这些接口中定义的方法。这是因为JDK代理的内部机制是通过接口来创建代理类的,你在创建代理时必须提供一个接口列表,代理类将基于这些接口生成。如果你的类没有实现任何接口,那么你就不能使用JDK动态代理来代理这个类的对象。
- 定义接口 (
Movable
): 首先定义了一个接口Movable
,它声明了一个move
方法。这个接口是我们希望代理的目标方法所在的接口。 - 实现接口 (
Tank
类): 接着,我们有一个Tank
类,它实现了Movable
接口,并覆盖了move
方法。这个Tank
类的实例是我们希望通过代理增强行为的对象。 - 创建代理 (
LogProxy
类):LogProxy
类实现了InvocationHandler
接口,它在内部持有一个Movable
类型的对象(这里是Tank
的实例)。在其invoke
方法中,我们在调用原始move
方法前后添加了日志输出,以实现方法调用的日志记录功能。 - 生成代理对象: 在
main
方法中,我们通过Proxy.newProxyInstance
方法动态地创建了一个实现了Movable
接口的代理对象。这个代理对象在内部使用了LogProxy
作为其调用处理器,因此当我们通过这个代理对象调用move
方法时,实际上是通过LogProxy
的invoke
方法来实现的,这允许我们在调用实际move
方法前后执行额外的操作(如日志记录)。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 一个接口,定义一个 move 方法。
*/
interface Movable {
void move();
}
/**
* 使用jdk的动态代理。
* Tank是被代理的实际对象
*/
public class Tank implements Movable {
@Override
public void move() {
System.out.println("Tank moving cla....");
}
public static void main(String[] args) {
Tank tank = new Tank();
// 利用 JDK 的 Proxy 类创建代理对象
// 参数一: 类加载器,用于加载代理对象
// 参数二: 要实现的接口数组,代理对象将实现这些接口
// 参数三: 调用处理器,定义代理对象调用方法时的具体行为(传入InvocationHandler的实现类)
Movable o = (Movable) Proxy.newProxyInstance(
Tank.class.getClassLoader(),
new Class[]{Movable.class},
new LogProxy(tank));
o.move(); // 调用代理对象的 move 方法
}
}
/**
* 调用处理器实现。
*/
class LogProxy implements InvocationHandler {
private Movable movable; // 被代理的对象,即原始对象
public LogProxy(Movable movable) {
this.movable = movable;
}
// proxy:代理对象本身,大多数情况下都不使用它
// method:正在被调用的方法的反射对象
// args:调用方法时传递的参数数组。如果接口方法不接受参数,则为null
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在原始方法调用前打印日志
System.out.println("方法:" + method.getName() + "()执行前");
// 调用原始对象的方法
Object result = method.invoke(movable, args); // 此处相当于调用 movable.move()
// 在原始方法调用后打印日志
System.out.println("方法:" + method.getName() + "()执行后");
return result;
}
}
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
总结
在java的java.lang.reflect包下提供了一个
Proxy
类和一个InvocationHandler
接口,通过这个类和这个接口可以生成JDK动态代理类
和动态代理对象
。InvocationHandler
的invoke
方法是一个中心点,它会拦截代理对象上的所有方法调用。任何通过代理对象调用的方法都会被会被代理类重写的
invoke
方法捕获和处理,然后在invoke
方法内调用Method.invoke
,可以执行被代理对象的具体方法。代理类重写的
invoke
方法提供了在被代理对象的方法执行前后加入自定义逻辑的机会,这是实现 AOP 的关键机制。
# 3.2 cglib动态代理
CGLib(Code Generate Library) 与JDK动态代理不同的是,cglib生成代理是被代理对象的子类。因此它拥有继承方法实现静态代理的
优点:不需要被代理对象实现某个接口。
缺点:不能给final
类生成代理,因为final
类无法拥有子类。
使用cglib
生成代理类也很简单,只要指定 父类 和 设置回调(方法拦截器) 即可
首先需要引入cglib
依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
2
3
4
5
- 创建代理类实例: 使用
Enhancer
类来创建代理类的实例。Enhancer
是CGLib提供的一个类工厂,用于在运行时动态创建给定类的子类。 - 指定父类: 通过
enhancer.setSuperclass(Tank.class);
指定代理类的父类。在下面这个例子中,Tank
类是被代理的类,所以代理类将会是Tank
类的子类。 - 设置回调(方法拦截器): 通过
enhancer.setCallback(new TimeMethodInterceptor());
设置方法拦截器。当代理类的方法被调用时,不是直接执行这些方法,而是执行拦截器(TimeMethodInterceptor
)中的intercept
方法。 - 生成代理实例并调用方法: 通过
enhancer.create();
创建代理实例,然后通过这个实例调用方法(如tank.move();
),这时方法调用会被拦截。 - 方法拦截器处理调用: 在
TimeMethodInterceptor
的intercept
方法中,可以在调用实际方法之前和之后添加自定义逻辑(如打印日志、性能监控等)。通过proxy.invokeSuper(obj, args);
调用实际被代理类的方法。这里,proxy
是MethodProxy
的实例,它提供了invokeSuper
方法来调用被代理类的原始方法。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 被代理的类
class Tank {
public void move() {
System.out.println("Tank moving clacla....");
}
}
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer(); // 创建Enhancer对象,用于生成代理类
enhancer.setSuperclass(Tank.class); // 设置代理类的父类,即指定被代理类
enhancer.setCallback(new TimeMethodInterceptor()); // 设置回调(方法拦截器)
Tank proxy = (Tank)enhancer.create(); // 生成代理实例
proxy.move(); // 调用代理实例的方法,会触发MethodInterceptor的intercept方法
}
}
// 实现MethodInterceptor接口,自定义方法拦截逻辑
class TimeMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// obj:代理类实例
// method:被代理的方法
// args:方法参数
// proxy:用于调用super(父类)版本的方法
System.out.println("生成的类名: " + obj.getClass().getName());
System.out.println("生成的类的父类: " + obj.getClass().getSuperclass().getName());
System.out.println("方法执行前,被代理的方法: " + method.getName());
// 调用父类的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("方法执行后,被代理的方法: " + method.getName());
return result; // 返回方法执行结果
}
}
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
# SpringAOP使用以及原理
关于SpringAOP的使用以及原理可以查看我的这篇文章 SpringAOP使用以及原理 (opens new window)
# 4. JDK动态代理与CGLIB的区别
Spring AOP支持
JDK Proxy
和CGLIB
方式实现动态代理,这两种方式都是在程序运行期,动态的将切面织入字节码形成代理对象
- JDK 动态代理:当被代理的目标对象实现了至少一个接口时,Spring AOP 默认使用 JDK 动态代理。在这种模式下,Spring AOP 会为目标对象实现的接口创建一个代理对象,这个代理对象会拦截所有接口方法的调用。这种方式不需要额外的库支持,因为它使用了 Java 核心库中的
java.lang.reflect.Proxy
类。 - CGLIB 代理:当被代理的目标对象没有实现任何接口时,Spring AOP 会回退到使用 CGLIB 来创建代理对象。CGLIB(Code Generation Library)是一个第三方代码生成库,它通过在运行时动态生成被代理对象的子类来实现代理。与 JDK 动态代理相比,CGLIB 能够代理没有实现接口的类。
JDK动态代理与CGLIB的区别?
- JDK动态代理要求被代理的类必须实现接口,因此它只能代理接口中定义的方法,当你通过代理对象调用接口中的方法时,这个调用会被转发到
InvocationHandler
的的invoke
方法,然后通常会通过反射来调用被代理对象的原始方法. CGLIB动态代理不要求被代理的类实现接口,而是通过继承被代理类。 - JDK动态代理在生成代理对象的速度上更快,因为JDK动态代理直接使用了Java自带的API,而CGLIB则需要通过字节码技术动态生成新的类。
- CGLIB 无法代理
final
类和final
方法,因为CGLIB通过继承目标类来创建子类,而Java语言规定final
类不能被继承,final
方法不能被重写。而JDK动态代理可以代理任意类的方法。
综上所述,JDK Proxy 和 CGLIB 都有自己的优缺点和适用场景。如果目标对象实现了接口并且需要代理的方法较少,则建议使用 JDK Proxy;如果目标对象没有实现接口或需要代理的方法较多,则建议使用 CGLIB。