代理模式 Proxy
代理模式(Proxy):为其他对象提供一种代理,以控制对这个对象的访问。
The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it.
啥意思呢?代理的本意是指一个人或组织代表另一个人或组织执行某些任务、权利或职责。比如销售代理可以帮着销售供应商的产品。
先看一下 UML 类图:
代理模式角色如下:
⨳ Subject
(抽象主题):定义了 RealSubject
和 Proxy
的共同接口,真实主题和代理都实现了这个接口。
⨳ Real Subject
(真实主题):定义了真正的对象,Proxy
需要代理的类。
⨳ Proxy
(代理):代理对象,维护一个指向 RealSubject
的引用,控制对 RealSubject
的访问,并在必要时执行一些额外的操作。
类比销售代理(Proxy)和供应商(Real Subject):销售代理和供应商的目的都是为了销售产品(有相同的行为),而且本地客户一般不和供应商打交道,有啥事都是找本地销售代理,本地销售代理被授权处理相关事宜(控制对这个对象的访问)。
总而言之,代理类隔离了客户端与真实主题。
为啥要隔离呢?这样做有什么好处呢?下面看一下代理模式基本实现。
基本实现
抽象主题 Subject
Subject
类,定义了 RealSubject
和 Proxy
的共用接口,这样就在任何使用 RealSubject
的地方都可以使用 Proxy
。
java
public interface Subject {
void request();
}
真实主题 RealSubject
RealSubject
类,定义 Proxy
所代表的真实实体。
java
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("真实的请求");
}
}
代理 Proxy
Proxy
类,保存一个引用使得代理可以访问实体,并提供一个与 Subject
的接口相同的接口,这样代理就可以用来替代真实主题。
java
public class Proxy implements Subject{
private Subject realSubject;
@Override
public void request() {
if (realSubject==null)
realSubject=new RealSubject();
preRequest();
realSubject.request();
postRequest();
}
private void preRequest(){
System.out.println("访问真实主题之前的预处理");
}
private void postRequest(){
System.out.println("访问真实主题之后的后续处理");
}
}
客户端 Client
客户端可以像使用真实主题一样使用代理。
java
Subject subject = new Proxy();
subject.request();
输出结果如下:
java
访问真实主题之前的预处理
真实的请求
访问真实主题之后的后续处理
上述代码,演示了代理者可以为目标主体的行为添加附加方法,进行行为增强。
更重要的是,如果附加方法的逻辑一样,多个真实主题都想要这种附加,那同一个代理类就可以代理这些主题了,不需要引入其他代理类:
更进一步,如果不同主题的增强逻辑都一样,那应该怎么做呢?
代理者只有继承或实现目标主题 ,才能和目标主题 保持一样的行为,才能让客户端使用代理类 像使用目标主题一样,那现在不同主题的增强逻辑都一样,那有几个主题就搞几个代理类吗?哪怕它们的增强逻辑都一样。
这就需要动态代理技术了。
动态代理 Dynamic Proxy
动态代理(Dynamic Proxy)是指在程序运行时动态地创建代理对象的一种机制,而不需要在编译时就确定代理类的类型。JDK 原生有一种动态代理,Cglib 也有一种动态代理,下面分别看一下:
JDK 动态代理
JDK 动态代理是基于接口的动态代理机制,要求被代理的类必须实现一个或多个接口,代理类是通过java.lang.reflect.Proxy
类的 newProxyInstance
方法动态生成的。
java
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h){...}
该方法中有三个参数:
⨳ ClassLoader loader
:被代理的对象所属类的类加载器;
⨳ Class[] interfaces
:被代理的对象所属类实现的接口;
⨳ InvocationHandler h
:代表增强的逻辑,当代理对象的方法被调用时,其invoke
方法就会被调用。
JDK 动态代理 将增强的逻辑封装到 InvocationHandler
中:
java
public class MyHandler implements InvocationHandler {
// 目标主题
private Subject subject;
public ProxyHandler(Subject subject){
this.subject = subject;
}
/**
*
* @param proxy 代理对象的引用
* @param method 代理对象执行的方法
* @param args 代理对象执行方法的参数列表
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("访问真实主题之前的预处理");
// 由于代理对象与目标主题实现了相同的接口,所以也可以反射调用目标主题的 method
Object ret = method.invoke(subject, args);
System.out.println("访问真实主题之后的后续处理");
return ret;
}
}
有了这个增强的地方,就可以创建任意目标主题的代理对象了:
java
// 目标主题
Subject subject = new RealSubject();
Class<? extends Subject> clazz = subject.getClass();
// 使用 JDK 动态代理,生成代理对象
Subject proxy = (Subject) java.lang.reflect.Proxy.newProxyInstance(
clazz.getClassLoader(), // 被代理的对象所属类的类加载器
clazz.getInterfaces(), // 被代理的对象所属类实现的接口
new MyHandler(subject)); // 增强的逻辑
// 客户端使用代理类像使用目标主题一样
proxy.request();
Cglib 动态代理
Cglib(Code Generation Library)是一个强大的字节码生成库,CGLIB动态代理不要求被代理的类实现接口,它通过继承的方式创建代理类。
和 JDK 动态代理一样,Cglib 也有一个封装增强逻辑的地方:MethodInterceptor
。当代理对象的方法被调用时,会被转发到MethodInterceptor
的intercept
方法中。
java
public class MyInterceptor implements MethodInterceptor {
// 目标主题
private Object subject;
public MyInterceptor(Object subject) {
this.subject = subject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("访问真实主题之前的预处理");
// 由于代理对象与目标主题是继承关系,所以也可以反射调用目标主题的 method
Object ret = method.invoke(subject, objects);
System.out.println("访问真实主题之后的后续处理");
return ret;
}
}
CGLIB动态代理的核心类是net.sf.cglib.proxy.Enhancer
,其 create 方法可以创建代理类:
java
public static Object create(Class type, Callback callback){//...}
同 JDK 动态代理相似,Cglib 动态代理的内容相对较少,它只需要传入两个东西:
⨳ Class type
:被代理的对象所属类的类型
⨳ Callback callback
:增强的代码实现,由于一般情况下我们都是对类中的方法增强,所以在传入 Callback
时通常选择这个接口的子接口 MethodInterceptor
。
java
// 目标主题
Subject subject = new RealSubject();
// 使用 Cglib 动态代理,生成代理对象
Subject proxy = (Subject) net.sf.cglib.proxy.Enhancer.create(
subject.getClass(), // 被代理的对象所属类的类型
new MyInterceptor(subject) // 增强的代码实现
);
// 客户端使用代理类像使用目标主题一样
proxy.request();
使用 Cglib 注意点如下:
⨳ 被代理的类不能是 final
的:Cglib 动态代理会修改字节码创建子类,final 类型的 Class 无法继承;
⨳ 被代理的类必须有默认无参构造方法:Cglib 底层反射创建对象时拿不到构造方法参数;
源码鉴赏
Spring 之 AOP
Spring AOP(面向切面编程)是 Spring 框架的一个关键模块,它通过动态代理技术为应用程序提供了横切关注点的分离能力,允许在方法执行前、后或异常抛出时,执行特定的代码。
Spring AOP 将增强逻辑写到切面(Aspect)中,AOP代理工厂(简单工厂模式)会根据情况选择JDK 还是 Cglib 动态代理。
默认使用Cglib,如果要代理的本身就是接口,或者已经是被 JDK 动态代理了的代理对象,则使用 JDK动态代理。
Mybatis 之 Mapper
MyBatis 使用了JDK动态代理技术来创建 Mapper 接口的代理对象,从而实现了对 SQL 语句的自动映射和执行。
MapperProxy
代码节选如下:
java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} // catch ......
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
就是目标主题没有实现类,也可以使用 JDK动态代理技术生成接口的代理类!!!
Dubbo 之 ProxyFactory
Dubbo 是一个分布式服务框架,它支持使用动态代理来实现远程服务调用。在 Dubbo 中,动态代理主要用于代理服务接口,以便客户端可以透明地调用远程服务,而无需关心底层的网络通信细节。
ProxyFactory
是Dubbo 中的代理工厂接口,用于创建服务接口的代理对象。
当客户端调用服务接口的方法时,Dubbo 会动态生成代理对象,并将调用请求转发给代理对象。代理对象负责将调用请求封装成远程调用请求,并通过底层的网络通信框架(如 Netty)发送到远程服务端,然后接收服务端的响应,并将响应返回给客户端。
总的来说,Dubbo 中的动态代理主要用于实现服务接口的远程调用,它隐藏了底层的网络通信细节,使得客户端可以像调用本地服务一样调用远程服务。
总结
代理模式适用场景如下:
⨳ 保护目标对象:代理可以控制对对象的访问权限,限制对对象的直接访问,增强安全性。
⨳ 增强目标对象:代理可以通过在访问对象前后进行一些操作,如缓存、延迟加载等,从而提升系统的性能。
代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。