本文转载自微信公众号《JavaKeeper》,作者海星。转载本文请联系JavaKeeper公众号。基本介绍代理模式是一种结构化设计模式。为对象提供一个替代对象来控制对该对象的访问。也就是说,目标对象是通过代理对象访问的,并允许在请求提交给对象之前和之后进行一些处理。代理对象可以是远程对象、创建成本高昂的对象或需要安全控制的对象。代理模式主要有三种不同的形式:静态代理:代理类由程序员创建或特定工具自动生成源代码,然后进行编译。代理类的.class文件在程序运行前已经存在。动态代理(JDK代理、接口代理):是在程序运行时利用反射机制动态创建的。动态是程序运行时产生的,不是编译时产生的。cglib代理(它可以在内存中动态创建对象,而不是实现接口,属于动态代理的范畴)为什么要控制对一个对象的访问?例如:有这么一个非常消耗系统资源的巨大对象,你只是偶尔需要,而不是一直需要。图:refactoringguru.cn可以实现惰性初始化:在真正需要的时候创建对象。该对象的所有客户端都执行惰性初始化代码。不幸的是,这很可能导致大量重复代码。理想情况下,我们希望将代码直接放入对象的类中,但这并不总是可能的:例如,该类可能是第三方封闭库的一部分。解决方案代理模式建议创建一个与原始服务对象具有相同接口的新代理类,然后更新应用程序以将代理对象传递给所有原始对象客户端。代理类收到客户端请求后,会创建实际的服务对象,并将所有工作委托给它。图:refactoringguru.cn代理将自己伪装成数据库对象,可以在客户端或实际数据库对象不知情的情况下处理惰性初始化和缓存查询结果。这有什么好处?如果你需要在类的主要业务逻辑之前或之后执行一些工作,你可以在不修改类的情况下完成。由于代理实现与原始类相同的接口,您可以将它传递给任何使用实际服务对象的客户端。代理模式结构图:refactoringguru.cn服务接口(ServiceInterface)声明了服务接口。代理必须符合此接口才能伪装成服务对象。Service类提供了一些有用的业务逻辑。Proxy类包含一个指向服务对象的引用成员变量。代理完成其任务(如惰性初始化、日志记录、访问控制和缓存等)后,它会将请求传递给服务对象。通常,代理管理它所服务的对象的整个生命周期。客户端(Client)可以通过同一个接口与服务或代理交互,所以你可以在任何需要服务对象的代码中使用代理。玩游戏有代练,买卖房子有中介。比如,普通公司也可以找代理公司投资网络广告。这里的代练、中介、广告代理的角色都是代理人。这里举一个更贴近程序员的例子。比如有些变态的公司不让我们在公司里刷微博,看视频。我们可以通过一层代理来限制我们对这些网站的访问。事不宜迟,让我们从静态代理开始。静态代理1.定义网络接口publicinterfaceInternet{voidconnectTo(StringserverHost)throwsException;}2.真实网络连接publicclassRealInternetimplementsInternet{@OverridepublicvoidconnectTo(StringserverHost)throwsException{System.out.println("Connectingto"+serverHost);}}3。公司的NetworkproxypublicclassProxyInternetimplementsInternet{//目标对象,通过接口聚合privateInternetinternet;//通过构造方法publicProxyInternet(Internetinternet){this.internet=internet;}//网络黑名单privatestaticListbannedSites;static{bannedSites=newArrayList();bannedSites.add("bilibili.com");bannedSites.add("youtube.com");bannedSites.add("weibo.com");bannedSites.add("qq.com");}@OverridepublicvoidconnectTo(Stringserverhost)throwsException{//添加限制函数if(bannedSites.contains(serverhost.toLowerCase())){thrownewException("AccessDenied:"+serverhost);}internet.connectTo(serverhost);}}4.客户端认证publicclassClient{publicstaticvoidmain(String[]args){Internetinternet=newProxyInternet(newRealInternet());try{internet.connectTo("so.com");internet.connectTo("qq.com");}catch(Exceptione){System.out.println(e.getMessage());}}}5.输出Connectingtoso.comAccessDenied:qq.com不能访问娱乐网站,但是可以用360搜索,SO靠谱,哈哈静态代理类的优缺点优点:不用修改target对象,可以通过代理对象扩展代理到目标对象,这样客户端就不需要知道实现类是什么,是怎么做的,客户端只需要知道代理即可(解耦)。对于上面的客户端代码,RealInterner()可以应用工厂隐藏它的缺点:代理类和委托类实现相同的接口,代理类通过委托类实现相同的方法。这会产生大量代码重复。如果接口增加了一个方法,除了所有的实现类都需要实现这个方法外,所有的代理类也需要实现这个方法。增加了代码维护的复杂性。代理对象只服务于一种类型的对象,如果它想服务于多种类型的对象的话。需要对每个对象进行代理,程序规模稍大时静态代理无法胜任。动态代理静态代理会生成很多静态类,所以我们不得不想办法通过一个代理类来完成所有的代理功能,这就引出了动态代理。JDK的原生动态代理代理对象不需要实现接口,但是目标对象必须实现接口,否则动态代理代理对象的生成无法使用,通过JDKAPI在内存中动态构造代理对象(Java中的反射机制)。为了实现动态代理机制,java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类支持Coding1,网络接口不变publicinterfaceInterfaceInternet{voidconnectTo(StringserverHost)throwsException;}2。不会改变publicclassRealInternetimplementsInternet{@OverridepublicvoidconnectTo(StringserverHost)throwsException{System.out.println("Connectingto"+serverHost);}}3.动态代理需要实现InvocationHandler,我们使用Lambda表达式来简化publicclassProxyFactory{/***维护Atarget对象**/privateObjecttarget;/***构造函数,初始化目标对象**/publicProxyFactory(Objecttarget){this.target=target;}publicObjectgetProxyInstance(){/**通过参数传入代理对象target,通过target.getClass().getClassLoader()获取ClassLoader对象,然后通过target.getClass().getInterfaces()获取其实现的所有接口,再将target包装成一个实现了InvocationHandler接口的对象。我们通过newProxyInstance函数得到一个动态代理对象。*/returnProxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),newInvocationHandler(){@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{if(bannedSites.contains(args[0].toString().toLowerCase())){thrownewException("AccessDenied:"+args[0]);}//反射机制调用目标对象的方法Objectobj=method.invoke(target,args);returnobj;}});}privatestaticListbannedSites;static{bannedSites=newArrayList();bannedSites.add("bilibili.com");bannedSites.add("youtube.com");bannedSites。add("weibo.com");bannedSites.add("qq.com");}}4.客户端publicclassClient{publicstaticvoidmain(String[]args){Internetinternet=newProxyInternet(newRealInternet());try{internet.connectTo("360.cn");internet.connectTo("qq.com");}catch(Exceptione){System.out.println(e.getMessage());}}}动态代理的方式,所有functioncalls最后会被invoke函数转发过来,这样我们就可以做一些我们自己的操作了想要这里,比如日志系统,事务,拦截器,权限cglib代理静态代理和JDK代理模式都需要目标对象实现接口,但有时目标对象只是一个单独的对象,没有实现任何接口。这时可以使用目标对象子类来实现代理,即cglib代理。cglib(CodeGenerationLibrary)是一个基于ASM的字节码生成库,它允许我们在运行时修改和动态生成字节码。cglib通过继承实现代理。它被很多AOP框架广泛使用,比如我们的SpringAOP。cglib包的底层是利用字节码处理框架ASM对字节码进行改造,生成新的类。cglib代理也称为子类代理,在内存中构建子类对象,实现目标对象的功能扩展。编码添加cglib依赖cglibcglib3.3.01,nointerfacepublicclassRealInternet{publicvoidconnectTo(StringserverHost){System.out.println("Connectingto"+serverHost);}}2.代理工厂类System.out.println("cglibproxystarts,logiccanbeadded");Objectobj=method.invoke(target,objects);System.out.println("cglibproxyends");returnobj;}publicObjectgetProxyInstance(){//工具类,类似于JDK动态代理的Proxy类Enhancerenhancer=newEnhancer();//设置父类enhancer.setSuperclass(target.getClass());//设置回调函数enhancer.setCallback(this);//创建子Class对象,即代理对象returnenhancer.create();}}3.ClientpublicclassClient{publicstaticvoidmain(String[]args){//目标对象RealInternetarget=newRealInternet();//获取代理对象,并将目标对象传递给代理对象RealInternetinternet=(RealInternet)newProxyFactory(target).getProxyInstance();internet.connectTo("so.cn");}}4.outputcglibproxystart,可以添加逻辑Connectingtoso.cncglibproxyend代理模式适用于应用场景代理模式的使用方式有很多种,我们来看看最常见的惰性初始化(虚拟代理):如果你有一个weight偶尔使用agent模式可以在保持对象运行会消耗系统资源的情况下使用。你不需要在程序启动时创建对象,你可以延迟对象的初始化,直到真正需要它时。访问控制(保护代理):如果只想让特定的客户端使用服务对象,这里的对象可以是操作系统很重要的一部分,而客户端是各种启动的程序(包括恶意程序),在这个时候就可以使用Proxy模式了。仅当客户端凭据满足要求时,代理才能将请求传递给服务对象。在本地执行远程服务(远程代理):适用于服务对象位于远程服务器上的情况。在这种情况下,代理通过网络传递客户端请求并处理与网络相关的所有复杂细节。记录请求(记录代理):当您需要保留对服务对象的请求历史记录时很有用。代理可以在将请求传递给服务之前进行记录。缓存请求结果(缓存代理):适用于缓存客户端请求结果,管理缓存生命周期,尤其是返回结果非常大的时候。代理可以为重复请求缓存相同的结果,并可以使用请求参数作为键来索引缓存。比如请求图片、文件等资源时,先去代理缓存中获取,如果没有,则去公网获取并缓存到代理服务器中智能参考:当没有客户端使用某重量级时对象,它可以立即销毁。代理将跟踪所有获得指向服务对象或其结果的指针的客户端。有时,代理会遍历各种客户端,检查它们是否仍在运行。如果对应的客户端列表为空,代理将销毁服务对象,释放底层系统资源。代理还可以记录客户端是否修改了服务对象。其他客户端也可以重用未修改的对象。AOP中的代理模式AOP(面向切面编程)的主要实现技术主要有SpringAOP和AspectJ。AspectJ的底层技术是静态代理,用AspectJ支持的特定语言编写切面,用命令编译,生成新的代理类,增强业务类,在编译时增强。与下面提到的运行时增强相比,编译时增强的性能更好。(AspectJ的静态代理,不像我们前面介绍的,需要为每个目标类手动写一个代理类,AspectJ框架可以在编译时生成目标类的“代理类”,这里加了一个冒号,因为实际它不生成新类,而是将代理逻辑直接编译到目标类中)SpringAOP使用动态代理,在运行过程中增强了业务方法,因此不会生成新类。对于动态代理技术,SpringAOP提供了对JDK动态代理的支持和CGLib的支持。默认情况下,Spring对实现接口的类使用JDKProxy方法,否则使用CGLib。但是,您可以配置SpringAOP通过CGLib生成代理类。具体逻辑在org.springframework.aop.framework.DefaultAopProxyFactory类中,该方法根据AdvisedSupport对象的配置生成AopProxy确定源码如下:publicclassDefaultAopProxyFactoryimplementsAopProxyFactory,Serializable{publicDefaultAopProxyFactory(){}publicAopProxycreateSopportAopportifconfig(AdvisedAopProxycreateSopupportAprowifconfig)config.isOptimize()&&!config.isProxyTargetClass()&&!this.hasNoUserSuppliedProxyInterfaces(config)){returnnewJdkDynamicAopProxy(config);}else{Class>targetClass=config.getTargetClass();if(targetClass==null){thrownewAopConfigException("TargetSourcecannotdeterminetargetclass:Eitheraninterfaceoratargetisrequiredforproxycreation.");}else{//如果目标类是接口和代理类,则使用JDK动态代理类,否则使用Cglib生成代理类return(AopProxy)(!targetClass.isInterface()&&!Proxy.isProxyClass(targetClass)?newObjenesisCglibAopProxy(config):newJdkDynamicAopProxy(config));}}}privatebooleanhasNoUserSuppliedProxyInterfaces(AdvisedSupportconfig){}}具体内容就不展开了,后面会整理SpringAOP等待进一步参考并感谢https://refactoringguru.cn/design-patterns/proxyhttps://www.geeksforgeeks.org/proxy-design-pattern/