23种设计模式--代理模式
结构型设计模式的一种,可分为静态代理模式和动态代理模式
【DP】定义
为其他对象提供一种代理以控制对这个对象的访问
使用场景
- 客户类不想或不能直接引用一个委托对象
- 需要在已有的功能前后加一些功能,如日志打印等,即扩展委托类功能
- 远程过程调用
类图
静态代理模式:
可以看到,静态代理模式很像适配器模式,二者都有复用功能的作用,但是有区别,静态代理会修改一部分原有的功能,而适配器模式一般会全部复用,并且适配器会将复用的类适配一个接口。
动态代理模式(JDK实现,用于代理接口):
可以看到,使用JDK实现的动态代理并不直接引用接口(在运行时根据对象的实际类型去动态执行,可以看下边的代码举例来理解)。
动态代理模式(CGLIB使用字节码实现,可以直接代理类):
举例
静态代理
假设现在有一个查询的服务,但是在调用这个服务时需要将用户信息绑定到线程本地变量(ThreadLocal)中,那么可以按照如下的方法做:
查询服务接口:1
2
3public interface IQueryService {
Object query();
}
查询服务实现类:1
2
3
4
5
6
7public class QueryServiceImpl implements IQueryService{
public Object query() {
//TODO 查查查
return null;
}
}
用于真正调用的代理类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class StaticProxy implements IQueryService {
// 被代理类
private IQueryService srv;
// 通过某种方法获取线程本地变量
private final ThreadLocal<String> threadLocal;
public StaticProxy (IQueryService srv) {
this.srv = srv;
}
public Object query() {
threadLocal.set("userId");
srv.query();
}
}
客户端:1
2
3
4
5
6public class Main {
public static void main() {
IQueryService queryService = new StaticProxy();
Object res = queryService.query();
}
}
这样就可以实现在查询服务中拿到线程本地变量中的userId了,但是有没有发现代理类StaticProxy和查询的接口严格关联了,此时想要在打印的接口同样增加相同的线程变量,又得写一个代理类(把所有的方法都抽成一个接口,也存在增加接口后无法代理的问题),因此动态代理模式应运而生。
动态代理(JDK实现)
同样是对查询接口的执行线程中绑定userId信息,使用动态代理应该按照如下方法做:
用于真正调用的代理类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class DynamicProxy implements InvocationHandler {
// 被代理对象
private Object proxyObj;
// 通过某种方法获取线程本地变量
private final ThreadLocal<String> threadLocal;
public DynamicProxy(Object proxyObj) {
this.proxyObj = proxyObj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
threadLocal.set("userId");
method.invoke(proxyObj, args);
}
}
客户端:1
2
3
4
5
6
7
8
9
10
11
12
13public class Main {
public static void main() {
// 被代理的接口
IQueryService queryService = new QueryServiceImpl();
// 确定要代理的类
InvocationHandler handler = new DynamicProxy(queryService);
// 创建代理实例
IQueryService proxyService = (IQueryService) Proxy.newProxyInstance(IQueryService.class.getClassLoader(),
new Class[] {IQueryService.class}, handler);
// 调用
Object result = proxyService.query();
}
}
动态代理类只需实现InvocationHandler,在调用的时候使用Proxy生成代理实例即可,这样就和具体的接口解耦了。可以看到,Proxy在新建代理实例时,需要ClassLoader和接口类,jvm会在运行时根据类型找到实际的类型。
由于这种方式只能代理接口,但是如果有一些类没有接口,又需要代理,就只能使用下面的CGLIB来实现了。
动态代理(CGLIB实现)
假设1
2
3
4
5
6
7
8
9
10
11```Java
public class QueryService {
// 普通方法
public Object query() {
return null;
}
// final方法
public final Object finalQuery() {
return null;
}
}
用于真正调用的代理类: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
37import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class DynamicProxy implements MethodInterceptor{
// 业务类对象,供代理方法中进行真正的业务方法调用
private Object target;
// 通过某种方法获取线程本地变量
private final ThreadLocal<String> threadLocal;
/**
* 创建代理类
*/
public Object getInstance(Object target) {
this.target = target;
//创建加强器,用来创建动态代理类
Enhancer enhancer = new Enhancer();
// 为生成的代理类指定父类
enhancer.setSuperclass(this.target.getClass());
// 设置回调:对于代理类上所有方法的调用,都会调用CallBack
// 而Callback则需要实现intercept()方法进行拦截
enhancer.setCallback(this);
// 创建动态代理类对象并返回
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// 设置线程本地变量
threadLocal.set("userId");
proxy.invokeSuper(obj, args);
return null;
}
}
客户端类:1
2
3
4
5
6
7
8
9
10
11
12public class CGLIBMain {
public static void main(String[] args) {
QueryService srv = new QueryService();
DynamicProxy cglib = new DynamicProxy();
QueryService proxyService = (QueryService) cglib.getInstance(srv);
// 会被代理
Object result = proxyService.query();
// final方法,不会被代理
Object result2 = proxyService.finalQuery();
System.out.println(result);
}
}
可以看到,动态代理类实现了MethodInterceptor
,是因为使用Enhancer
创建子类时需要使用拦截器。
在QueryService
中的final方法不会被代理,因为生成子类时不能重写final方法。
注意事项
1.动态代理使用Proxy新建代理实例时,第二个参数是接口类的数组,说明该种方式只能代理接口,不能代理具体类。原因是所有通过这种方式生成的类会以Proxy作为父类,如果该类有其他的父类,会不符合Java中单继承的规则。
Proxy类的注释第一段说明了这一点:
Proxy provides static methods for creating dynamic proxy classes and instances,
and it is also the superclass of all dynamic proxy classes created by those methods.
2.关于动态代理的两种实现对比:
2.1. JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
2.2. JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
2.3. JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。
Reference: