当前位置: 首页 > news >正文

设计模式 三、结构型设计模式

一、代理模式

        代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供了一个代理,以控制对这个对象的访问。 代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。简单来说,代理模式 就是通过代理对象来控制对实际对象的访问,代理对象在客户端和目标对象之间起到了中介的作用。
        在设计模式中,代理模式可以分为静态代理和动态代理。静态代理是指代理类在编译时就已经确定,而动态代理是指代理类在运行时动态生成。

        

        1、静态代理

        静态代理是在代码编译阶段就已经生成了代理类,代理类需要实现目标对象相同的接口。
        优点:可以在不修改目标对象的前提下对目标对象进行增强。
        缺点:需要为每个目标创建一个代理类,导致系统中类的数量增加,维护成本较高。

        2、静态代理的使用场景

        2.1 缓存代理

        缓存代理是一种特殊类型的的代理模式,它可以为耗时的操作或者重复的请求提供缓存功能,从而提高程序的执行效率。缓存代理通常会在内部维护一个缓存的数据结构。如HashMap 或者 LinkHashMap,用来 存储已经处理过的请求及其结果。

        以下是一个示例:

        假设有一个数据查询接口,它从数据库或者其他数据源中检索数据,在没有缓存代理的情况下,每次查询都需要访问数据库,这可能会导致较高的资源消耗和延迟,通过引入缓存代理,我们可以将查询结果存储在内存中,从而避免重复查询数据库。

        首先定义一个数据查询接口:

public interface DataQuery {String query(String queryKey);
}

        然后,实现一个真实的数据查询类:

public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 查询数据库并返回结果return "Result from database: " + queryKey;}
}

        然后创建一个缓存代理类,它实现DataQuery接口,并在内部使用HashMap作为缓存:

public class CachingDataQueryProxy implements DataQuery {private final DataQuery realDataQuery;private final Map<String, String> cache;public CachingDataQueryProxy(DataQuery realDataQuery) {this.realDataQuery = new DatabaseDataQuery();cache = new HashMap<>();}@Overridepublic String query(String queryKey) {String result = cache.get(queryKey);if (result == null) {result = realDataQuery.query(queryKey);cache.put(queryKey, result);System.out.println("Result retrieved from database and added to cache.");} else {System.out.println("Result retrieved from cache.");}return result;}
}

        最后,我们在客户端就可以使用缓存代理:

public class Client {public static void main(String[] args) {DataQuery realDataQuery = new DatabaseDataQuery();DataQuery cachingDataQueryProxy = newCachingDataQueryProxy(realDataQuery);String queryKey = "example_key";// 第一次查询,从数据库中获取数据并将其缓存System.out.println(cachingDataQueryProxy.query(queryKey));// 第二次查询相同的数据,从缓存中获取System.out.println(cachingDataQueryProxy.query(queryKey));}
}

        其实也就是说我们单独实现了一个方法,我们在这个方法中取调用原本的查询操作,也就是在这个新的方法中完成了过滤的操作。

        2.2 安全代理

        (Security Proxy)是一种代理模式用的应用,它用于控制对真实主题对象的访问。通过安全代理,可以实现访问控制、权限控制等安全相关功能。
       
        以下是一个简单的安全代理示例:
       
        假设我们有一个敏感数据查询接口,只有具有特定权限的用户才能访问:
        首先,我们定义一个数据查询接口:

public interface SensitiveDataQuery {String queryData(String userId);
}

        接着实现一个真的敏感数据查询类:

public class SensitiveDataQueryImpl implements SensitiveDataQuery {@Overridepublic String queryData(String userId) {// 查询敏感数据并返回结果return "Sensitive data for user: " + userId;}
}

        然后,我们创建一个安全代理类,它实现了 SensitiveDataQuery接口,并在内部进行权限验证:

public class SecurityProxy implements SensitiveDataQuery {private final SensitiveDataQuery sensitiveDataQuery;private final UserAuthenticator userAuthenticator;public SecurityProxy(SensitiveDataQuery sensitiveDataQuery,UserAuthenticator userAuthenticator) {this.sensitiveDataQuery = sensitiveDataQuery;this.userAuthenticator = userAuthenticator;}@Overridepublic String queryData(String userId) {if (userAuthenticator.hasPermission(userId)) {return sensitiveDataQuery.queryData(userId);} else {return "Access Denied: Insufficient permission for user" + userId;}}
}

        我们使用一个UserAuthenticator类来模拟用户权限验证:

public class UserAuthenticator {private final List<String> authorizedUserIds;public UserAuthenticator() {// 模拟从数据库或配置文件中获取已授权的用户列表authorizedUserIds = Arrays.asList("user1", "user2", "user3");}public boolean hasPermission(String userId) {return authorizedUserIds.contains(userId);}
}

        在客户端中调用:

public class Client {public static void main(String[] args) {SensitiveDataQuery sensitiveDataQuery = new SensitiveDataQueryImpl();UserAuthenticator userAuthenticator = new UserAuthenticator();SensitiveDataQuery securityProxy = new SecurityProxy(sensitiveDataQuery,userAuthenticator);String userId1 = "user1";String userId2 = "user4";// 用户1具有访问权限System.out.println(securityProxy.queryData(userId1));// 用户4没有访问权限System.out.println(securityProxy.queryData(userId2));}
}

        2.3 虚拟代理

        (Virtual Proxy)是一种代理模式,用于在需要时延迟创建耗时或资源密集型对象。虚拟代理在初始访问时才创建实际对象,之后将直接使用该对象。这可以避免在实际对象尚未使用的情况下就创建它,从而节省资源。

        例如:我们有一个大型图片类,他从网络加载图像。由于图像可能非常大。我们希望在需要显示时才加载他,为了实现这一点,我们可以创建一个虚拟代理来代表大型图片类,首先定义一个图片接口:

public interface Image {void display();
}

        然后实现了一个大型图片类,他从网络加载图像并实现display() 方法:

public class LargeImage implements Image {private final String imageUrl;public LargeImage(String imageUrl) {this.imageUrl = imageUrl;loadImageFromNetwork();}private void loadImageFromNetwork() {System.out.println("Loading image from network: " + imageUrl);// 真实的图像加载逻辑...}@Overridepublic void display() {System.out.println("Displaying image: " + imageUrl);}
}

         然后创建一个虚拟代理类,它实现了Image接口,并在内部使用LargeImage:

public class VirtualImageProxy implements Image {private final String imageUrl;private LargeImage largeImage;public VirtualImageProxy(String imageUrl) {this.imageUrl = imageUrl;}@Overridepublic void display() {if (largeImage == null) {largeImage = new LargeImage(imageUrl);}largeImage.display();}
}

        最后我们在客户端中使用虚拟代理:

public class Client {public static void main(String[] args) {Image virtualImageProxy = newVirtualImageProxy("https://example.com/large-image.jpg");System.out.println("Image will not be loaded until it is displayed.");// 调用 display() 方法时,才会创建并加载大型图片virtualImageProxy.display();}
}

        这个例子就是通过虚拟代理实现懒加载,以减少资源消耗和提高程序性能,当实际对象的创建和初始化非常耗时或占用大量资源时,虚拟代理是一个很好的选择。

        2.4 远程代理

        (Remote Proxy)是一种代理模式,用于访问位于不用的地址空间的对象。远程代理可以为本地对象提供与远程对象相同的接口,使得客户端可以透明的访问远程对象。通常,远程代理需要处理网络通信、序列化和反序列化等细节。以后做rpc时也会使用。
        
        简单的示例:首先定义一个服务接口:

public interface RemoteService {String fetchData(String dataId);
}

        然后实现一个远程服务类,在服务端运行并实现fetchData()方法:

public class RemoteServiceImpl implements RemoteService {@Overridepublic String fetchData(String dataId) {// 实际操作,例如从数据库获取数据return "Data from remote service: " + dataId;}
}

·        接下来,我们创建了一个远程代理类,它实现了RemoteService接口,并在内部处理网络通信等细节:

public class RemoteServiceProxy implements RemoteService {private final String remoteServiceUrl;private RemoteService remoteService;public RemoteServiceProxy(String remoteServiceUrl) {this.remoteServiceUrl = remoteServiceUrl;this.remoteService = new RemoteService();}@Overridepublic String fetchData(String dataId) {// 网络通信、序列化和反序列化等逻辑System.out.println("Connecting to remote service at: " + remoteServiceUrl);// 假设我们已经获取到远程服务的数据String result = remoteService.fetchData(dataId);System.out.println("Received data from remote service.");return result;}
}

        

        2.5 静态代理步骤总结

        通过前面几个案例,大致了解了静态代理的使用方式,其大致流程如下:
        1.创建一个接口,定义代理类和被代理类共同实现的方法。
        2.创建被代理类,实现这个接口,并且在其中定义实现方法。
        3.创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量。
        4.在代理类中实现接口中的方法,方法中调用被代理类中的对应方法。
        5.通过创建代理对象,并调用其方法,方法增强。

        这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。

        在静态代理中,也可以使用继承来实现代理,具体步骤如下:
        1.创建被代理类,定于需要被代理的方法。
        2.创建代理类,继承被代理类,重写被代理类中的方法,对方法进行增强。
        3.再重写的方法中添加代理逻辑,例如在调用被代理类中的方法前后添加日志记录、安全检查等功能。
        4.在使用代理类时,创建代理对象,调用重写方法。
        这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。使用继承来实现代理类的好处就是简单易懂,不需要创建接口,同时继承可以继承被代理类的属性和方法,可以更方便的访问类中的成员。但是这个方法也有一些缺点,类如代理类与被代理类的耦合度较高,不够灵活。

        3、动态代理

        java中动态代理的实现方式主要有两种:基于JDK的动态代理和基于CGLB的动态代理。
       
        动态代理是指在程序运行时动态生成代理类,无需手动编写代理类,大大降低了代码的复杂度,动态代理一般使用Java提供的反射机制实现,可以对任意实现了接口的类进行代理。动态代理的优点是灵活性高,可以根据需要动态生成代理类,缺点是性能相对较低,由于使用反射机制,在运行时会产生额外的开销。
        静态代理 和 动态代理都是代理模式的实现方式,其主要区别在于代理类的生成时机和方式。静态代理需要手动编写代理类,适用于代理类数量较少,不需要频繁修改的场景。而动态代理不需要手动编写代理类,可以动态的生成代理类,适用于代理类数量较多、需要频繁修改的场景。

       

        4.1 基于JDK的动态代理实现步骤 

        基于JDK的动态代理需要使用 java.lang.reflect.Proxy 类 和 Java.lang.reflect.InvocationHandler 接口。我们依旧使用上述的缓存代理的案例来实现,具体步骤如下:
       
        1)定义一个接口,声明需要代理的方法:

public interface DataQuery {String query(String queryKey);String queryAll(String queryKey);
}

        2)创建一个被代理类,实现这个接口,并在其中定义实现方法:

public class DatabaseDataQuery implements DataQuery {@Overridepublic String query(String queryKey) {// 他会使用数据源从数据库查询数据很慢System.out.println("正在从数据库查询数据");return "result";}@Overridepublic String queryAll(String queryKey) {// 他会使用数据源从数据库查询数据很慢System.out.println("正在从数据库查询数据");return "all result";}
}

        3)创建一个代理类,实现了InvocationHandler接口,并在其中定义一个被代理类的对象作为属性。

public class CacheInvocationHandler implements InvocationHandler {private HashMap<String,String> cache = new LinkedHashMap<>(256);private DataQuery databaseDataQuery;public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {this.databaseDataQuery = databaseDataQuery;}public CacheInvocationHandler() {this.databaseDataQuery = new DatabaseDataQuery();}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1、判断是哪一个方法String result = null;if("query".equals(method.getName())){// 2、查询缓存,命中直接返回result = cache.get(args[0].toString());if(result != null){System.out.println("数据从缓存重获取。");return result;}// 3、未命中,查数据库(需要代理实例)result = (String) method.invoke(databaseDataQuery, args);// 4、如果查询到了,进行呢缓存cache.put(args[0].toString(),result);return result;}// 当其他的方法被调用,不希望被干预,直接调用原生的方法return method.invoke(databaseDataQuery,args);}
}

        在代理类中,我们实现了InvocationHandler接口,并在其中定义了一个被代理类的对象作为属性。在invoke方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。

        4)使用代理类,创建被代理类的对象和代理类的对象,并使用Proxy.newProxyInstance方法生成代理对象。

public class Main {public static void main(String[] args) {// jdk提供的代理实现,主要是使用Proxy类来完成// 1、classLoader:被代理类的类加载器ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 2、代理类需要实现的接口数组Class[] interfaces = new Class[]{DataQuery.class};// 3、InvocationHandlerInvocationHandler invocationHandler = new CacheInvocationHandler();DataQuery dataQuery = (DataQuery)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);// 事实上调用query方法的使用,他是调用了invokeString result = dataQuery.query("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.query("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.query("key2");System.out.println(result);System.out.println("++++++++++++++++++++++++++++++++++++");// 事实上调用queryAll方法的使用,他是调用了invokeresult = dataQuery.queryAll("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.queryAll("key1");System.out.println(result);System.out.println("--------------------");result = dataQuery.queryAll("key2");System.out.println(result);System.out.println("--------------------");}
}


        再看一下生成日志的例子:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;interface UserService {void saveUser(String username);
}class UserServiceImpl implements UserService {public void saveUser(String username) {System.out.println("Saving user: " + username);}
}class LogProxy implements InvocationHandler {private Object target;public LogProxy(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method: " + method.getName());Object result = method.invoke(target, args);System.out.println("After method: " + method.getName());return result;}
}public class DynamicProxyExample {public static void main(String[] args) {UserService userService = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),new LogProxy(userService));proxy.saveUser("Alice");}
}

        其实主要的步骤就是要编写我们的动态代理类,并在invoke 方法中书写自己需要添加的逻辑,Proxy.newProxyInstance 方法生成代理对象,这一步很多人看不懂,其实不需要担心,因为这个其实就是一个固定的api知道怎么调用即可!

        4.2 基于CGLIB的动态代理实现步骤

        基于CGLB的动态代理需要使用 net.sf.cglib.proxy.Enhancer类 和 net.sf.cglib.proxy.MethodInterceptor 接口 ,具体步骤如下:

        1)创建一个被代理类,定义需要被代理的方法:

public class DatabaseDataQuery {public String query(String queryKey) {// 他会使用数据源从数据库查询数据很慢System.out.println("正在从数据库查询数据");return "result";}public String queryAll(String queryKey) {// 他会使用数据源从数据库查询数据很慢System.out.println("正在从数据库查询数据");return "all result";}
}

        2)创建一个方法拦截器类,实现MethodInterceptor接口,并在其中定义一个被代理类的对象作为属性。

public class CacheMethodInterceptor implements MethodInterceptor {private HashMap<String,String> cache = new LinkedHashMap<>(256);private DatabaseDataQuery databaseDataQuery;public CacheMethodInterceptor() {this.databaseDataQuery = new DatabaseDataQuery();}@Overridepublic Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {// 1、判断是哪一个方法String result = null;if("query".equals(method.getName())){// 2、查询缓存,命中直接返回result = cache.get(args[0].toString());if(result != null){System.out.println("数据从缓存重获取。");return result;}// 3、未命中,查数据库(需要代理实例)result = (String) method.invoke(databaseDataQuery, args);// 4、如果查询到了,进行呢缓存cache.put(args[0].toString(),result);return result;}return method.invoke(databaseDataQuery,args);}
}

        在这个代理类中,我们实现了MethodInterceptor 接口,并在其中定义了一个被代理类的对象作为属性,在intercept方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。

        3)在使用代理类时,创建被代理类的对象和代理类的对象,并使用Enhancer.create方法生成代理对象。

public class Main {public static void main(String[] args) {// cglib通过EnhancerEnhancer enhancer = new Enhancer();// 设置他的父类enhancer.setSuperclass(DatabaseDataQuery.class);// 设置一个方法拦截器,用来拦截方法enhancer.setCallback(new CacheMethodInterceptor());// 创建代理类DatabaseDataQuery databaseDataQuery =(DatabaseDataQuery)enhancer.create();databaseDataQuery.query("key1");databaseDataQuery.query("key1");databaseDataQuery.query("key2");}
}

       实现打印日志的例子:

class UserServiceImpl {public void saveUser(String username) {System.out.println("Saving user: " + username);}
}class LogInterceptor implements MethodInterceptor {public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method: " + method.getName());Object result = proxy.invokeSuper(obj, args);System.out.println("After method: " + method.getName());return result;}
}public class CglibProxyExample {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserServiceImpl.class);enhancer.setCallback(new LogInterceptor());UserServiceImpl proxy = (UserServiceImpl) enhancer.create();proxy.saveUser("Bob");}
}

        在这个示例中,我们使用 Enhancer.create 方法生成代理对象,并将代理对象转成RealSubject类型,以便调用request方法,在代理对象调用request方法时,会调用DynamicProxy类中的intercept方法,实现对被代理对象的增强。
        在实际应用中,基于CGLIB的动态代理可以代理任意类,但是生成的代理类比较重量级。如果被代理类是一个接口,建议使用基于JDK的动态代理来实现,这也是spring的做法;如果被代理类没有实现接口或者需要代理的方法时final方法,建议使用基于CGLIB的动态代理来实现。

        4.3 spring aop 与 动态代理之间的关系

        spring aop 即面向切面编程,他对aop进行了封装,使用面向对象的思想来实现,所以aop的底层使用动态代理来实现的。

        4、动态代理的应用场景


http://www.mrgr.cn/news/96723.html

相关文章:

  • CMD(命令提示符)、PowerShell 和 Windows Terminal
  • C++练习3
  • ZGC 参数优化与 GC 触发机制解析分享
  • cpu下安装MinerU进行数据清洗
  • Linux centos 7 常用服务器搭建
  • 解决 CMS Old GC 频繁触发线上问题技术方案
  • Spring Boot向Vue发送消息通过WebSocket实现通信
  • 初学STM32系统时钟设置
  • 【SpringBoot + MyBatis + MySQL + Thymeleaf 的使用】
  • Linux基础入门指南:用户管理、基本指令(一)
  • QT 非空指针 软件奔溃
  • RAG优化:python从零实现Proposition Chunking[命题分块]让 RAG不再“断章取义”,从此“言之有物”!
  • SpringIoC和DI
  • Sink Token
  • Day3 蓝桥杯省赛冲刺精炼刷题 —— 排序算法与贪心思维
  • Redis 6.2.6 生产环境单机配置详解redis.conf
  • 深入解析拓扑排序:算法与实现细节
  • 【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
  • nodejs:midi-writer-js 将基金净值数据转换为 midi 文件
  • 如何本地部署RWKV-Runner尝鲜CPU版