Provide a surrogate or placeholder for another object to control access to it.
为其它对象提供一种代理以控制对这个对象的访问。
代理模式是通过创建一个代理对象,用代理对象去代表真实的对象。当客户端去操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成,只不过是通过代理操作的,也就是客户端操作代理,代理操作真实对象。
代理对象夹在客户端和真实对象中间,相当于一个中转,那么在中转的时候,我们就可以实现很多的花招,比如,权限控制、事务、记录日志等。
1、通过聚合方式实现代理模式(代理对象和真实对象实现相同的接口,代理对象持有真实对象的实例)
2、通过继承方式实现代理模式(代理对象继承自真实对象)
聚合比继承更适合代理模式,如果需要叠加多种代理需求,使用聚合方式更加灵活。
Subject 目标接口 RealSubject 具体的目标对象 Proxy 代理对象 三、代理模式的分类 虚拟代理(Virtual 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,切面编程)等领域都发挥了重要的作用。
从JDK 1.3开始,Java语言提供了对动态代理的支持,Java语言实现动态代理时需要用到位于java.lang.reflect包中的一些类,一般主要用到到以下两个类:
InvocationHandler 接口
InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler接口的子类)。
客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,由invoke()方法来实现对请求的统一处理。
Proxy 类
Proxy类提供了用于创建动态代理类和实例对象的方法。
假设我们的订单系统需要实现一个日志功能,在用户做一些关键操作时,将操作信息记录到数据库中。
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包,一般主要用到到以下两个类:
我们还拿前面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底层,其实是借助了ASM这个非常强大的Java字节码生成框架。生成字节码文件,再将生成的字节码文件Load进内存,最后创建一个动态代理类对象。
使用字节码技术生成代理类,比使用Java反射效率要高。
CGLib实现动态代理,不受代理类必须实现接口的限制,但是它不能对 final 类进行代理,也不能拦截被 final 和 static 修饰的方法。
六、三种代理方式的对比代理方式 | 优点 | 缺点 |
---|---|---|
静态代理 | 实现简单,容易理解。 | 被代理对象增删等变动方法时,代理类也需要改变。并且,如果被代理的类很多,也需要相应创建很多个代理类。 |
JDK动态代理 | 动态创建代理类,代码复用率高 。 | 只能够代理实现了接口的委托类。 |
CGLIB动态代理 | 动态创建代理类,且被代理类无需实现接口。使用字节码技术生成代理类,比使用Java反射效率要高。 | 不能对 final 类,以及 final 和 static 修饰的方法进行代理。 |