代理模式(Proxy Pattern)属于结构型模式
概述
代理模式就是一个代理对象来间接访问对象,常用于无法直接访问某个对象或访问某个对象不方便的情况。
实际上代理在生活中处处都存在,比如房屋中介就是代理,Apple 的授权经销商就是代理,访问国外网站所用的代理服务器也是代理,Spring 框架的 AOP 也是通过代理模式实现的。
这些代理都有一个共同特点,就是使用的一致性和中间环节的透明性,也就是说找代理做的事情需要与找对象本身做的事情是一样的,只是中间环节隐藏了而已。
代理模式分为静态代理和动态代理。
静态代理
静态代理一般包含以下角色:
- 动作 : 一般使用接口或者抽象类来实现。
- 真实角色 : 被代理的角色。
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作。
- 客户 : 使用代理角色来进行一些操作。
代码实现1
1、定义租赁操作
/**
* 租赁操作
*/
public interface Rent {
/**
* 租房
*/
void rentHouse();
}
2、定义房东
/**
* 房东
*/
public class Landlord implements Rent{
@Override
public void rentHouse() {
System.out.println("房东出租房子");
}
}
3、定义中介
/**
* 中介
*/
public class Intermediary implements Rent{
/**
* 房东
*/
private Landlord landlord;
public Intermediary() {
}
public Intermediary(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rentHouse() {
// 看房
seeHouse();
// 签合同
contract();
// 租房
landlord.rentHouse();
// 收取费用
toll();
}
/**
* 看房
*/
public void seeHouse() {
System.out.println("中介带你看房");
}
/**
* 签合同
*/
public void contract() {
System.out.println("签租赁合同");
}
/**
* 收取费用
*/
public void toll() {
System.out.println("收中介费");
}
}
4、租客租房
// 房东
Landlord landlord = new Landlord();
// 中介给房东代理
Proxy proxy = new Proxy(landlord);
// 租房。不用面对房东,直接找中介租房即可
proxy.rentHouse();
在这个过程中,租客直接接触的是中介,见不到房东,但是租客依旧通过代理租到了房东的房子。
代码实现2
日常工作中最常见的就是增删改查业务,这里以实现增删改查代码的日志插入为例理解静态代理。
1、定义用户服务
/**
* 用户服务
*/
public interface UserService {
/**
* 新增
*/
public void add();
/**
* 删除
*/
public void delete();
/**
* 修改
*/
public void update();
/**
* 查询
*/
public void query();
}
2、定义用户服务实现类
/**
* 用户服务实现类
*/
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
3、定义用户服务代理类
/**
* 用户服务代理类
*/
public class UserServiceProxy implements UserService {
/**
* 用户服务实现类
*/
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
/**
* 打印日志
*
* @param msg 消息
*/
public void log(String msg) {
System.out.println("使用了" + msg + "方法");
}
}
4、客户端使用
// 用户服务实现类
UserServiceImpl userService = new UserServiceImpl();
// 用户服务代理类
UserServiceProxy proxy = new UserServiceProxy();
// 代理用户服务
proxy.setUserService(userService);
// 代理实现新增
proxy.add();
// 代理实现删除
proxy.delete();
// 代理实现修改
proxy.update();
// 代理实现查询
proxy.query();
执行结果为:
使用了add方法
新增了一个用户
使用了delete方法
删除了一个用户
使用了update方法
修改了一个用户
使用了query方法
查询了一个用户
使用代理类实现在不改动原有业务代码的情况下增加了日志。
优缺点
优点
1、可以使得真实角色更加轻松,不用再去关注一些琐碎的事情。
2、公共的业务由代理来完成,实现了业务的分工。
3、公共业务发生变化时扩展更加方便。
缺点
类变多了,多了代理类,工作量变大了,开发效率降低。
我们想要静态代理的优点,又不想要静态代理的缺点,所以 , 就有了动态代理 。
动态代理
- 动态代理的角色和静态代理的一样。
- 动态代理的代理类是动态生成的,静态代理的代理类是提前写好的。
动态代理分为两类 : 一类是基于接口 , 一类是基于类
- 基于接口的动态代理:JDK 动态代理。
- 基于类的动态代理:CGLIB 动态代理。
- 现在用的比较多的是 Javassist 来生成动态代理。
- 这里使用 JDK 的原生代码来实现,其余的道理都是一样的。
JDK 的动态代理需要了解两个类:Proxy
和 InvocationHandler
。查看 JDK 帮助文档:
Proxy:代理类
Proxy
提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,它实现了接口 InvocationHandler
。
newProxyInstance
方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
参数:
- loader - 类加载器来定义代理类。
- interfaces - 代理类实现的接口列表。
- h - 调度方法调用的调用处理函数。
返回值:
具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例。
异常:
IllegalArgumentException
:非法参数异常。
InvocationHandler:调用处理程序
InvocationHandler
是由代理实例的调用处理程序实现的接口 。 每个代理实例都有一个关联的调用处理程序。
invoke
方法:
Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable
处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
参数:
- proxy - 调用该方法的代理实例。
- method - 所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
- args - 包含的方法调用传递代理实例的参数值的对象的阵列,或 null 如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer 或 java.lang.Boolean。
代码实现1
抽象角色和真实角色和之前的一样。
1、定义租赁操作
/**
* 租赁操作
*/
public interface Rent {
/**
* 租房
*/
void rentHouse();
}
2、定义房东
/**
* 房东
*/
public class Landlord implements Rent {
@Override
public void rentHouse() {
System.out.println("房东出租房子");
}
}
3、定义中介
/**
* 中介
*/
public class Intermediary implements InvocationHandler {
/**
* 租赁操作
*/
private Rent rent;
/**
* 代理租赁
*
* @param rent 需要租赁的对象
*/
public void setRent(Rent rent) {
this.rent = rent;
}
/**
* 生成代理对象
*
* @return 代理对象
*/
public Object getProxy() {
// 重点是第二个参数,获取要代理的抽象角色,之前都是一个角色,现在可以代理一类角色
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
/**
* 处理代理实例上的方法调用并返回结果
*
* @param proxy 代理类
* @param method 代理类的调用处理程序的方法对象
* @param args 包含的方法调用传递代理实例的参数值的对象的阵列
* @return 代理对象
* @throws Throwable 错误
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 看房
seeHouse();
// 签合同
contract();
// 动态代理租房业务:本质利用反射实现
Object result = method.invoke(rent, args);
// 收取费用
toll();
return result;
}
/**
* 看房
*/
public void seeHouse() {
System.out.println("中介带你看房");
}
/**
* 签合同
*/
public void contract() {
System.out.println("签租赁合同");
}
/**
* 收取费用
*/
public void toll() {
System.out.println("收中介费");
}
}
4、租客租房
// 房东
Landlord landlord = new Landlord();
// 中介
Intermediary intermediary = new Intermediary();
// 中介给房东提供代理服务
intermediary.setRent(landlord);
// 动态生成对应的代理类
Rent proxy = (Rent) intermediary.getProxy();
// 代理类执行租房操作
proxy.rentHouse();
一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口。
代码实现2
使用动态代理再来实现前面的增删改查业务。
1、定义用户服务
/**
* 用户服务
*/
public interface UserService {
/**
* 新增
*/
public void add();
/**
* 删除
*/
public void delete();
/**
* 修改
*/
public void update();
/**
* 查询
*/
public void query();
}
2、定义用户服务实现类
/**
* 用户服务实现类
*/
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
3、定义用户服务代理类
/**
* 用户服务代理类
*/
public class UserServiceProxy implements InvocationHandler {
/**
* 目标对象
*/
private Object target;
/**
* 代理目标对象
*
* @param target 目标对象
*/
public void setTarget(Object target) {
this.target = target;
}
/**
* 生成代理对象
*
* @return 代理对象
*/
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
* 处理代理实例上的方法调用并返回结果
*
* @param proxy 代理类
* @param method 代理类的调用处理程序的方法对象
* @param args 包含的方法调用传递代理实例的参数值的对象的阵列
* @return 代理对象
* @throws Throwable 错误
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 打印日志
log(method.getName());
// 执行代理方法
return method.invoke(target, args);
}
/**
* 打印日志
* @param msg 消息
*/
public void log(String msg) {
System.out.println("使用了" + msg + "方法");
}
}
4、客户端使用
// 用户服务实现类
UserServiceImpl userService = new UserServiceImpl();
// 用户服务代理类
UserServiceProxy userServiceProxy = new UserServiceProxy();
// 代理用户服务
userServiceProxy.setTarget(userService);
// 动态生成代理对象
UserService proxy = (UserService) userServiceProxy.getProxy();
// 代理实现新增
proxy.add();
// 代理实现删除
proxy.delete();
// 代理实现修改
proxy.update();
// 代理实现查询
proxy.query();
执行结果为:
使用了add方法
新增了一个用户
使用了delete方法
删除了一个用户
使用了update方法
修改了一个用户
使用了query方法
查询了一个用户
这样就用动态代理实现了增删改查业务,而且由于目标对象为 Object,需要代理其他类时只需要转化为对应的类即可,十分易于扩展。
优缺点
优点
1、静态代理有的它都有,静态代理没有的,它也有。
2、可以使得真实角色更加轻松,不用再去关注一些琐碎的事情。
3、公共的业务由代理来完成,实现了业务的分工。
4、公共业务发生变化时扩展更加方便。
5、动态代理可以代理一类业务。
6、动态代理可以代理多个类,代理的是接口。
缺点
1、需要对实现动态代理的类和方法有一定了解,学习成本较静态代理更高。
2、动态代理的使用逻辑更为复杂,不如静态代理好理解。
使用场景
按职责来划分,通常有以下使用场景:
1、远程代理。
2、虚拟代理。
3、Copy-on-Write 代理。
4、保护(Protect or Access)代理。
5、Cache代理。
6、防火墙(Firewall)代理。
7、同步化(Synchronization)代理。
8、智能引用(Smart Reference)代理。
注意事项
1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
参考
https://www.bilibili.com/video/BV1mc411h719?p=9\&vd\_source=299f4bc123b19e7d6f66fefd8f124a03
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。