JDK动态代理
1 关于代理
本笔记整理于动力节点老杜Spring6教程GoF之代理模式
1.1 对代理的理解
牛村的牛二看上了隔壁村小花,牛二不好意思直接找小花,于是牛二找来了媒婆王妈妈。这里面就有一个非常典型的代理模式。牛二不能和小花直接对接,只能找一个中间人。其中王妈妈是代理类,牛二是目标类。王妈妈代替牛二和小花先见个面。(现实生活中的婚介所)【在程序中,对象A和对象B无法直接交互时。】
代理模式种的角色
- 代理类
- 目标类
- 代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。
代理的作用
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
1.2 代理模式
- 静态代理
- 动态代理
2 静态代理
订单接口
/**
* 表示厂家、商家都要完成的功能
*/
public interface OrderService {
/**
* 生成订单
*/
String generate(String name);
}
实现订单接口
public class OrderServiceImpl implements OrderService {
@Override
public String generate(String name) {
System.out.println("订单已生成");
return name;
}
}
此时有个需求,想要统计代码运行了多长时间,怎么办
方案一:直接修改源代码添加统计时间方法这样直接导致了违反了OCP原则(对修改关闭,对扩展开开放)
方案二:编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:
public class OrderServiceImplSub extends OrderServiceImpl{
@Override
public String generate(String name) {
long begin = System.currentTimeMillis();
super.generate(name);
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
return name;
}
}
虽然这种方法也可以解决问题,但是设想由一百个类,每个类中有几十个方法需要统计,就会导致代码之间的耦合度增加
方案三:静态代理
可以为OrderService接口提供一个代理类。
public class OrderServiceProxy implements OrderService{
// 代理对象
// 目标对象
private OrderService orderService;
// 通过构造方法将目标对象传递给代理对象
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public String generate(String name) {
long begin = System.currentTimeMillis();
// 执行目标对象的目标方法
orderService.generate(name);
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
return name;
}
}
符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。
提供客户端程序
public class Client {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxy = new OrderServiceProxy(target);
// 调用代理对象的代理方法
String str = proxy.generate("张三通过静态代理下单了");
System.out.println(str);
}
}
以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。
系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。
此时就需要动态代理了
3 JDK 动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
订单接口
/**
* 表示厂家、商家都要完成的功能
*/
public interface OrderService {
/**
* 生成订单
*/
String generate(String name);
}
实现订单接口
public class OrderServiceImpl implements OrderService {
@Override
public String generate(String name) {
System.out.println("订单已生成");
return name;
}
}
客户端程序
public class Client {
public static void main(String[] args) {
// 第一步:创建目标对象
OrderService target = new OrderServiceImpl();
// 第二步:创建代理对象
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
// 第三步:调用代理对象的代理方法
orderServiceProxy.generate("张三通过动态代理下单了");
}
}
此时动态代理与静态代理的区别就是没有了代理类及其下行代码
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
这行代码做了两件事
- 在内存中生成了代理类的字节码
- 创建代理对象
关于
Proxy.newProxyInstance()
方法
*Proxy
是JDK提供的一个类,被称为动态代理。全包名为java.lang.reflect.Proxy
- 三个参数
- 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
- 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
- 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:
java.lang.reflect.InvocationHandler
。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
实现
InvocationHandler
接口
public class TimerInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
详解
InvocationHandler
InvocationHandler
接口中有一个方法invoke
,这个invoke
方法上有三个参数:
- 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
- 第二个参数:Method method。目标方法。
- 第三个参数:Object[] args。目标方法调用时要传的参数。
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;
}
}
完善Client程序
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));
// 调用代理对象的代理方法
String str = orderServiceProxy.generate("张三通过动态代理下单了");
System.out.println(str);
}
}
不管你有多少个Service接口,多少个业务类,这个TimerInvocationHandler接口是不是只需要写一次就行了,代码得到复用了。
而且最重要的是,以后程序员只需要关注核心业务的编写了,像这种统计时间的代码根本不需要关注。因为这种统计时间的代码只需要在调用处理器中编写一次即可。