代理模式
代理模式
参考文章:Java 代理模式详解 | JavaGuide(Java面试+学习指南)
定义
代理模式:为目标对象
提供一个代理对象
,由代理对象
控制对目标对象
的访问,在不修改目标对象
的前提下,提供额外的功能操作,扩展目标对象
的功能。
静态代理
静态代理中,我们对目标对象
的每个方法的增强都需要手动完成。
从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
步骤
静态代理实现步骤:
- 定义接口及其实现类(
目标对象
); - 创建代理类并实现该接口(
代理对象
); - 将
目标对象
注入到代理对象
中,在代理对象
中的方法中调用目标对象
中对应的方法; - 使用
代理对象
中的方法;
示例
定义短信发送接口
1
2
3public interface SmsService {
String sendMsg(String message);
}实现短信发送接口
1
2
3
4
5
6
7public class SmsServiceImpl implements SmsService {
public String sendMsg(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
19public class SmsProxy implements SmsService {
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
public String sendMsg(String message) {
// 调用方法之前
System.out.println("before method sendMsg()");
// 执行目标对象所对应的方法
smsService.sendMsg(message);
// 调用方法之后
System.out.println("after method sendMsg()");
return null;
}
}实际使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Main {
public static void main(String[] args) {
// 创建目标对象
SmsService smsService = new SmsServiceImpl();
// 创建代理对象
SmsProxy smsProxy = new SmsProxy(smsService);
// 使用代理对象的方法
smsProxy.sendMsg("pigwantacat");
}
}
// 控制台打印:
// before method sendMsg()
// send message --> pigwantacat
// after method sendMsg()
动态代理
动态代理中,我们不需要针对每个目标类都单独创建一个代理类,也不需要代理类实现对应的接口。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
JDK动态代理
在 Java 动态代理机制中 Proxy
类和InvocationHandler
接口是核心。
Proxy
类中使用频率最高的方法是:newProxyInstance()
,这个方法主要用来生成一个代理对象
。
1 | public static Object newProxyInstance(ClassLoader loader, |
newProxyInstance
方法中的参数解释:
loader
:类加载器,用于加载代理对象
。interfaces
: 被代理类实现的接口;h
: 实现了InvocationHandler
接口的对象;
要实现动态代理的话,必须实现InvocationHandler
来自定义处理逻辑。 当代理对象
调用方法时,该方法的调用就会被转发到实现InvocationHandler
接口类的 invoke
方法中。
1 | public interface InvocationHandler { |
invoke()
方法中的参数解释:
proxy
:动态生成的代理类method
:目标对象
所对应的方法args
: 当前 method 方法的参数
也就是说:你通过Proxy
类的 newProxyInstance()
创建的代理对象
在调用方法的时候,实际会调用到实现InvocationHandler
接口的类的 invoke()
方法。
步骤
- 定义一个接口及其实现类(
目标对象
); - 自定义
InvocationHandler
并重写invoke
方法,在invoke
方法中调用目标对象
的方法; - 使用
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象
; - 使用
代理对象
中的方法;
示例
定义并实现短信发送接口
1
2
3public interface SmsService {
String sendMsg(String message);
}实现短信发送接口
1
2
3
4
5
6
7public class SmsServiceImpl implements SmsService {
public String sendMsg(String message) {
System.out.println("send message --> " + message);
return message;
}
}实现JDK动态代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class MyInvocationHandler implements InvocationHandler {
/**
* 目标对象
*/
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
// 调用方法之前
System.out.println("before method " + method.getName());
// 执行目标对象所对应的方法
Object result = method.invoke(target, args);
// 调用方法之后
System.out.println("after method " + method.getName());
return result;
}
}获取代理对象的工厂类
1
2
3
4
5
6
7
8
9
10
11
12public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
// 获取目标对象的类加载器
target.getClass().getClassLoader(),
// 获取目标对象实现的接口集合
target.getClass().getInterfaces(),
// 实现InvocationHandler接口的对象
new MyInvocationHandler(target)
);
}
}实际使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Main {
public static void main(String[] args) {
// 创建目标对象
SmsService smsService = new SmsServiceImpl();
// 创建代理对象
SmsService proxySmsService = (SmsService)JdkProxyFactory.getProxy(smsService);
// 使用代理对象的方法
proxySmsService.sendMsg("pigwantacat");
}
}
// 控制台打印:
// before method sendMsg()
// send message --> pigwantacat
// after method sendMsg()
CGLIB动态代理
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIBopen in new window(Code Generation Library)是一个基于ASMopen in new window的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIBopen in new window, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
你需要自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
1 | public interface MethodInterceptor |
- obj : 被代理的对象(需要增强的对象)
- method : 被拦截的方法(需要增强的方法)
- args : 方法入参
- proxy : 用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
步骤
- 定义一个类;
- 自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; - 通过
Enhancer
类的create()
创建代理类;
示例
不同于 JDK 动态代理不需要额外的依赖。CGLIBopen in new window(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
引入CGLIB依赖
1
2
3
4
5<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>实现短信发送接口
1
2
3
4
5
6public class AliSmsService {
public String sendMsg(String message) {
System.out.println("send message --> " + message);
return message;
}
}自定义
MethodInterceptor
(方法拦截器)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
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
14public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}实际使用
1
2
3
4
5
6
7
8
9
10
11public class Main {
public static void main(String[] args) {
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.sendMsg("pigwantacat");
}
}
// 控制台打印:
// before method sendMsg()
// send message --> pigwantacat
// after method sendMsg()
JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
静态代理和动态代理的对比
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。