🎉🎉🎉写在前面:
博主主页:🌹🌹🌹戳一戳,欢迎大佬指点!
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个小菜鸟嘿嘿
-----------------------------谢谢你这么帅气美丽还给我点赞!比个心-----------------------------
代理设计模式
- 一,初识代理模式
- 二,静态代理模式
- 2.1,静态代理使用场景
- 2.1.1,缓存代理
- 2.1.2,安全代理
- 2.1.3,虚拟代理
- 2.2,静态代理总结
- 三,动态代理模式
- 3.1,JDK动态代理
- 3.1.1,实例实现
- 3.1.2,过程解析
- 3.2,CGLIB动态代理
- 3.2.1,实例实现
- 3.2.2,过程解析
- 3.3,JDK动态代理 vs CGLIB
一,初识代理模式
什么是代理设计模式?
代理设计模式是一种结构型设计模式,它为我们的目标对象提供一个代理,以控制对象的访问。这个代理的过程中,我们可以通过代理对象来做到增强目标对象功能的目的。
举一个例子来理解,目标对象就是明星本人,而代理对象是此人的经纪人,当商家找这位明星做代言的时候,商家不会直接去和明星本人谈,而是通过经纪人去交涉,确定一些列的合作细节,比如代言非,合同等一切琐碎的细节问题,最终出席代言的还是明星本人。这个过程中你,经纪人就作为代理对象,控制了目标对象的访问,并增强了一些功能。
代理设计模式可以用于实现懒加载,安全访问控制,日志记录等功能。代理模式可以分为静态代理与动态代理。静态代理指的是代理类在编译时就已经确定,而动态代理指的是运行时动态的生成代理类,下面会详细的介绍这两种代理
二,静态代理模式
2.1,静态代理使用场景
2.1.1,缓存代理
缓存代理是一种特殊类型的代理模式,它可以为耗时的操作或者重复的请求提供缓存功能,从而提高程序的执行效率。缓存代理通常会在内部维护一个缓存数据结构,例如HashMap或者LinkedHashMap,用来存储已经处理过的请求以及结果。
缓存代理应用实例:
假设现在存在一个数据查询接口,它从数据库或者其他数据源中检索数据。在没有缓存代理的情况下,每次检索数据都需查询数据库,这样查询效率会比较低,并且也会占用更多的资源。此时可以通过缓存代理,将查询过的数据保存在内存中,从而避免重复查询数据库
定义一个查询接口:
//定义查询数据库的接口 public interface DataQuery { String qurey(String queryKey); }
定义具体数据查询类:
/* 定义一个组件类 这个类是用来具体查询数据库的 会实现查询接口 * */ public class DataQueryUtil implements DataQuery{ @Override public String qurey(String queryKey) { return "hello world";//这个按照具体要求会去查询数据库 } }
定义数据查询代理类:
import java.util.HashMap; import java.util.Map; /* * 定义一个代理类 主要用来实现缓存代理的实现 * */ public class DataQueryProxy implements DataQuery{ private final DataQueryUtil dataQueryUtil;//定义目标对象的引用 private final Map<String,String> cache; public DataQueryProxy(DataQueryUtil dataQueryUtil){ this.dataQueryUtil = dataQueryUtil;//在构造代理对象的时候初始化目标对象 this.cache = new HashMap<>();//构建缓存 } @Override public String qurey(String queryKey) { //在这里实现对于数据库查询操作的增强 //首先查询缓存 String res = cache.get(queryKey); if(res == null){ //如果缓存查询结果为空 就说明当前并无改查询的缓存记录 需要去查询数据库 res = dataQueryUtil.qurey(queryKey); //将数据库的查询结果加入缓存 cache.put(queryKey,res); System.out.println("result from database~"); }else{ //如果不是空就说明是在缓存中查询到了 直接返回结果 System.out.println("result from cache"); } return res; } }
定义主函数类,进行测试:
public class Main { public static void main(String[] args) { //通过代理类去进行数据查询 DataQueryUtil dataQueryUtil = new DataQueryUtil(); DataQueryProxy dataQueryProxy = new DataQueryProxy(dataQueryUtil); String queryKey = "key"; //此时是第一次查询 应该是会去查询数据库 String res = dataQueryProxy.qurey(queryKey); System.out.println("这是第一次查询的结果:" + res); //此时进行第二次查询 应该是走的缓存 res = dataQueryProxy.qurey(queryKey); System.out.println("这是第二次查询的结果:" + res); } }
最终测试结果输出:
最终结果与我们预期的一样,最开始应该是查询的数据库,第二次查询就应该走缓存了。这里的查询代理类就实现了我们的功能增强,也就是缓存代理
2.1.2,安全代理
安全代理主要是为了通过代理对象,来实现对于目标对象的访问的控制,通过安全代理,可以实现权限验证等安全相关功能。
比如现在需要去查询一个隐私数据,在查询之前可以通过代理对象来进行权限验证:
定义一个查询接口:
package securityProxy; //定义一个私密查询接口 public interface PersonalQuery { String queryData(String key,String username); }
定义一个查询实体类:
package securityProxy; //实际来查询数据的类 public class InfoQuery implements PersonalQuery{ @Override public String queryData(String key,String username) { return "hello world"; } }
定义一个代理类:
package securityProxy; import java.util.ArrayList; import java.util.List; public class InfoQueryProxy implements PersonalQuery{ private final InfoQuery infoQuery; private final List<String> userAuthorizationList ;//用户授权列表 public InfoQueryProxy(InfoQuery infoQuery) { this.infoQuery = infoQuery; userAuthorizationList = new ArrayList<>(); userAuthorizationList.add("zhangsan");//人为的添加授权列表 } @Override public String queryData(String key,String username) { //具体的代理逻辑 //首先验证用户是否具有权限 String ret = ""; if(userAuthorizationList.contains(username)){ //如果说包含该用户名 那么就具有权限去进行查询操作 ret = infoQuery.queryData(key,username); System.out.println("具有权限进行查询,结果如下:" + ret); }else{ //否则就是不具有查询权限 System.out.println("不具有权限进行查询"); } return ret; } }
主函数进行验证:
import cacheProxy.DataQueryProxy; import cacheProxy.DataQueryUtil; import securityProxy.InfoQuery; import securityProxy.InfoQueryProxy; public class Main { public static void main(String[] args) { InfoQuery infoQuery = new InfoQuery(); InfoQueryProxy infoQueryProxy = new InfoQueryProxy(infoQuery); String queryKey = "key"; String res1 = infoQueryProxy.queryData(queryKey,"zhangsan"); System.out.println(res1); String res2 = infoQueryProxy.queryData(queryKey,"lisi"); System.out.println(res2); } }
最终测试结果:
可以看到,当我们的用户名是lisi的时候,就查询失败,因为lisi不在我们的授权列表中
2.1.3,虚拟代理
虚拟代理主要用于实现懒加载,也就是延迟创建耗时或者资源密集型对象。虚拟代理在初始访问时才会创建对象,之后则可以直接使用该对象,也就是做到懒加载我们的密集型资源。
假设现在我们有一个图片资源,从网络加载图片,但是当前图片资源很大,所以我们希望当需要进行展示的时候才会去加载图片资源。
定义一个显示图片接口:
package ImageProxy; public interface Image { void display(); }
定义加载图片显示的实体类:
package ImageProxy; public class LargeImage implements Image{ public LargeImage(String url) { System.out.println("开始加载图片资源"); } @Override public void display() { System.out.println("进行图片资源展示"); } }
定义显示图片的代理类:
package ImageProxy; public class LargeImageProxy implements Image{ private String url; private LargeImage largeImage; public LargeImageProxy(String url) { this.url = url; } @Override public void display() { if(largeImage == null){ largeImage = new LargeImage(url);//创建具体的实体类,让其去加载 } largeImage.display(); } }
主函数:
import ImageProxy.LargeImage; import ImageProxy.LargeImageProxy; import cacheProxy.DataQueryProxy; import cacheProxy.DataQueryUtil; import securityProxy.InfoQuery; import securityProxy.InfoQueryProxy; public class Main { public static void main(String[] args) { LargeImageProxy largeImageProxy = new LargeImageProxy("xxxxx"); largeImageProxy.display(); } }
测试结果:
此时,只有当我们通过代理类调用display()方法时,才回去真正的创建LargeImage的实体类,进行图片的加载展示。
2.2,静态代理总结
总体而言,静态代理的核心就是定义一个公共的接口,然后我们的被代理类与代理类都需要实现该接口,重写对应的方法,然后在代理类中的方法中实现对被代理类的方法的增强。(面向接口编程,让两个对象之间关联起来)
当然,这是通过接口的形式实现静态代理,其实通过继承也是可以的,只不过就是代理类继承被代理类重写方法,然后去实现对于被代理类方法的增强,相对来说耦合度会更高,不够灵活。两种方式的关联方式不同,一个是通过接口,一个是通过继承的关系。
三,动态代理模式
Java中动态代理的实现主要有两种:一种是基于JDK的动态代理,另一种是基于CGLIB的动态代理。
动态代理相对于静态代理,动态代理是在运行的时候动态的去生成代理类,不需要人为的定义实现代理类,会更加方便灵活。但是因为动态代理的底层实现其实是基于反射机制的,所以在性能上会更差,开销会大一些。
3.1,JDK动态代理
对于JDK动态代理来说,我们重点关注两个点,一个是 java.lang.reflect.Proxy 类,一个是 InvocationHandler 接口
3.1.1,实例实现
还是以上面静态代理中的代理缓存的例子来说,我们现在将其改为JDK动态代理实现,如下:
定义一个查询接口:
package cacheProxy; //定义查询数据库的接口 public interface DataQuery { String qurey(String queryKey); }
定义实际的数据查询类:
package cacheProxy; /* 定义一个组件类 这个类是用来具体查询数据库的 会实现查询接口 * */ public class DataQueryUtil implements DataQuery{ @Override public String qurey(String queryKey) { return "hello world";//这个按照具体要求会去查询数据库 } }
定义拦截处理类,实现InvocationHandler接口:
package cacheProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; //定义具体的拦截实现规则 也就是拦截到方法后 具体要做些什么操作 public class MyInvocationHandler implements InvocationHandler { private final DataQueryUtil dataQueryUtil;//定义目标对象的引用 private final Map<String,String> cache; public MyInvocationHandler(DataQueryUtil dataQueryUtil) { this.dataQueryUtil = dataQueryUtil; this.cache = new HashMap<>(255);//构建缓存 } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String ret = null; if(method.getName().equals("qurey")){ //如果说调用的方法是query 则开始走校验逻辑 查询缓存或者是数据库 ret = cache.get(args[0].toString());//获取到方法的参数 从缓存中进行查询 if(ret == null){ //说明缓存中没有 需要查询数据库 就是调用目标对象的方法 ret = (String) method.invoke(dataQueryUtil,args); System.out.println("data from database~"); cache.put(args[0].toString(),ret); }else{ System.out.println("data from cache~"); } } return ret; } }
主函数:
import cacheProxy.DataQuery; import cacheProxy.DataQueryUtil; import cacheProxy.MyInvocationHandler; import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { DataQueryUtil dataQueryUtil = new DataQueryUtil(); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(dataQueryUtil); DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(dataQueryUtil.getClass().getClassLoader(),dataQueryUtil.getClass().getInterfaces(),myInvocationHandler); String ret1 = dataQuery.qurey("key"); System.out.println(ret1); String ret2 = dataQuery.qurey("key"); System.out.println(ret2); } }
测试运行结果:
3.1.2,过程解析
可以看到,此时我们使用JDK动态代理的时候,是不需要我们自己去定义代理类的,只需要指定好相关的参数即可:
可能看到这里,小伙伴们还是会不太明白到底是怎么实现代理的,几个方法的参数到底是什么意思,现在我会给大家慢慢解答~
首先,关注点在 java.lang.Proxy的newProxyInstance(ClassLoader loader , Class<?>[] interfaces , InvocationHandler h)上,如下:
- ClassLoader loader
类加载器,一般指定为目标对象的类加载器。因为代理类是在运行时动态生成的,所以说在编译时期是没有对应的字节码文件的,所以这里就需要我们指定好类加载器,确保我们动态生成的代理类可以被正确的加载和使用。我们的代理类是在Application ClassLoader下被加载的
- Class<?>[] interfaces
目标对象的接口列表,也就是指定好我们的代理类最终需要实现的接口,当然实现接口后重写的对应方法时不需要具体的实现,因为此处的方法相当于只是起了一个标记作用,最终调用处理还是在invoke()方法中。在JDK动态代理中,也是通过面向接口的形式关联代理类与被代理类
- InvocationHandler h
这个参数是最重要的,因为具体的代理增强逻辑都是在这个里面实现的,我们需要传入一个实现了InvocationHandler接口的类的实例,并且该实例中重写写invoke()方法。它的工作逻辑就是当我们在调用代理类中对应的方法的时候,JVM会将请求拦截并转发到invoke()方法中做统一的调度实现,在invoke()方法实现对于目标对象的增强,因为最终还是会在此invoke()方法中利用method.invoke()方法调用目标对象的方法,但是在前后可以实现例如日志管理,权限校验等功能的增强
另外,既然说到了InvocationHandler的invoke()方法,那么也是需要具体解释一下这个方法的~
此方法也含有三个参数,invoke( Object proxy , final Method method , Object[] args)
- Object proxy
proxy是代理对象,也就是动态生成的代理类的实例对象。可以通过proxy访问代理对象本身,可以在代理对象上执行一些特定的操作,如获取代理对象的信息等
- final Method method
Method 对象是一个用来表示 Java 语言中的一个方法的类。它提供了获取方法信息(如方法名、返回值、参数类型等)和调用方法的能力。当我们使用反射来调用某个方法时,首先需要获取该方法对应的 Method 对象。可以通过 Class 对象的 getMethod() 或 getDeclaredMethod() 方法来获取一个 Method 对象。这里的method对象主要是用来调用我们的目标对象的方法的,通过method.invoke()方法
invoke(Object obj, Object… args),该方法也是两个参数,obj是我们的目标对象的实例,因为最终执行体还是我们的目标对象的方法,args也就是之前说的参数列表,用于给方法调用提供参数
- Object[] args
参数列表,是一个Object类型数组,其中包含着我们目标对象方法调用所需要的参数
3.2,CGLIB动态代理
CGLIB是通过继承的方式来做到对于被代理类的增强,它会在运行时动态的构造出一个子类来扩展目标对象的功能。这里我们主要关注两个点,一个是Enhancer类,一个是MethodInterceptor接口
3.2.1,实例实现
还是是缓存代理的例子来进行实现,此时通过CGLIB实现代理,如下:
引入CGLIB的依赖:
因为CGLIB它相当于是一个第三方的库,所以是需要我们手动去引入一下依赖才能进行使用的
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.12</version> </dependency> </dependencies>
定义查询接口:
package cacheProxy; //定义查询数据库的接口 public interface DataQuery { String qurey(String queryKey); }
定义查询实体类:
package cacheProxy; /* 定义一个组件类 这个类是用来具体查询数据库的 会实现查询接口 * */ public class DataQueryUtil implements DataQuery{ @Override public String qurey(String queryKey) { return "hello world";//这个按照具体要求会去查询数据库 } }
定义MyInterceptor接口实现类:
package cacheProxy; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class MyMethodInterceptor implements MethodInterceptor { private final Map<String,String> cache; public MyMethodInterceptor() { this.cache = new HashMap<>(255); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { String ret = null; if(method.getName().equals("qurey")){ //如果说调用的方法是query 则开始走校验逻辑 查询缓存或者是数据库 ret = cache.get(objects[0].toString());//获取到方法的参数 从缓存中进行查询 if(ret == null){ //说明缓存中没有 需要查询数据库 就是调用目标对象的方法 ret = (String) methodProxy.invokeSuper(o,objects); System.out.println("data from database~"); cache.put(objects[0].toString(),ret); }else{ System.out.println("data from cache~"); } } return ret; } }
定义主函数:
import cacheProxy.*; import net.sf.cglib.proxy.Enhancer; public class Main { public static void main(String[] args) { MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor(); Enhancer enhancer = new Enhancer();//定义Enhancer实例 enhancer.setClassLoader(DataQueryUtil.class.getClassLoader());//设置类加载器 enhancer.setSuperclass(DataQueryUtil.class);//设置被代理类 enhancer.setCallback(myMethodInterceptor);//设置方法拦截器 也即是实现MethodInterceptor接口的类的实例 DataQueryUtil dataQueryUtil1 = (DataQueryUtil) enhancer.create();//创建代理类 代理类是目标类的子类 String ret1 = dataQueryUtil1.qurey("key"); System.out.println(ret1); String ret2 = dataQueryUtil1.qurey("key"); System.out.println(ret2); } }
测试运行结果:
3.2.2,过程解析
其实相对于JDK动态代理而言,CGLIB的实现过程其实差异并不是很大,只是这里是通过继承来是实现的增强代理,MethodInterceptor接口就相当于之前的InvocationHandler接口,interceptor()就相当于invoke()。
首先,定义拦截处理规则,具体是冲喜intercceptor方法,具体参数如 intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
- Object o
o就是我们的目标对象,即被代理对象,后面具体调用的还是该对象的对应方法
- Method method
method对象的作用和前面JDK动态代理的作用一样,主要是用来获取方法相关的信息
- Object[] objects
调用方法所需要的参数,这是一个参数数组
- MethodProxy methodProxy
这是CGLIB提供的一个专门用来调用被代理类原始方法的类,我们可以通过它提供的invokeSuper()方法来调用我们目标对象的方法,该方法有两个参数,一个是目标对象的实例,也就是 o ,然后再就是对应的参数 objects
然后通过Enhancer来生成代理类,步骤如下:
定义Enhancer实例
Enhancer enhancer = new Enhancer();//定义Enhancer实例
定义MethodInterceptor实例
MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
设置类加载器
enhancer.setClassLoader(DataQueryUtil.class.getClassLoader());//设置类加载器
设置父类,也就是被代理类
enhancer.setSuperclass(DataQueryUtil.class);//设置被代理类
设置方法拦截器
enhancer.setCallback(myMethodInterceptor);//设置方法拦截器 也即是实现MethodInterceptor接口的类的实例
创建代理类
DataQueryUtil dataQueryUtil1 = (DataQueryUtil) enhancer.create();//创建代理类 代理类是目标类的子类
调用方法
dataQueryUtil1.qurey("key")
3.3,JDK动态代理 vs CGLIB
- 应用场景不同
JDK动态代理是通过面向接口编程的方式来实现代理的,所以这就要求被代理类至少是实现了一个接口的。但是CGLIB是可以适用于实现接口的,或者是没有实现接口的类都可以进行代理,应用面更广一些
- 代理方式不同
JDK是通过反射来动态的生成代理类,代理类与被代理类之间的关联方式是通过实现相同的接口。CGLIB的代理类也是反射生成的,只不过被代理类与代理类之间是是一个父子类的关系,也就是通过继承的手法使得代理类与被代理类关联起来
既然CGLIB是继承的手法来实现代理,所以是不可以对private,final,static修饰的方法进行代理的
- 效率不同
在大部分情况下,JDK动态代理的效率是会更高的,CGLIB生成的代理类更重量,并且随着JDK的发展,效率是会越来越好的。总体而言,如果被代理类存在接口,那么就使用JDK动态代理,不存在接口就使用CGLIB代理,这也是Spring AOP采用的方式
好久没更新博客了,还是老样子,有错误大家多多指正,后面会持续的进行更新了,主要是框架,设计模式,算法相关的了,大家一起加油,秋招冲冲冲!!!