组成
代理模式为其他对象提供一种代理,以控制对这个对象的访问。
对一个对象进行访问控制,而不是给这个对象添加额外的功能,比如访问目标对象的时候,判断是否有权限、记录目标对象方法的执行时间等,但不会影响目标对象本身的功能。
如果要给目标对象添加和业务逻辑无关的功能(比如权限判断、记录日志、耗时等),又不希望修改目标对象的类,那么使用代理模式可以很容易添加和移除这些功能。
代理模式是一个中间人,而不是目标对象的外壳。
代理模式的组成如下:

图1. 代理模式的组成
Subject:定义的接口
RealSubject:接口的具体实现类,需要被代理的真实类
Proxy:代理类,提供与 Subject 相同的方法,并持有对 RealSubject 的一个引用,这样可以代替 RealSubject,对外提供相同的方法。
客户创建 Proxy 对象,同时在 Proxy 对象中创了 RealSubject 对象;客户端调用 Proxy 的方法,Proxy 把方法代理到 RealSubject 的同名方法:

应用场景
下面是一些可以使用 Proxy 模式的常见情况:
1、远程代理(Remote Proxy):为一个在不同地址空间的对象提供代理,从而让 client 对要调用的目标对象实现透明调用。
2、虚代理(Virtual Proxy):根据需要创建开销很大的对象,延迟对开销很大的对象的访问,实现懒加载
3、保护代理(Protection Proxy):控制对原始对象的访问,用于对象有不同访问权限的时候
4、智能指引(Smart Reference):它在访问对象时执行一些附加操作,比如对实际对象的引用计数、访问一个对象前检查是否已经锁定等。
还有一种代理模式的实现就是 copy-on-write 方式,访问目标对象时,如果要修改目标对象 Proxy 则拷贝出一个新的对象,否则直接读取原对象。
示例代码
最常见的代理模式应用,是JDK中的静态代理和动态代理。
静态代理
静态代理完全按照图1所示的结构,分别创建 Subject、RealSubject、Proxy 类,使用Proxy 实现 Subject 并持有 RealSubject,示例如下。
1、创建 Subject 接口和 RealSubject 类
java
// 被代理的接口
public interface UserService {
/**
* 根据ID查询用户对象
* @param id 用户ID
* @return User
*/
User getUserById(Long id);
}
// 被代理的实现类
@Slf4j
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Long id) {
User user = new User().setId(id);
log.info("user ===== {}", user);
return user;
}
}
2、创建 Proxy 类
java
@Slf4j
public class UserProxy implements UserService {
private UserService userService;
public UserProxy(UserService userService) {
this.userService = userService;
}
@Override
public User getUserById(Long id) {
log.info("before getUserById === class: {}", this.getClass().getName());
User user = userService.getUserById(id);
log.info("after getUserById === class: {}", this.getClass().getName());
return user;
}
}
3、创建代理对象,并调用 ReaslSubject 中的方法
java
public static void main(String[] args) {
UserService proxy = new UserProxy(new UserServiceImpl());
proxy.getUserById(1000L);
}
// ========== 打印结果 =============== //
===== UserProxy : getUserById : begin
===== UserServiceImpl : getUserById : id:1000 =====
===== UserProxy : getUserById : end
proxy 在执行同名方法时,会先执行自身的逻辑,然后再执行 RealSubject 的逻辑,从而实现了对真实对象的方法控制。
静态代理的缺点很明显:要求代理类实现与被代理类相同的接口,一个代理类只能服务于一个类,如果要服务于多个类需要创建多个代理类。因此,JDK中更常用的是动态代理。
JDK 动态代理
JDK提供了动态代理,与静态代理相比最大的不同在于:它可以动态地创建代理,并动态地处理对代理方法的调用。
JDK怎么动态地创建Proxy?
Java对象是由JVM根据编译完成的 .class 文件创建的,因此要想动态创建 Proxy 对象,并且持有 RealSubject 的引用,需要完成以下几个步骤:
1、创建 Proxy 的 .class 文件,并加载到 JVM 中,创建 Class 对象
2、使用 Proxy 的 Class 对象,调用构造器创建对象实例
3、Proxy 对象直接或间接持有 RealSubject 对象的引用,在调用同名方法时能够真正调用 RealSubject 的方法
JDK动态代理的使用示例如下。
1、创建 Subject 接口和 RealSubject 类,这部分与静态代理相同
java
// 被代理的接口
public interface UserService {
/**
* 根据ID查询用户对象
* @param id 用户ID
* @return User
*/
User getUserById(Long id);
}
// 被代理的实现类
@Slf4j
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Long id) {
User user = new User().setId(id);
log.info("user ===== {}", user);
return user;
}
}
2、创建 InvocationHandler 接口的实现类
java
@Slf4j
public class UserInvokeHandler implements InvocationHandler {
// 被代理的 RealSubject 对象
private Object target;
public UserInvokeHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("=== UserInvokeHandler invoke begin ===");
Object result = method.invoke(target, args);
log.info("=== UserInvokeHandler invoke end ===");
return result;
}
}
3、使用 Proxy.newProxyInstance() **创建代理类对象
java
public static void main(String[] args) {
UserInvokeHandler handler = new UserInvokeHandler(new UserServiceImpl());
UserService proxy = (UserService) Proxy.newProxyInstance(
handler.getClass().getClassLoader(), new Class[]{UserService.class}, handler);
proxy.getUserById(200L);
}
// ========== 打印结果 =============== //
=== UserInvokeHandler invoke begin ===
===== UserServiceImpl : getUserById : id:200 =====
=== UserInvokeHandler invoke end ===
在动态代理的示例代理中,主要有3个步骤:
1、创建需要被代理的接口和实现类,这一步与静态代理相同
2、创建 InvocationHandler 接口的实现类, invoke 方法里实现控制逻辑,把 RealSubject 对象作为参数传入,从而让代理类间接持有 RealSubject 的引用。
3、使用 Proxy.newProxyInstance() 方法创建代理类对象
在调用接口方法时,实际上是调用 handler 的 invoke() 方法,相关的源码介绍:静态代理和动态代理。
JDK动态代理被广泛使用在各个框架中,比如 MyBatis 框架,下面简单介绍一下 MyBatis 中对JDK动态代理的使用。
MyBatis
在MyBatis测试用例中,使用 sqlSession.getMappser(Mapper.class) 创建 Mapper 接口的代理对象,再调用同名方法,使用的就是JDK动态代理实现的。
java
@Test
void shouldGetAUserStatic() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Mapper mapper = sqlSession.getMapper(Mapper.class);
User user = mapper.getUserStatic(1);
Assertions.assertNotNull(user);
Assertions.assertEquals("User1", user.getName());
}
}
sqlSession.getMapper()的时序图如图2所示:

图2. sqlSession.getMapper() 的时序图
MapperProxyFactory.newInstance 中使用 Proxy.newProxyInstance() 创建 Mpper 的代理对象,传入的参数 MapperProxy 就是 InvocationHandler 接口的实现类:
java
public class MapperProxy<T> implements InvocationHandler, Serializable {
// ..... 省略 ........ //
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
优点和缺点
优点
1、高扩展性:不需要对真实类 RealSubject 进行修改,就可以扩展它的功能
2、把控制和职责分离,与 RealSubject 无关的逻辑可以由代理类实现
缺点
1、额外增加了代理类,特别是动态代理在运行时创建 .class 字节码和 对象,造成性能下降****
小结
1、使用代理模式控制对真实类的访问、提供与真实类无关的逻辑实现等
2、使用动态代理在运行时创建接口的代理实现
从两者的 UML 图上可以看出,代理模式和装饰器模式 很相似,但是两者有着很大的差异,两者的差异如下:
1、 重点不同:装饰器模式的重点在于增强目标对象的功能,代理模式的重点在于保护和隐藏目标对象(实现透明访问)或者增加与业务无关的功能。
2、实现方式不同:装饰器是把目标对象作为构造器参数,代理模式是在代理类中创建目标对象。
3、 结果不同:装饰器模式对目标对象产生一种连续的、叠加的增强效果,代理模式是在代理类中一次性为目标对象添加功能(比如检查、增加日志)等。