代理模式
Java 中的代理是一种设计模式,它允许在不改变原始对象的情况下,通过引入一个代理对象来控制对原始对象的访问。代理对象充当了客户端与原始对象之间的中介,可以在调用原始对象的方法前后执行一些额外的操作,如权限检查、日志记录等。
Java 的代理主要有静态代理和动态代理两种实现方式。静态代理需要手动编写代理类,而动态代理则使用 Java 提供的反射机制动态生成代理对象。
代理的作用包括:
- 控制对原始对象的访问:代理对象可以限制客户端对原始对象的直接访问,从而增加安全性。
- 实现远程调用:代理对象可以将方法调用转发到另一台计算机上的对象,从而实现远程调用。
- 实现懒加载:代理对象可以在需要时才创建原始对象,从而实现懒加载,提高系统性能。
- 实现事务管理:代理对象可以在调用原始对象的方法前后进行事务操作,从而实现事务管理。
# 静态代理
在静态代理中,代理类和原始类实现了相同的接口,客户端通过代理类访问原始类的方法。
使用 Java 静态代理有以下几个步骤:
- 创建一个接口,定义原始对象和代理对象都要实现的方法;
- 创建一个原始对象的实现类,并实现接口中的方法;
- 创建一个代理类,实现接口并持有原始对象的引用;
- 在代理类的方法中,调用原始对象的方法,并在调用前后执行需要的额外操作;
- 在客户端代码中,创建一个代理对象,调用代理对象的方法即可。
// 接口
public interface Subject {
void request();
}
// 原始对象
public class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
public void request() {
System.out.println("Proxy: Logging before request.");
realSubject.request();
System.out.println("Proxy: Logging after request.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.request();
}
}
// 结果
Proxy: Logging before request.
RealSubject: Handling request.
Proxy: Logging after request.
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
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
# 动态代理
Java 动态代理是一种使用反射机制在运行时动态地生成代理类的技术,可以用来代理一个或多个接口的实现类,并在代理对象调用方法时进行额外的操作。
Java 动态代理有两种实现方式:基于接口的动态代理(JDK)和基于类的动态代理(CGLIB)。
- 基于接口的动态代理(JDK):是指代理对象实现(或继承)了被代理对象实现的接口,代理对象通过接口中的方法来实现对被代理对象的代理。这种方式更灵活、安全和低耦合,因为代理对象和被代理对象之间只需要共同实现一个接口即可。
- 而基于类的动态代理(CGLIB):是指代理对象和被代理对象没有共同实现的接口,但代理对象是被代理对象的子类,因此可以覆盖被代理对象的方法并在其中实现对被代理对象的代理。这种方式相对于基于接口的动态代理来说更灵活,因为代理对象不受被代理对象的接口限制,可以代理任意类的任意方法,但是它也带来了一些问题,例如代理对象无法同时代理多个类,以及可能会破坏被代理对象的封装性和安全性。
Java 动态代理的作用包括但不限于:
- 实现 AOP(面向切面编程)的功能,如日志记录、性能统计等;
- 隐藏某些类或方法的具体实现细节;
- 实现 RPC(远程过程调用)等功能。
# JDK 代理
使用 Java 动态代理需要遵循以下步骤:
- 定义一个接口;
- 编写一个实现该接口的类;
- 创建一个 InvocationHandler 对象,并实现 invoke () 方法,在该方法中对被代理对象的方法进行增强;
- 通过 Proxy.newProxyInstance () 方法创建代理对象。该方法需要传入三个参数:ClassLoader 对象、代理对象要实现的接口数组、InvocationHandler 对象;
- 调用代理对象的方法,实际上会调用 InvocationHandler 的 invoke () 方法。
// 接口
public interface Subject {
void request();
}
// 实现类
public class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Object realObject;
public MyInvocationHandler(Object realObject) {
this.realObject = realObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("MyInvocationHandler: Logging before request.");
Object result = method.invoke(realObject, args);
System.out.println("MyInvocationHandler: Logging after request.");
return result;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
MyInvocationHandler handler = new MyInvocationHandler(realSubject);
// 创建代理对象
Subject proxy = (Subject)Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler);
// 调用代理对象的方法
proxy.request();
}
}
// 结果
MyInvocationHandler: Logging before request.
RealSubject: Handling request.
MyInvocationHandler: Logging after request.
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
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
# CGLIB 代理
CGLIB 是一个强大的 Java 字节码生成库,能够在运行时动态地生成代理对象。与基于接口的动态代理不同,CGLIB 动态代理可以代理没有实现接口的类。
使用 CGLIB 动态代理需要依赖 cglib 库,可以通过 Maven 等构建工具引入。下面是一个简单的使用 CGLIB 动态代理的示例:
- 定义一个需要代理的类 Person,并定义其相关方法。
public class Person {
public void eat() {
System.out.println("Person: Eating");
}
public void sleep() {
System.out.println("Person: Sleeping");
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 创建一个 MethodInterceptor 对象,在 intercept () 方法中实现对被代理对象方法的增强。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After " + method.getName());
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 使用 CGLIB 创建代理对象,并调用代理对象的方法。
import net.sf.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new MyMethodInterceptor());
Person person = (Person)enhancer.create();
person.eat();
person.sleep();
}
}
// 结果
Before eat
Person: Eating
After eat
Before sleep
Person: Sleeping
After sleep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 区别
基于 CGLIB 和 JDK 动态代理各有优缺点,取决于具体的应用场景和需求。具体优缺点如下:
优点:
CGLIB | JDK |
---|---|
可以代理没有实现接口的类 | 不需要依赖额外的库 |
生成的代理类比较小,执行效率高 | 采用的是接口代理,更加灵活和安全 |
支持方法级别的拦截,可以对类中的任意方法进行拦截 | 在 Java 1.3 及以上版本中可以直接使用,易于部署和维护 |
缺点:
CGLIB | JDK |
---|---|
对于 final 方法或 private 方法无法进行代理 | 只能代理实现了接口的类 |
由于使用继承实现代理,可能会破坏被代理对象的封装性和安全性 | 生成的代理类相对较大,执行效率不如基于 Java 字节码生成库的代理 |
生成的代理类需要依赖额外的库 | 只能对接口中的方法进行拦截 |
上次更新: 4/1/2025, 5:03:02 PM