代理模式是一种结构性设计模式,让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
我们有一个常用的数据库访问接口,大量的客户端都是对数据库进行直接的访问,对系统资源的消耗特别大,并且有很多的重复查询操作。
直接访问数据库,可能会非常的慢
这时候我们考虑加入缓存,当需要重复的查询时直接从缓存中获取数据返回到客户端,节省系统开销,并记录一下每一个客户端访问花费的时间。
代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。 代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。
代理将自己伪装成数据库对象,可以在客户端不知道的情况下做缓存查询操作并记录其访问时间或日志
定义查询数据库的接口
java
public interface DataService { // 通过ID查询数据 String getById(Integer id);}具体的数据库查询业务类
java
public class DataServiceImpl implements DataService{ // 模拟数据 final Map dataMap = new HashMap(){{ for (int i = 0; i < 10; i++) { put(i,"data_"+ i); } }}; @Override public String getById(Integer id) { // 模拟数据库查询的耗时 try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } return dataMap.get(id); }} 创建代理类,伪装业务类
java
public class DataServiceProxy implements DataService{ DataService dataService; // 缓存 Map cacheMap = new HashMap<>(); public DataServiceProxy(DataService dataService) { this.dataService = dataService; } @Override public String getById(Integer id) { // 记录访问的开始时间 final long start = System.currentTimeMillis(); String result = null; // 优先从缓存获取 String cache = getCache(id); if (cache == null){ result = dataService.getById(id); // 放入缓存中 putCache(id,result); }else { result = cache; } final long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start) + "ms"); return result; } // 缓存信息 private void putCache(Integer id,String value){ cacheMap.put(id,value); } // 获取缓存信息 private String getCache(Integer id){ return cacheMap.get(id); } } 客户端
java
@Testpublic void ProxyTest() { DataService dataService = new DataServiceImpl(); DataServiceProxy dataServiceProxy = new DataServiceProxy(dataService); dataServiceProxy.getById(1); // 第二次查询 dataServiceProxy.getById(1); dataServiceProxy.getById(1);}这种代理模式的设计方式,我们一般称之为静态代理:由编码人员创建完成或由特定工具生成源代码,在编译时就已经将接口、被代理类、代理类等确定类下来,在程序运行之前,代理类的字节码文件已经生成了。如果有其他的代理内容,可能需要新建很多的代码来实现。
与静态代理最大的区别在于,动态代理类是在程序运行时创建的代理。例如在上面的例子中DataServiceProxy代理类是我们自己定义的,在程序运行之前就已经编译完成。在动态代理中,代理类不是在代码中定义,而是在程序运行时根据我们的需要在Java代码中动态生成的。
在Java中我们提到动态代理,一般绕不开JDK动态代理和CGLIB动态代理。
利用JDK自带的代理类来完成,相当于利用一个拦截器(需实现接口InvocationHanlder)配合反射机制生成一个实现代理类的匿名接口,在调用具体的方法前调用InvocationHanlder来处理。
我们依旧使用DataService接口和DataServiceImpl业务类来完成一个动态代理的案例。
创建被代理类的接口和业务类(已经有了)创建InvocationHanlder接口的实现类,在invoke方法中实现代理的逻辑通过Proxy的静态方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)创建一个代理对象。java
public class JDKProxy implements InvocationHandler { // 被代理对象 private Object object; // 缓存 Map cacheMap = new HashMap<>(); public JDKProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 只代理其中的查询方法 if (method.getName().equals("getById")){ // 参数 Integer id = (Integer) args[0]; // 记录访问的开始时间 final long start = System.currentTimeMillis(); String result = null; // 优先从缓存获取 String cache = getCache(id); if (cache == null){ // 代理执行 result =(String) method.invoke(object,args); // 放入缓存中 putCache(id,result); }else { result = cache; } final long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start) + "ms"); return result; }else { return method.invoke(object,args); } } // 缓存信息 private void putCache(Integer id,String value){ cacheMap.put(id,value); } // 获取缓存信息 private String getCache(Integer id){ return cacheMap.get(id); }} InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法(Method)时,方法调用被编码分派到调用处理程序的invoke方法。
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:
java
/*** proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0(按次序进行,每生成一个 +1)* method:我们所要调用某个对象真实的方法的Method对象* args:指代代理对象方法传递的参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;客户端在调用时的方式也和静态代理不一样,最终是使用代理类$Proxy来进行方法的调用
java
@Testpublic void JDKProxyTest() { DataService dataService = new DataServiceImpl(); JDKProxy jdkProxy = new JDKProxy(dataService); // 获取代理对象 DataService dataServiceProxy = (DataService) Proxy.newProxyInstance(DataService.class.getClassLoader(), new Class[]{DataService.class}, jdkProxy); dataServiceProxy.getById(1); dataServiceProxy.getById(1);}其运行的结果是一样的,都完成了代理内容。
Proxy类就是用来创建一个代理对象的类,它提供了很多方法,我们最常用的是newProxyInstance方法。
java
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)newProxyInstance就是创建一个代理类对象,它接收三个参数:
loader:指定代理类的类加载器(我们传入当前测试类的类加载器)interfaces:一个interface对象数组,代理类需要实现的接口(我们传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口)h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,用来处理方法的调用。这里传入我们自己实现的handler利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
导入cglib-xxx.jar包,这里包含了asm和cglib创建MethodInterceptor接口的实现类,在intercept方法中实现代理的逻辑编写getCglibProxy方法(自定义)返回代理类对象xml
cglib cglib 3.3.0 java
public class CglibProxy implements MethodInterceptor { // 被代理对象,便于通用,可以写成Object private Object object; // 缓存 Map cacheMap = new HashMap<>(); @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 只代理其中的查询方法 if (method.getName().equals("getById")) { // 参数 Integer id = (Integer) args[0]; // 记录访问的开始时间 final long start = System.currentTimeMillis(); String result = null; // 优先从缓存获取 String cache = getCache(id); if (cache == null) { result = (String)method.invoke(object,args); // 放入缓存中 putCache(id, result); } else { result = cache; } final long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start) + "ms"); return result; } else { return method.invoke(object, args); } } // 获取代理对象 这里采用了范型的写法,更直观的传入被代理类,然后返回代理对象 public T getCglibProxy(T t){ this.object = t;//为目标对象target赋值 Enhancer enhancer = new Enhancer(); //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类 enhancer.setSuperclass(object.getClass()); //设置回调 enhancer.setCallback(this); //创建并返回代理对象 Object result = enhancer.create(); return (T) result; } // 缓存信息 private void putCache(Integer id,String value){ cacheMap.put(id,value); } // 获取缓存信息 private String getCache(Integer id){ return cacheMap.get(id); }} java
@Testpublic void CGLBProxyTest(){ // 被代理类 这里可以不用接口声明哦 DataService dataService = new DataServiceImpl(); CglibProxy cglibProxy = new CglibProxy(); // 获取代理对象 DataService proxy = cglibProxy.getCglibProxy(dataService); proxy.getById(1); proxy.getById(1);}可以发现两种动态代理的写法基本差不多,基本的思路都是生成代理类,拦截,反射,获取真正的代理类方法,执行。那么两种方式有什么区别和用法呢?
版权声明:我们致力于保护作者版权,注重分享,被刊用文章【java代理(设计模式)】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理!;
工作时间:8:00-18:00
客服电话
电子邮件
beimuxi@protonmail.com
扫码二维码
获取最新动态
