一、定义
代理模式(Proxy Pattern):结构型模式之一,为真实对象提供一个代理,从而控制对真实对象的访问。
二、UML类图
三、角色职责
- 抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。
- 代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
四、代码实现
前言:举个栗子,我马上就要过生日了,为了奖励辛苦了一年的自己,我决定给自己买一台iPhone13,于是我打开了苹果官网,发现256G的iPhone13居然需要6799元,这实在是太贵了!我咨询香港的朋友,发现港版的iPhone13只需要6299元,整整便宜了500块,于是我决定去香港购买一台iPhone13,这个时候又想起来,现在香港的疫情非常严重,我可能去的时候好好的,回不来了,于是我想到了可以让代购帮我购买一台iPhone13。这个时候我们就用到了代理模式。
-
静态代理:要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。
售卖接口(抽象角色 Subject)
public interface iPhone13 { void sell(); }
香港AppleStore(真实角色 RealSubject)
public class HongKongAppleStore implements iPhone13 { @Override public void sell() { System.out.println("出售iPhone13"); } }
代购(代理角色 Proxy)
public class DelegatedPurchase implements iPhone13 { private iPhone13 iphone13; DelegatedPurchase(iPhone13 iphone13) { this.iphone13 = iphone13; } @Override public void sell() { System.out.println("开始代购iPhone13"); iphone13.sell(); System.out.println("代购成功"); } }
测试类
public class ProxyTest { public static void main(String[] args) { // 创建AppleStore iPhone13 iPhone = new HongKongAppleStore(); // 帮助用户代购iPhone13 iPhone13 delegatedPurchase = new DelegatedPurchase(iPhone); delegatedPurchase.sell(); } }
运行结果
开始代购iPhone13 出售iPhone13 代购成功
-
动态代理:在程序运行时创建的代理方式被成为动态代理。代理类并不是在Java代码中定义的,而是在运行时根据代码中的“指示”动态生成。
我们只需要修改代理类和测试类即可。
代购(代理角色 Proxy)public class DelegatedPurchase implements InvocationHandler { private iPhone13 iphone13; DelegatedPurchase(iPhone13 iphone13) { this.iphone13 = iphone13; } /** * 代理方法 * * @param proxy 代理对象 * @param method 调用的方法,被封装成一个方法对象 * @param args 调用方法时的实际参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始代购iPhone13"); // 通过反射去调用被代理类的方法 Object invoke = method.invoke(iphone13, args); System.out.println("代购成功"); return invoke; } }
测试类
public class ProxyTest { public static void main(String[] args) { // 创建AppleStore iPhone13 iPhone = new HongKongAppleStore(); // 帮助用户代购iPhone13 DelegatedPurchase proxy = new DelegatedPurchase(iPhone); /* * 通过Proxy.newProxyInstance()方法在运行时动态创建代理类 * ClassLoader loader, 类加载器,这里主要是选择代理的类加载器 * lass<?>[] interfaces, 被代理类的所有接口 * InvocationHandler h 代理类 */ iPhone13 delegatedPurchase = (iPhone13) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), iPhone.getClass().getInterfaces(), proxy); delegatedPurchase.sell(); } }
-
CGLib实现动态代理
我们只需要修改代理类与测试类即可。
Maven依赖<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>```
代购(代理角色 Proxy)
public class DelegatedPurchase implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { // 设置需要创建子类的类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); // 通过字节码技术动态创建子类实例 return enhancer.create(); } // 实现MethodInterceptor接口方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开始代购iPhone13"); // 通过代理类调用父类中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("代购成功"); return result; } }
代理对象的生成由Enhancer类实现,Enhancer是CGLib的字节码增强器,可以很方便的对类进行扩展。
Enhancer 创建代理对象的大概步骤如下:
1、生成代理类 Class 二进制字节码。
2、通过 Class.forname() 加载字节码文件, 生成 Class 对象。
3、过反射机制获得实例构造, 并创建代理类对象。
CGLib定义的intercept() 接口方法,拦截所有目标类方法的调用。其中obj代表目标类的实例,method为目标类方法的反射,args为方法的动态入参, proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。测试类
public class ProxyTest { public static void main(String[] args) { DelegatedPurchase proxy = new DelegatedPurchase(); iPhone13 delegatedPurchase = (iPhone13) proxy.getProxy(HongKongAppleStore.class); delegatedPurchase.sell(); } }
使用JDK创建动态代理有一个限制,即它只能为接口创建代理实例,对于没有定义接口的业务方法的类,可以使用CGLib进行动态代理,CGLib是一个强大的, 高性能的代码生成库,被广泛应用于AOP框架,用以提供方法拦截操作。CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用,并织入横切逻辑。根据CGLib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么CGLib代理是无法正常工作的,因为final类型方法不能被重写。
五、源码分析
JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类,这两个类相互配合,入口是Proxy。
Proxy有个静态方法:getProxyClass(ClassLoader, interfaces),只要你给它传入类加载器和一组接口,它就给你返回代理Class对象。用通俗的话说,getProxyClass()这个方法,会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象的。
所以,一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理Class,通过代理Class即可创建代理对象。
静态代理
动态代理
不过实际编程中,一般不用getProxyClass(),而是使用Proxy类的另一个静态方法:Proxy.newProxyInstance(),直接返回代理实例,连中间得到代理Class对象的过程都帮你隐藏。那么我们就来讲一讲Proxy.newProxyInstance()。
newProxyInstance()方法有三个参数:
ClassLoader loader:被代理类的装载器
Class<?>[] interfaces:被代理类的所有接口(生成哪个对象的代理对象,通过接口指定)
InvocationHandler h:代理类在执行对应方法的时候要调用的方法
那newProxyInstance()是如果通过接口构造一个代理对象的呢?我们进入源码一探究竟。
方法中会通过getProxyClass0(loader, intfs)生成代理类,然后获取代理类的构造方法,再根据代理类的构造方法实例化代理类对象。那getProxyClass0()是如果生成代理类的呢?我们点进这个方法。
我们点进代理类缓存类。
原来接口工厂和代理类工厂都通过kv的形式存储在代理类缓存中了,然后会从这个缓存中get()代理类。那他是如果get()到代理类的呢?我们点进get()方法。
其中key和parameter就代表我们传进去的两个参数,那如何根据接口生成subKey呢?我们再点进apply()方法。
这里apply()方法会根据你接口的数量去进行实例化生成subKey对象。然后通过subKey去Map中拿到我们所需要的代理类。
如果Map中没有我们的代理类,就会调用Factory()方法。然后factory就有值了,但这个时候supplier肯定也是空,所以会把factory的值赋值给supplier。
之后循环第二次while (true) 的时候,supplier就不为null了,再去调用get()方法。
这里就会将参数生成key,再存入Map中。这样就可以get()到代理类了。那我们的代理类究竟长什么样子呢?我们先找到ProxyGenerator类。
然后在里面找到saveGeneratedFiles复制里面的值。我这里是JDK1.8版本,其他版本的可以对应着去找一下。在我们的测试类中加入下面这行代码运行即可。
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
这样我们就成功的生成了代理类。
所有通过动态代理实现的方法全部通过invoke() 调用。
invoke()方法的三个参数:
Object proxy:代理对象
Method method:反射调用的方法,被封装成一个方法对象
Object[] args:调用方法时的实际参数、
所以看到这里,大家应该就非常明了了。那我现在举个栗子,一个大内太监(接口Class),空有一身武艺(类信息),但是无法传给后人。现在江湖上有个妙手神医(Proxy类),发明了克隆大法(newProxyInstance),不仅能克隆太监的一身武艺,还保留了小DD(构造器)…(这到底是道德の沦丧,还是人性的扭曲,欢迎走进动态代理)
六、优缺点分析
静态代理
优点:静态代理对客户隐藏了被代理类接口的具体实现类,在一定程度上实现了解耦合,同时提高了安全性。
缺点:静态代理只能对某个固定接口的实现类进行代理服务,其灵活性不强。故一般大项目不会选择静态代理。
动态代理
-
JDK动态代理
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。
2、JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口。 -
CGLib动态代理
1、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final。
2、CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理。
七、适用场景
想要对类中的方法批量处理,或者想要再目标对象的基础上 , 对目标对象的功能进行增强,也就是扩展对象的功能时我们可以使用代理模式。假如一个类中的方法有100个,我们想要对这些方法都做日志处理。这个时候我们就可以使用代理,简化代码,不需要在每个方法调用的时候都做日志处理。
八、总结
代理模式的设计动机是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。在代理模式中,代理对象主要起到一个中介的作用,用于协调与连接调用者(即客户端)和被调用者(即目标对象),在一定程度上降低了系统的耦合度,同时也保护了目标对象。