代理模式是GoF23种设计模式之一,其作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。
代理模式中的角色:代理类、目标类、代理类和目标类的公共接口(客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口)。
代理模式有动态代理和静态代理两种模式,先实现静态代理作为引入。
考虑一个业务场景:某个项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。
第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑。这种方法可以满足需求,但显然是违背了OCP开闭原则,这种方案不可取。
第二种方案:编写一个子类继承OrderServiceImpl,在子类中重写每个方法。这种方式也可以解决需求,但是由于采用了继承的方式,导致代码之间的耦合度较高。
第三种方案就是使用静态代理了:
先准备一个接口类和实现类:
- package service;
-
- public interface OrderService {
- void generate();
-
- void detail();
-
- void modify();
- }
- package service;
-
- public class OrderServiceImpl implements OrderService{
- @Override
- public void generate() {
- try {
- Thread.sleep(1234);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单已生成");
- }
-
- @Override
- public void detail() {
- try {
- Thread.sleep(2541);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单信息如下:******");
- }
-
- @Override
- public void modify() {
- try {
- Thread.sleep(1010);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("订单已修改");
- }
- }
其中Thread.sleep()方法的调用是为了模拟操作耗时。
采用静态代理为OrderService接口提供一个代理类。
- package service;
-
- public class OrderServiceProxy implements OrderService{
- // 目标对象
- private OrderService orderService;
-
- // 通过构造方法将目标对象传递给代理对象
- public OrderServiceProxy(OrderService orderService) {
- this.orderService = orderService;
- }
-
- @Override
- public void generate() {
- long begin = System.currentTimeMillis();
- // 执行目标对象的目标方法
- orderService.generate();
- long end = System.currentTimeMillis();
- System.out.println("耗时"+(end - begin)+"毫秒");
- }
-
- @Override
- public void detail() {
- long begin = System.currentTimeMillis();
- // 执行目标对象的目标方法
- orderService.detail();
- long end = System.currentTimeMillis();
- System.out.println("耗时"+(end - begin)+"毫秒");
- }
-
- @Override
- public void modify() {
- long begin = System.currentTimeMillis();
- // 执行目标对象的目标方法
- orderService.modify();
- long end = System.currentTimeMillis();
- System.out.println("耗时"+(end - begin)+"毫秒");
- }
- }
这种方式的优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的。
下面在客户端中使用代理对象:
- import service.OrderService;
- import service.OrderServiceImpl;
- import service.OrderServiceProxy;
-
- public class Client {
- public static void main(String[] args) {
- //创建目标对象
- OrderService target = new OrderServiceImpl();
- //创建代理对象
- OrderService proxy = new OrderServiceProxy(target);
-
- //调用代理对象的代理方法
- proxy.generate();
- proxy.modify();
- proxy.detail();
-
- }
- }
运行结果:
以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。
但是如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
这里简单实现一下JDK动态代理技术:
我们在静态代理的时候,除了以上一个接口和一个实现类之外,还要写一个代理类UserServiceProxy。在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:
- import service.OrderService;
- import service.OrderServiceImpl;
- import service.TimerInvocationHandler;
-
- import java.lang.reflect.Proxy;
-
- public class client {
- public static void main(String[] args) {
- // 第一步:创建目标对象
- OrderService target = new OrderServiceImpl();
- // 第二步:创建代理对象
- OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
- // 第三步:调用代理对象的代理方法
- // 调用代理对象的代理方法
- orderServiceProxy.detail();
- orderServiceProxy.modify();
- orderServiceProxy.generate();
- }
- }
以上第二步创建代理对象是重点,其中newProxyInstance()方法有三个参数:
接下来要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:
- package service;
-
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
-
- public class TimerInvocationHandler implements InvocationHandler {
- // 目标对象
- private Object target;
-
- // 通过构造方法来传目标对象
- public TimerInvocationHandler(Object target) {
- this.target = target;
- }
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 目标执行之前增强。
- long begin = System.currentTimeMillis();
- // 调用目标对象的目标方法
- Object retValue = method.invoke(target, args);
- // 目标执行之后增强。
- long end = System.currentTimeMillis();
- System.out.println("耗时"+(end - begin)+"毫秒");
- // 一定要记得返回哦。
- return retValue;
- }
- }
InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:
最后,可以运行客户端程序了,运行结果: