文章目录
一、代理模式定义
Provide a surrogate or placeholder for another object to control access to it.
为其它对象提供一种代理以控制对这个对象的访问。
代理模式是通过创建一个代理对象,用代理对象去代表真实的对象。当客户端去操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成,只不过是通过代理操作的,也就是客户端操作代理,代理操作真实对象。
代理对象夹在客户端和真实对象中间,相当于一个中转,那么在中转的时候,我们就可以实现很多的花招,比如,权限控制、事务、记录日志等。
二、代理模式的结构和说明
1、通过聚合方式实现代理模式(代理对象和真实对象实现相同的接口,代理对象持有真实对象的实例)
2、通过继承方式实现代理模式(代理对象继承自真实对象)
聚合比继承更适合代理模式,如果需要叠加多种代理需求,使用聚合方式更加灵活。
- Subject 目标接口
- RealSubject 具体的目标对象
- Proxy 代理对象
三、代理模式的分类
- 虚拟代理(Virtual Proxy)
如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 - 保护代理(Protect Proxy)
控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。 - 远程代理(Remote Proxy)
为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。远程代理在Java中最典型的应用就是RMI技术。 - 缓冲代理(Cache Proxy)
为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。 - 智能引用代理(Smart Reference Proxy)
当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。 - copy on write 代理(Copy on write Proxy)
只有对象确实改变了,才会真的去拷贝(或克隆)这个对象。 - 防火墙代理(Firewall Proxy)
保护对象不被恶意用户访问和操作。 - 同步代理(Synchronization Proxy)
使多个用户可以同时访问目标对象而不会出现冲突。
四、代理模式示例
让我们来模拟一个保护代理的场景。有一个订单系统,要求是,一旦订单被创建,只有订单的创建人才能修改订单的订购数量,其他人则不能修改。
OrderApi 订单对象的接口定义(目标接口)
/**
* 订单对象的接口定义(目标接口)
*/
public interface OrderApi {
/**
* 修改订单数量
* @param orderNum 订单数量
* @param user 修改人
*/
void changeOrderNum(int orderNum,String user);
}
Order 订单对象(具体的目标对象)
/**
* 订单对象(具体的目标对象)
*/
@Data
public class Order implements OrderApi{
/**
* 订单订购的产品名称
*/
private String productName;
/**
* 订单订购的数量
*/
private int orderNum;
/**
* 创建订单的人员
*/
private String orderUser;
/**
* 构造方法,传入构建需要的数据
* @param productName 订单订购的产品名称
* @param orderNum 订单订购的数量
* @param orderUser 创建订单的人员
*/
public Order(String productName,int orderNum,String orderUser){
this.productName = productName;
this.orderNum = orderNum;
this.orderUser = orderUser;
}
@Override
public void changeOrderNum(int orderNum, String user) {
this.orderNum = orderNum;
}
@Override
public String toString() {
return "Order{" +
"productName='" + productName + '\'' +
", orderNum=" + orderNum +
", orderUser='" + orderUser + '\'' +
'}';
}
}
OrderProxy 订单的代理对象
/**
* 订单的代理对象
*/
public class OrderProxy implements OrderApi{
/**
* 持有被代理的具体的目标对象
*/
private Order order;
/**
* 构造方法,传入被代理的具体的目标对象
* @param realSubject 被代理的具体的目标对象
*/
public OrderProxy(Order realSubject){
this.order = realSubject;
}
@Override
public void changeOrderNum(int orderNum, String user) {
//控制访问权限,只有创建订单的人员才能够修改
if(Objects.equals(user, order.getOrderUser())){
order.changeOrderNum(orderNum,user);
}else{
System.out.println("对不起"+user+",您无权修改订单中的产品名称。");
}
}
@Override
public String toString() {
return "OrderProxy{" +
"order=" + order +
'}';
}
}
以上,保护代理已经设计好了,下边我们来模拟一下使用。
public class Client {
public static void main(String[] args) {
//张三先登录系统创建了一个订单
OrderApi order = new OrderProxy(new Order("泡面",1,"张三"));
//李四想要来修改,那就会报错
order.changeOrderNum(3, "李四");
//输出order
System.out.println("李四修改后订单记录没有变化:"+order);
//张三修改就不会有问题
order.changeOrderNum(3, "张三");
//再次输出order
System.out.println("张三修改后,订单记录:"+order);
}
}
执行main方法后,得到下边的输出:
对不起李四,您无权修改订单中的产品名称。
李四修改后订单记录没有变化:OrderProxy{order=Order{productName=‘泡面’, orderNum=1, orderUser=‘张三’}}
张三修改后,订单记录:OrderProxy{order=Order{productName=‘泡面’, orderNum=3, orderUser=‘张三’}}
五、动态代理
像前边定义的这种,代理类在程序运行前就已经存在,代理类所实现的接口和所代理的方法都被固定,这种代理方式被称为静态代理。
静态代理会存在这样的缺点:如果Subject(目标接口)中方法发生变化,那么真实类和代理类都需要跟着变化;如果需要被代理的真实类有很多,就需要相应的创建很多个代理类。
那么,有没有一种机制能够让系统在运行时动态创建代理类,并可以对多个真实类进行代理操作?有,这就是我们将要介绍的这种动态代理(Dynamic Proxy)。
动态代理是一种较为高级的代理模式,它在事务管理、AOP(Aspect-OrientedProgramming,切面编程)等领域都发挥了重要的作用。
1、JDK动态代理
从JDK 1.3开始,Java语言提供了对动态代理的支持,Java语言实现动态代理时需要用到位于java.lang.reflect包中的一些类,一般主要用到到以下两个类:
-
InvocationHandler 接口
InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler接口的子类)。
客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,由invoke()方法来实现对请求的统一处理。 -
Proxy 类
Proxy类提供了用于创建动态代理类和实例对象的方法。
JDK动态代理使用步骤
- 创建一个实现接口 InvocationHandler 的类,重写invoke方法。
- 通过 Proxy 的静态方法 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 创建一个代理对象。
- 通过代理对象调用方法。
JDK动态代理示例
假设我们的订单系统需要实现一个日志功能,在用户做一些关键操作时,将操作信息记录到数据库中。
UserService 用户业务层的接口定义(目标接口)
/**
* 用户业务层的接口定义
*/
public interface UserService {
/**
* 修改密码
*/
void changePwd();
}
UserServiceImpl 用户业务层的具体处理类(真实的对象)
/**
* 用户业务层的具体处理类
*/
public class UserServiceImpl implements UserService{
@Override
public void changePwd() {
System.out.println("用户修改密码");
}
}
以上,目标接口和实现类已经有了,接下来我们来定义一个处理类,声明一下代理中具体要执行的动作。
LogHandler 使用JDK动态代理时具体处理者
/**
* 使用JDK动态代理时具体处理者
*/
public class LogHandler implements InvocationHandler {
/**
* 被代理的真实对象
*/
private Object object;
public LogHandler(Object object) {
this.object = object;
}
/**
* @param proxy 代理对象,Proxy.newProxyInstance方法的返回对象
* @param method 调用的方法
* @param args 方法中的参数
* @return 调用方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(String.format("%s.%s 方法被调用 -- 保存到数据库", object.getClass().getSimpleName(),method.getName()));
return method.invoke(object, args);
}
}
我们来看下怎么来使用JDK动态代理
public class Client {
public static void main(String[] args) {
// 创建一个用户业务的真实处理对象
UserService userService = new UserServiceImpl();
// 创建处理程序实现类(InvocationHandler)
LogHandler logHandler = new LogHandler(userService);
Class cls = userService.getClass();
/**
* 创建代理对象
* 三个参数分别为:类加载器、目标接口、处理程序实现类(InvocationHandler)
*/
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), logHandler);
// 执行修改密码方法
userServiceProxy.changePwd();
}
}
执行main方法后,得到下边的输出:
UserServiceImpl.changePwd 方法被调用 – 保存到数据库
用户修改密码
如果我们的目标接口中方法有变动,如新增一个方法:
UserService 用户业务层的接口定义(目标接口)
/**
* 用户业务层的接口定义
*/
public interface UserService {
/**
* 修改密码
*/
void changePwd();
/**
* 修改收货地址(新增方法)
*/
void changeAddr();
}
UserServiceImpl 用户业务层的具体处理类(具体的目标对象)
/**
* 用户业务层的具体处理类
*/
public class UserServiceImpl implements UserService{
@Override
public void changePwd() {
System.out.println("用户修改密码");
}
@Override
public void changeAddr() {
System.out.println("用户修改收货地址");
}
}
我们新增的方法加好了,我们再来看看动态代理能否对新方法有效
public class Client {
public static void main(String[] args) {
// 创建一个用户业务的真实处理对象
UserService userService = new UserServiceImpl();
// 创建处理程序实现类(InvocationHandler)
LogHandler logHandler = new LogHandler(userService);
Class cls = userService.getClass();
/**
* 创建代理对象
* 三个参数分别为:类加载器、目标接口、处理程序实现类(InvocationHandler)
*/
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), logHandler);
// 执行修改密码方法
userServiceProxy.changePwd();
// 执行修改收货地址方法
userServiceProxy.changeAddr();
}
}
执行main方法后,得到下边的输出:
UserServiceImpl.changePwd 方法被调用 – 保存到数据库
用户修改密码
UserServiceImpl.changeAddr 方法被调用 – 保存到数据库
用户修改收货地址
从输出中,我们可以看出,动态代理对新增的方法同样有效。
那么,如果我们需要代理的类非常多怎么办?来,安排上。
OrderService 订单业务层的接口定义(目标接口)
/**
* 订单业务层的接口定义
*/
public interface OrderService {
/**
* 保存订单
*/
void saveOrder();
}
OrderServiceImpl 订单业务层的具体处理类(具体的目标对象)
/**
* 订单业务层的具体处理类
*/
public class OrderServiceImpl implements OrderService{
@Override
public void saveOrder() {
System.out.println("保存订单信息");
}
}
新的接口和具体实现类也加好了,我们再来看看动态代理能否有效
public class Client {
public static void main(String[] args) {
// 创建一个订单业务的真实处理对象
OrderService orderService = new OrderServiceImpl();
// 创建处理程序实现类(InvocationHandler)
LogHandler logHandler = new LogHandler(orderService);
Class cls = orderService.getClass();
/**
* 创建代理对象
* 三个参数分别为:类加载器、目标接口、处理程序实现类(InvocationHandler)
*/
OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), logHandler);
// 执行代理对象的保存订单方法
orderServiceProxy.saveOrder();
}
}
执行main方法后,得到下边的输出:
OrderServiceImpl.saveOrder 方法被调用 – 保存到数据库
保存订单信息
同样有效,这样一来,我们就不需要像静态代理那样创建那么多的代理类了。
JDK动态代理实现原理
利用Java的反射技术(Java Reflection),在运行时创建一个实现给定目标接口的新类(也称“动态代理类”),将产生的新类进行编译生成class文件,将class字节码文件Load进内存,最后创建一个此动态代理类的对象。
JDK动态代理局限性
使用JDK动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方式实现动态代理。
2、CGLIB动态代理
前面介绍了JDK动态代理的局限性,它要求被代理类必须实现一个接口。
而CGLIB动态代理则没有此类强制性要求。CGLib动态代理是代理类去继承目标类,然后重写目标类的方法。既然是继承,则被代理类不能为final,并且 final 和static 修饰的方法也不会被拦截。
使用CGLIB动态代理,需要引入cglib.jar这个Jar包,一般主要用到到以下两个类:
- MethodInterceptor 接口
MethodInterceptor 接口是一个方法拦截器接口,接口中只有一个intercept回调方法,我们要实现的业务在这个方法中添加即可。 - Enhancer 类
CGLIB通过Enhancer类来动态创建代理类和实例对象。
CGLIB动态代理使用步骤
- 创建一个实现接口 MethodInterceptor 的类,重写 intercept 方法。
- 通过 Enhancer 的 create 方法创建一个代理对象。
- 通过代理对象调用方法。
CGLIB动态代理示例
我们还拿前面JDK动态代理中的示例来做下演示。为了演示CGLIB对没有实现接口的类也可以实现动态代理,我们这里先把接口去掉。
OrderServiceImpl 订单业务层的具体处理类
/**
* 订单业务层的具体处理类
*/
public class OrderServiceImpl{
public void saveOrder() {
System.out.println("保存订单信息");
}
}
我们来创建一个方法拦截器。
LogMethodInterceptor 使用CGLIB动态代理时具体处理者
/**
* 使用CGLIB动态代理时具体处理者
*/
public class LogMethodInterceptor implements MethodInterceptor {
/**
* @param obj CGLib动态生成的代理类实例
* @param method 被代理的方法
* @param args 方法的参数
* @param methodProxy 代理类对方法的代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println(String.format("%s.%s 方法被调用 -- 保存到数据库", method.getDeclaringClass().getSimpleName(),method.getName()));
return methodProxy.invokeSuper(obj,args);
}
}
我们来看下怎么来使用CGLIB动态代理
public class Client {
public static void main(String[] args) {
// 创建拦截器(MethodInterceptor)
LogMethodInterceptor logMethodInterceptor = new LogMethodInterceptor();
// 创建Enhancer对象,类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
// 设置被代理类,也就是父类
enhancer.setSuperclass(OrderServiceImpl.class);
// 设置回调函数
enhancer.setCallback(logMethodInterceptor);
// 这里的create方法就是正式创建代理类
OrderServiceImpl orderServiceProxy = (OrderServiceImpl) enhancer.create();
// 执行代理对象的保存订单方法
orderServiceProxy.saveOrder();
}
}
执行main方法后,得到下边的输出:
OrderServiceImpl.saveOrder 方法被调用 – 保存到数据库
保存订单信息
CGLIB动态代理实现原理
CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。生成字节码文件,再将生成的字节码文件Load进内存,最后创建一个动态代理类对象。
使用字节码技术生成代理类,比使用Java反射效率要高。
CGLIB动态代理注意事项
CGLib实现动态代理,不受代理类必须实现接口的限制,但是它不能对 final 类进行代理,也不能拦截被 final 和 static 修饰的方法。
六、三种代理方式的对比
代理方式 | 优点 | 缺点 |
---|---|---|
静态代理 | 实现简单,容易理解。 | 被代理对象增删等变动方法时,代理类也需要改变。并且,如果被代理的类很多,也需要相应创建很多个代理类。 |
JDK动态代理 | 动态创建代理类,代码复用率高 。 | 只能够代理实现了接口的委托类。 |
CGLIB动态代理 | 动态创建代理类,且被代理类无需实现接口。使用字节码技术生成代理类,比使用Java反射效率要高。 | 不能对 final 类,以及 final 和 static 修饰的方法进行代理。 |
七、代理模式的应用场景及案例
- 需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理。
- 需要按需创建开销很大的对象的时候,可以使用虚拟代理。
- 需要控制对象访问权限的时候,可以使用保护代理。
- 需要对访问对象执行一些附加操作的时候,可以使用智能引用代理。
- Hibernate 的延迟加载(Lazy Load)就是虚拟代理的典型应用。
- Spring AOP就是动态代理的典型应用。