代理模式分为:静态代理、动态代理
我们以火车站售票的案例,涉及的对象有:购票人、火车站售票点、代售点(火车站售票点即为被代理对象,代售点即为代理对象)
购票人通过代售点买到火车站售票点的票,代售点为此收点费用!
代码实现:
public interface SellTickets {
void sell() ;
}
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站售票");
}
}
public class ProxyPoint implements SellTickets {
private TrainStation trainStation = new TrainStation() ;
@Override
public void sell() {
System.out.println("代理点先收点手续费!");
trainStation.sell();
}
}
public class Client {
public static void main(String[] args) {
// 创建代售点对象
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sell();
}
}
静态代理的缺点:
为了解决,这个问题,就有了动态代理 !
动态代理有两种实现方法:1、JDK实现动态代理 2、Cglib实现动态代理
前提:动态代理不存在代理类,而是在内存中动态的生成代理类!
Java 中提供了一个动态代理类 Proxy,Proxy 并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance
方法)来获取代理对象。
注:JDK 动态代要求必须定义接口,因为它只能对接口进行代理。
①、卖火车票的接口类:同静态代理。
public interface SellTickets {
void sell() ;
}
②、火车站类:火车票具有卖票功能,实现 SellTickets 接口,同静态代理。
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站售票");
}
}
③、获取代理对象的工厂类:ProxyFactory.java 通过工厂类获取代理对象,
/***
* 获取代理对象的工厂类
*
* 代理类也实现了对应的接口(此处接口指的是被代理对象实现的接口)
*/
public class ProxyFactory {
//声明目标对象(被代理对象)
private TrainStation station = new TrainStation() ;
public SellTickets getProxyObject(){
//返回代理对象
/*
* ClassLoader loader, // 类加载器,用于加载代理类,可以通过目标对象获取类加载器!
* Class<?>[] interfaces, //代理类实现的接口的字节码对象
* InvocationHandler h //代理对象的调用处理函数
*/
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
Object proxy, 代理对象,根ProxyObject是同一个对象,在invoke方法中基本不用
Method method, 对接口中的方法进行封装的method对象,本例子中指的就是sell
Object[] args 传递的实际参数
返回值:方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一定的费用(JDK动态代理)");
Object obj = method.invoke(station, args);// 通过反射执行目标对象中的方法(火车站中的sell方法)!
return obj;
}
}
);
return proxyObject ;
}
}
④、测试类client.java
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
SellTickets proxyObject = proxyFactory.getProxyObject();
proxyObject.sell(); //代理对象同样实现了SellTickets,此处用到多态,编译执行的是sell方法,本质执行的是invoke方法
}
}
测试结果 :
代理点收取一定的费用(JDK动态代理)
火车站售票
JDK动态代理底层的原理
思考问题:ProxyFactory 是代理类吗?
答:ProxyFactory 不是代理模式中所说的代理类,ProxyFactory 只是提供了一个生成代理对象的方法,而代理类是程序在运行过程中动
态的在内存中生成的。
1、准备工作:通过生成的代理对象可以获取到代理类的名字
System.out.println(proxyObject.getClass()); // class com.sun.proxy.$Proxy0
2、启动 Arthas【阿尔萨斯】,查看该代理类的结构 (注:已经删除非必要代码!)
// 程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
return;
}
public final void sell() {
this.h.invoke(this, m3, null);
}
}
我们看到:**1、**在静态代码块中==加载的是接口中的sell方法==,然后将其赋值给m3,**2、**代理类只存在一个有参构造,而且把参数通过
super(invocationHandler) ; 传递给父类Proxy中的成员变量 h ,这个invocationHandler就是我们的写的匿名内部类!
接下来就是:当我们代理对象调用sell 方法的时候,编译时执行的接口中的方法,运行时则是代理类中的sell方法,而代理类中的sell方法
public final void sell() {
this.h.invoke(this, m3, null);
}
会去调用我们匿名内部类invocationHandler中的invoke方法!
得出结论:代理对象的所有方法,都会被映射到invoke方法中执行 !
执行流程总结如下:
① 、在测试类中通过代理对象调用 sell() 方法
② 、根据多态的特性,执行的是代理类 $Proxy0 中的 sell() 方法
③ 、代理类 $Proxy0 中的 sell() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke 方法
④ 、invoke 方法通过反射执行了真实对象所属类 TrainStation 中的 sell() 方法
上面的案例中,如果没有定义 SellTickets 接口,只定义了 TrainStation 火车站类。则 JDK 代理无法使用,因为 JDK 动态代理要求必须定
义接口,它只能对接口进行代理。
CGLIB 是一个功能强大,高性能的代码生成包,它可以为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充
CGLIB 是第三方提供的包,所以需要引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
代码如下:CGLIB 动态代理可以不代理接口,直接代理类。
①、火车站类:火车票具有卖票功能
public class TranStation {
public void sell(){
System.out.println("火车站卖票!");
}
}
②、代理工厂类:用来获取代理对象
/**
* 代理对象工厂 : 用于获取我们的代理对象
*
* cglib的代理类,属于目标类的子类
*/
public class ProxyFactory implements MethodInterceptor {
private TranStation tranStation = new TranStation() ;
public TranStation getProxyObject(){
//创建Enhancer对象 , 类似于JDK代理中的Proxy类
Enhancer enhancer = new Enhancer() ;
//设置父类的字节码对象
enhancer.setSuperclass(TranStation.class);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TranStation proxyObject = (TranStation) enhancer.create() ;
return proxyObject ;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一定的费用(CGLIB实现动态代理)");
//调用目标类的方法
Object obj = method.invoke(tranStation, objects);
return obj;
}
}
两种动态代理的对比:JDK 代理 和 CGLib 代理
JDK 动态代理本质上是实现了被代理对象的接口,而 CGLib 本质上是继承了被代理对象,覆盖其中的方法
JDK 动态代理只能对实现了接口的类生成代理,CGLib 则没有这个限制。但是 CGLib 因为使用继承实现,所以 CGLib 所以无法对 final
类、private
方法和 static
方法进行代理。
JDK 动态代理是 JDK 里自带的,CGLib 动态代理需要引入第三方的 jar
包。
在性能上,JDK1.7 之前,由于使用了 FastClass 机制,CGLib 在执行效率上比 JDK 快,但是随着 JDK 动态代理的不断优化,从 JDK 1.7 开始,JDK 动态代理已经明显比 CGLib 更快了。所以,JDK1.8 以后,如果有接口就使用 JDK 动态代理,没有接口就使用 CGLib 代理。
动态代理和静态代理的对比:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理 (InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度,而动态代理不会出现该问题。
优点:
缺点:
JDK 动态代理为什么只能对实现了接口的类生成代理?
答:根本原因是通过 JDK 动态代理生成的类已经继承了 Proxy 类,所以无法再使用继承的方式去对类实现代理(类只支持单继承)。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_46571920/article/details/122677347
内容来源于网络,如有侵权,请联系作者删除!