框架中建立浮动框架_建立代理,而不是框架

框架中建立浮动框架

自从引入Java注释以来,它已成为大型应用程序框架API的组成部分。 此类API的很好示例是Spring或Hibernate的示例,其中添加了几行注释代码可实现非常复杂的程序逻辑。 尽管人们可以争论这些特定API的缺点,但大多数开发人员都会同意,这种使用正确的声明式编程形式具有很强的表现力。 但是,只有很少的开发人员选择为其自己的框架或应用程序中间件实现基于注释的API,主要是因为它们难以实现。 在下一篇文章中,我想说服您,相比之下,此类API的实现非常简单,并且使用正确的工具不需要任何有关Java内在函数的专门知识。

实施基于注释的API时,一个非常明显的问题是,正在执行的Java运行时未处理注释。 结果,不可能为给定的用户注释分配特定的含义。 例如,考虑我们想要定义一个@Log批注,该批注我们希望提供它用于简单地记录每次被注释方法的调用:

class Service {@Logvoid doSomething() { // do something ...}
}

由于@Log批注@Log@Log就无法执行程序逻辑,因此,由批注的用户来执行请求的日志记录。 显然,这使注释几乎无用,因为我们无法调用doSomething方法,并且期望在日志中观察到相应的语句。 到目前为止,注释仅用作标记,而没有贡献任何程序逻辑。

缩小差距

为了克服这种明显的局限性,许多注释驱动的框架将子类与方法重写结合使用以实现与特定注释关联的逻辑。 这通常称为子类检测。 对于建议的@Log注释,子类检测将导致创建一个类似于以下LoggingService

class LoggingService extends Service {@Overridevoid doSomething() { Logger.log("doSomething() was called");super.doSomething();}
}

当然,上述类通常不需要显式实现。 相反,一种流行的方法是仅在运行时使用诸如cglib或Javassist之类的代码生成库来生成此类。 这两个库均提供用于创建程序增强子类的简单API。 作为将类的创建延迟到运行时的一个很好的副作用,建议的日志记录框架无需任何特殊的准备就可以使用,并且始终与用户代码保持同步。 如果将以更明确的方式创建类(例如通过在构建过程中编写Java源文件)来创建类,则情况并非如此。

但是,它可扩展吗?

然而,该解决方案带来了另一个缺点。 通过将注释的逻辑放入生成的子类中,必须不再通过其构造函数实例化示例Service类。 否则,仍将不记录带注释的方法的调用:显然,调用构造函数不会创建所需子类的实例。 更糟糕的是,当使用建议的运行时生成方法时,由于Java编译器不知道运行时生成的类,因此LoggingService无法直接实例化。

因此,诸如Spring或Hibernate之类的框架使用对象工厂,并且不允许直接实例化被视为其框架逻辑一部分的对象。 使用Spring,由于工厂的所有对象都是已经由框架首先创建的托管bean,因此自然可以通过工厂创建对象。 同样,大多数Hibernate实体是作为查询结果创建的,因此不会显式实例化。 但是,例如当保存尚未在数据库中表示的实体实例时,Hibernate的用户需要用存储后从Hibernate返回的实例替换最近保存的实例。 通过查看有关Hibernate的问题,忽略这种替代已经构成了一个常见的初学者错误。 除此之外,由于有了这些工厂,子类​​检测对于框架用户来说几乎是透明的,因为Java的类型系统暗示子类可以替代其任何超类。 因此, LoggingService的实例可以在用户期望用户定义的Service类的实例的任何地方使用。

不幸的是,事实证明,这种批准的实例工厂方法难以实现建议的@Log注释,因为这将需要对可能被注释的类的每个单个实例使用工厂。 显然,这将增加大量的样板代码。 通过不将日志记录指令硬编码到方法中,我们甚至可能创建比我们避免的样板更多的样板。 同样,意外使用构造函数会给Java程序带来细微的错误,因为此类实例上的注释将不再像我们期望的那样被对待。 另一个问题是,工厂不容易构成。 如果我们想向已经是Hibernate bean的类添加@Log注释怎么办? 这听起来很琐碎,但需要进行大量配置才能合并两个框架的工厂。 最后,最终的工厂膨胀的代码看起来不会太漂亮,以致于无法使用该框架进行迁移。 这是使用Java代理进行检测的地方。 这种低估的检测形式为讨论的子类检测提供了很好的选择。

一个简单的代理

Java代理由一个简单的jar文件表示。 与普通Java程序类似,Java代理将某些类定义为入口点。 然后,期望此类定义一个静态方法,该方法在调用实际Java程序的main方法之前将被调用:

class MyAgent {public static void premain(String args, Instrumentation inst) {// implement agent here ...}
}

处理Java代理时,最有趣的部分是premain方法的第二个参数,它表示Instrumentation接口的实例。 通过定义ClassFileTransformer此接口提供了一种挂接到Java的类加载过程的方法。 使用此类转换器,我们能够在Java程序首次使用之前对其进行增强。

乍一看,使用此API听起来似乎很直接,但它却带来了新的挑战。 通过更改已编译的Java类(表示为Java字节码)来执行类文件转换。 实际上,Java虚拟机不知道编程语言是什么Java。 相反,它仅处理此字节码。 还要感谢字节码抽象,JVM能够轻松运行其他语言,例如Scala或Groovy。 结果,注册的类文件转换器仅提供将给定的字节(代码)数组转换为另一个数组的功能。

即使诸如ASM或BCEL之类的库提供了用于处理已编译Java类的简单API,也只有很少的开发人员具有处理原始字节码的经验。 更糟糕的是,正确执行字节码操作通常很麻烦,甚至通过抛出令人讨厌且不可恢复的VerifierError来弥补虚拟机的小错误。 幸运的是,有更好,更轻松的方式来处理字节码。

我编写和维护的Byte Buddy库提供了一个简单的API,用于处理已编译的Java类和创建Java代理。 在某些方面,Byte Buddy是类似于cglib和Javassist的代码生成库。 但是,除了那些库之外,Byte Buddy还提供了一个统一的API,用于实现子类和重新定义现有的类。 但是,对于本文,我们只希望研究使用Java代理重新定义类。 好奇的读者可以参考Byte Buddy的网页,该网页提供了有关其完整功能集的详细教程 。

使用Byte Buddy作为简单代理

字节伙伴提供的一种定义工具的方法是使用依赖项注入。 这样做,一个拦截器类(由任何普通的旧Java对象表示)都可以通过其参数的注释简单地请求任何必需的信息。 例如,通过在“ Method类型的参数上使用Byte Buddy的@Origin批注,Byte Buddy可以@Origin出拦截器想知道要拦截的方法。 这样,我们可以定义一个通用拦截器,该拦截器始终知道要拦截的方法:

class LogInterceptor {static void log(@Origin Method method) {Logger.log(method + " was called");} 
}

当然,Byte Buddy附带了更多注释。

但是,此拦截器如何表示我们打算用于拟议的日志记录框架的逻辑? 到目前为止,我们仅定义了一个记录方法调用的拦截器。 我们错过的是该方法的原始代码的后续调用。 幸运的是,Byte Buddy的乐器是可以组合的。 首先,我们为最近定义的LogInterceptor定义一个MethodDelegation ,默认情况下,该方法在每次调用方法时都会调用拦截器的静态方法。 从此开始,我们可以随后以SuperMethodCall表示的原始方法代码的后续调用来组成委托:

MethodDelegation.to(LogInterceptor.class).andThen(SuperMethodCall.INSTANCE)

最后,我们需要告知Byte Buddy有关指定工具要拦截的方法的信息。 如前所述,我们希望该工具适用于任何使用@Log注释的方法。 在字节伙伴中,可以使用类似于Java 8谓词的ElementMatcher来标识方法的这种属性。 在静态实用程序类ElementMatchers ,我们已经可以找到合适的匹配器来标识带有给定注释的方法: ElementMatchers.isAnnotatedWith(Log.class)

有了这些,我们现在可以定义一个实现建议的日志记录框架的代理。 对于Java代理,Byte Buddy提供了一个实用程序API,该API建立在我们刚刚讨论的类修改API的基础上。 与后一种API相似,它被设计为领域特定的语言,因此仅通过查看实现即可轻松理解其含义。 如我们所见,定义这样的代理仅需要几行代码:

class LogAgent {public static void premain(String args, Instrumentation inst) {new AgentBuilder.Default().rebase(ElementMatchers.any()).transform( builder -> return builder.method(ElementMatchers.isAnnotatedWith(Log.class)).intercept(MethodDelegation.to(LogInterceptor.class).andThen(SuperMethodCall.INSTANCE)) ).installOn(inst);}
}

请注意,这种最小的Java代理不会干扰应用程序的其余部分,因为任何执行代码都会观察所检测的Java类,就像将日志记录语句硬编码到任何带注释的方法中一样。

现实生活如何?

当然,提出的基于代理的记录器是一个简单的例子。 通常情况下,开箱即用的框架提供了类似的功能,例如Spring或Dropwizard都是很棒的。 但是,对于如何解决编程问题,人们通常也对此类框架持意见。 对于大量的软件应用程序,这可能不是问题。 但是,有时这些意见会阻碍更大的发展。 然后,围绕框架关于如何做事情的假设进行工作,不仅会导致一些问题,而且还会导致抽象的泄漏,并可能导致软件维护成本激增。 尤其是当应用程序随着时间增长和变化,并且其需求与基础框架所提供的需求有所不同时,尤其如此。

相反,当以图片混合的方式组成更专业的框架或库时,一个简单地用另一个替换了有问题的组件。 如果这两种方法都不起作用,甚至可以实施自定义解决方案,而不会干扰应用程序的其余部分。 据我们了解,这似乎很难在JVM上实现,这主要是Java严格类型系统的结果。 但是,使用Java代理很有可能克服这些类型限制。

我的观点是,我认为至少所有跨领域的关注都应该由代理驱动的专用库来解决,而不是由整体框架的内置模块来解决。 我真的希望更多的应用程序会考虑这种方法。 在最琐碎的情况下,使用代理在感兴趣的方法上注册侦听器并将其从那里获取就足够了。 这种组成代码模块的间接方法避免了我在遇到的大部分Java应用程序中观察到的强大凝聚力。 作为一个很好的副作用,它也使测试非常容易。 与运行测试类似,在启动应用程序时不添加代理,可以有针对性地禁用某些应用程序功能,例如日志记录。 所有这些都无需更改代码行,也不会使应用程序崩溃,因为JVM只是忽略了它在运行时无法解析的注释。 安全性,日志记录,缓存,有许多原因应以建议的方式处理这些主题,甚至更多。 因此,有时创建代理而不是框架。

翻译自: https://www.javacodegeeks.com/2015/01/make-agents-not-frameworks.html

框架中建立浮动框架

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338921.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

easyexcel 动态列_easyexcel动态表头列导出SequenceDiagram 阅读源码事半功倍

EasyExcel简介Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是…

python3进阶_Python3 进阶教程 2020全新版

REVENGE_7771天前如果不创建class类的话,直接使用dog.name是会报错的,因为一般数据类型不接受这种调用方式0赞 0采集潘某人永不屈服2天前class Student()定义的时候,需要在括号内写明继承的类Person在__init__()方法,需要调用sup…

C语言中的输入输出

在C语言中提供了许多内置的输入输出函数。标准文件的定义在C语言中会把所有设备当文件来处理。为了访问键盘和屏幕,以下三个文件会在程序执行时而打开。标准文件文件指针设备标准输入stdin键盘标准输出stdout屏幕标准错误stderr屏幕C 语言中的输入/输出通常使用内置…

sqlalchemy与mysql区别_sqlite3和sqlalchemy有什么区别?

sqlite3是一个嵌入式RDBMS。A relational database management system (RDBMS) is a database management system (DBMS) that is based on the relational model as introduced by E. F. Codd. Most popular commercial and open source databases currently in use are based…

lambda 延迟执行_Java Lambdas和低延迟

lambda 延迟执行总览 有关在Java和低延迟中使用Lambda的主要问题是: 他们会产生垃圾吗,您能做些什么吗? 背景 我正在开发一个支持不同有线协议的库。 这个想法是,您可以描述要写入/读取的数据,并且有线协议确定它是否…

C语言“悬空指针”和“野指针”究竟是什么意思?

各位,提起C语言我们很自然就会想到指针二字,没错,作为C的核心和灵魂,它的地位咱们就不再赘述了,今天我们想跟大家讲的是指针中的两个特有名词:“悬空指针”和“野指针”。悬空指针C语言中的指针可以指向一块…

grad在python什么模块_深度学习(Deep Learning)基础概念1:神经网络基础介绍及一层神经网络的python实现...

此专栏文章随时更新编辑,如果你看到的文章还没写完,那么多半是作者正在更新或者上一次没有更新完,请耐心等待,正常的频率是每天更新一篇文章。 该文章是“深度学习(Deep Learning)”系列文章的第一部分&…

ubuntu终端命令停止_从命令行关闭Linux计算机的5种方法

没有操作系统是完美的。 即使相对稳定,驱动程序和应用程序也可能存在问题。 Linux也不例外。 尽管比Windows更稳定(在许多情况下,并非全部!),但可能还需要重新启动Linux计算机。 这可能是因为某些东西不起作用。 或者,您可能通过SSH连接到远程计算机或服务器,并希望它重新…

C语言编写简单朗读发音小工具!!

各位,今天给大家带来C语言结合VBS脚本写的一个简单的朗读小工具,做一个能够发音的C语言程序(保证简单,人人都能学会)。具备的知识体系:C语言基本框架C语言输入输出C语言文件操作C语言system函数VBS指令&…

jgit_JGit身份验证说明

jgitJGit中的身份验证与本地Git大致相同。 支持SSH和HTTP(S)等常用协议及其身份验证方法。 本文总结了如何使用JGit身份验证API安全地访问远程Git存储库。 尽管本文中的示例使用CloneCommand,但是可以将所描述的技术应用于连接到远程存储库的…

未发现oracle(tm)客户端和网络组件_SpringColud Eureka的服务注册与发现

一、Eureka简介本文中所有代码都会上传到git上,请放心浏览 项目git地址:https://github.com/839022478/Spring-Cloud在传统应用中,组件之间的调用,通过有规范的约束的接口来实现,从而实现不同模块间良好的协作。但是被…

mysql global index_Oracle中addsplit partition对globallocal index的影响

生产库中某些大表的分区异常,需要对现有表进行在线操作,以添加丢失分区,因为是生产库,还是谨慎点好,今天有空,针对addspli生产库中某些大表的分区异常,需要对现有表进行在线操作,以添…

sap寄售退货单_多个退货单

sap寄售退货单我曾经听说过,过去人们一直在努力使方法具有单个出口点。 我知道这是一种过时的方法,从未认为它特别值得注意。 但是最近我与一些仍坚持该想法的开发人员联系(最后一次是在这里 ),这让我开始思考。 因此…

课堂经验值管理小程序_微信小程序怎么管理门店?

微信门店小程序是一种不用注册下载就能使用的购物平台,近年来很是流行,而且它操作简单,能让用户快速找到自己需要的产品,然后进行购买,深得用户喜爱,有用户的地方就会有商家,商家想拥有自己的微…

C语言 | 直接插入排序

解题思路:直接插入排序是一种最简单的排序方法,其基本操作是将一条记录插入到已排好的有序表中,从而得到一个新的、记录数量增1的有序表。C语言源代码演示:#include//头文件 int main()//主函数 {void insort(int post[],int n)…

mysql+after+commit_Spring事务aftercommit原理及实践

来道题CREATE TABLE goods (id bigint(20) NOT NULL AUTO_INCREMENT,good_id varchar(20) DEFAULT NULL,num int(11) DEFAULT NULL,PRIMARY KEY (id),KEY goods_good_id_index (good_id)) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ciClass.forName("c…

序列化和反序列化的概念_序列化的概念

序列化和反序列化的概念讨论了为什么Optional不可序列化以及如何处理(即将推出)之后,让我们仔细看看序列化。 总览 这篇文章介绍了序列化的一些关键概念。 它尝试精简地执行此操作,而不会涉及太多细节,包括将建议降至…

python自动解析json_Python语言解析JSON详解

本文主要向大家介绍了Python语言解析JSON详解,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助。 JSON 函数使用 JSON 函数需要导入 json 库:import json。函数 描述json.dumps 将 Python 对象编码成 JSON 字符串json.loads 将已…

C语言必学的12个排序算法:基数排序

# 基本思想基数排序(radix sort),同样时一种非比较的内部排序算法,主要基于多关键字排序的思想进行排序,它将单个关键字按照基数分成“多个关键字”进行排序。例如整数789是一个关键字,可以按照十进制位划分多关键字(十…

有没有code能改xml内容_Spring源码解析-applicationContext.xml加载和bean的注册

applicationContext文件加载和bean注册流程​ Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎么去使用怎么去配置,各种百度谷歌都能查到很多大牛教…