日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

使用AspectJ,Javassist和Java Proxy进行代码注入的实用介绍

發(fā)布時(shí)間:2023/12/3 java 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用AspectJ,Javassist和Java Proxy进行代码注入的实用介绍 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
靜態(tài)地或在運(yùn)行時(shí)將代碼片段注入已編譯的類(lèi)和方法中的功能可能會(huì)很有幫助。 這尤其適用于在沒(méi)有源代碼的第三方庫(kù)中或在無(wú)法使用調(diào)試器或探查器的環(huán)境中對(duì)問(wèn)題進(jìn)行故障排除。 代碼注入對(duì)于處理涉及整個(gè)應(yīng)用程序的問(wèn)題(例如性能監(jiān)視)也很有用。 以這種方式使用代碼注入在面向方面編程 (AOP)的名字下變得很流行。 相反,代碼注入并不是很少使用,就像相反。 每個(gè)程序員都會(huì)遇到這種能力可以避免很多痛苦和沮喪的情況。

這篇文章旨在為您提供您可能(或我寧愿說(shuō)“將要”)需要的知識(shí),并說(shuō)服您學(xué)習(xí)代碼注入的基礎(chǔ)確實(shí)值得您花很少的時(shí)間。 我將介紹三種不同的現(xiàn)實(shí)情況,在這些情況下我需要進(jìn)行代碼注入,并使用不同的工具解決每個(gè)問(wèn)題,最適合手頭的約束。

為什么您需要它

關(guān)于AOP的優(yōu)勢(shì)(因此有代碼注入),已經(jīng)有很多論述 ,因此,從故障排除的角度來(lái)看,我將只專(zhuān)注于一些要點(diǎn)。

最酷的事情是,它使您能夠修改第三方封閉源類(lèi) ,甚至實(shí)際上是JVM類(lèi)。 我們大多數(shù)人使用的是遺留代碼和我們沒(méi)有源代碼的代碼,因此不可避免地我們偶爾會(huì)遇到這些第三方二進(jìn)制文件的局限性或錯(cuò)誤,因此非常需要更改其中的一些小東西或深入了解代碼的行為。 如果沒(méi)有代碼注入,則無(wú)法修改代碼或添加對(duì)代碼增加可觀察性的支持。 同樣,您通常需要在生產(chǎn)環(huán)境中處理問(wèn)題或收集信息,在生產(chǎn)環(huán)境中,您不能使用調(diào)試器和類(lèi)似工具,而您通常至少可以以某種方式管理應(yīng)用程序的二進(jìn)制文件和依賴(lài)項(xiàng)。 請(qǐng)考慮以下情況:

  • 您正在將數(shù)據(jù)集合傳遞到閉源庫(kù)進(jìn)行處理,并且?guī)熘械囊粋€(gè)方法對(duì)其中一個(gè)元素失敗,但是異常未提供有關(guān)它是哪個(gè)元素的信息。 您需要對(duì)其進(jìn)行修改以記錄有問(wèn)題的參數(shù)或?qū)⑵浒ㄔ诋惓V小?(并且您不能使用調(diào)試器,因?yàn)樗鼉H在生產(chǎn)應(yīng)用程序服務(wù)器上發(fā)生。)
  • 您需要收集應(yīng)用程序中重要方法的性能統(tǒng)計(jì)信息,包括在典型生產(chǎn)負(fù)載下的某些封閉源組件。 (在生產(chǎn)環(huán)境中,您當(dāng)然不能使用探查器,并且您希望產(chǎn)生最小的開(kāi)銷(xiāo)。)
  • 您使用JDBC批量發(fā)送大量數(shù)據(jù)到數(shù)據(jù)庫(kù),而其中一個(gè)批量更新失敗。 您將需要一些不錯(cuò)的方法來(lái)找出批次和包含的數(shù)據(jù)。

實(shí)際上,我已經(jīng)遇到了這三種情況(在其他情況下),稍后您將看到可能的實(shí)現(xiàn)。

閱讀本文時(shí),您應(yīng)該牢記代碼注入的以下優(yōu)點(diǎn):

  • 代碼注入使您能夠修改您沒(méi)有源代碼的二進(jìn)制類(lèi)
  • 注入的代碼可用于在無(wú)法使用傳統(tǒng)開(kāi)發(fā)工具(例如探查器和調(diào)試器)的環(huán)境中收集各種運(yùn)行時(shí)信息。
  • 不要重復(fù)自己:當(dāng)您需要在多個(gè)地方使用相同的邏輯時(shí),可以定義一次,然后將其注入所有這些地方。
  • 使用代碼注入時(shí),您無(wú)需修改??原始源文件,因此非常適合僅在有限時(shí)間內(nèi)進(jìn)行的(可能是大規(guī)模的)更改,尤其是借助可以輕松打開(kāi)和關(guān)閉代碼注入的工具(例如,具有加載時(shí)編織功能的AspectJ)。 典型的情況是性能指標(biāo)收集和故障排除期間增加的日志記錄
  • 您可以在構(gòu)建時(shí)靜態(tài)或靜態(tài)地注入代碼,或者在JVM加載目標(biāo)類(lèi)時(shí)動(dòng)態(tài)注入代碼。

迷你詞匯

您可能會(huì)遇到以下與代碼注入和AOP有關(guān)的術(shù)語(yǔ):

忠告

要注入的代碼。 通常,我們談?wù)摻ㄗh之前,之后和周?chē)?#xff0c;這些建議是在目標(biāo)方法之前,之后或代替目標(biāo)方法執(zhí)行的。 除了將代碼注入方法之外,還可以進(jìn)行其他更改,例如,向類(lèi)添加字段或接口。

AOP(面向方面??的編程)

一個(gè)編程范例聲稱(chēng),“跨領(lǐng)域關(guān)注點(diǎn)”(在許多地方都需要的邏輯,沒(méi)有一個(gè)單獨(dú)的類(lèi)在哪里實(shí)現(xiàn))應(yīng)該實(shí)施一次,然后注入這些地方。 檢查維基百科以獲得更好的描述。

方面

AOP中的模塊化單位大致對(duì)應(yīng)于一個(gè)類(lèi)–它可以包含不同的建議和切入點(diǎn)。

聯(lián)合點(diǎn)

程序中可能成為代碼注入目標(biāo)的特定點(diǎn),例如方法調(diào)用或方法條目。

切入點(diǎn)

粗略地說(shuō),切入點(diǎn)是一個(gè)表達(dá)式,它告訴代碼注入工具在哪里注入特定代碼段,即在哪個(gè)聯(lián)合點(diǎn)上應(yīng)用特定建議。 它只能選擇一個(gè)這樣的點(diǎn)(例如,單個(gè)方法的執(zhí)行),也可以選擇許多類(lèi)似的點(diǎn),例如,所有帶有自定義注釋(例如@MyBusinessMethod)的方法的執(zhí)行。

織造

將代碼(建議)注入目標(biāo)位置(聯(lián)合點(diǎn))的過(guò)程。


工具

有很多非常不同的工具可以完成這項(xiàng)工作,因此我們將首先了解它們之間的差異,然后我們將熟悉代碼注入工具的不同演化分支的三個(gè)杰出代表。

代碼注入工具的基本分類(lèi)

一,抽象水平

表達(dá)要注入的邏輯以及表達(dá)應(yīng)在其中插入邏輯的切入點(diǎn)有多困難?

關(guān)于“建議”代碼:

  • 直接字節(jié)碼操作(例如ASM)–要使用這些工具,您需要了解類(lèi)的字節(jié)碼格式,因?yàn)樗鼈儚念?lèi)中提取的很少,您可以直接使用操作碼,操作數(shù)堆棧和單個(gè)指令。 一個(gè)ASM示例:

    methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,“ java / lang / System”,“ out”,“ Ljava / io / PrintStream;”);

    由于級(jí)別太低,因此難以使用,但功能最強(qiáng)大。 通常,它們用于實(shí)現(xiàn)更高級(jí)別的工具,實(shí)際上很少需要使用它們。

  • 中級(jí)–字符串代碼,類(lèi)文件結(jié)構(gòu)的抽象(Javassist)
  • Java建議(例如AspectJ)–要注入的代碼表示為語(yǔ)法檢查和靜態(tài)編譯的Java
  • 關(guān)于將代碼注入到哪里的規(guī)范:

  • 手動(dòng)注入–您必須以某種方式掌握要注入代碼的位置(ASM,Javassist)
  • 原始切入點(diǎn)–表達(dá)特定位置,特定類(lèi),類(lèi)的所有公共方法或組中所有類(lèi)的公共方法(Java EE攔截器)的地方,代碼的插入可能性非常有限。
  • 模式匹配切入點(diǎn)表達(dá)式–強(qiáng)大的表達(dá)式,可基于多個(gè)條件使用通配符匹配關(guān)節(jié)點(diǎn),對(duì)上下文的了解(例如“從包XY中的類(lèi)調(diào)用”)等(AspectJ)
  • 二。 當(dāng)魔術(shù)發(fā)生時(shí)

    可以在不同的時(shí)間點(diǎn)注入代碼:

    • 在運(yùn)行時(shí)手動(dòng)–您的代碼必須明確要求增強(qiáng)代碼,例如,通過(guò)手動(dòng)實(shí)例化包裝目標(biāo)對(duì)象的自定義代理(可以說(shuō)這不是真正的代碼注入)
    • 在加載時(shí)–在JVM加載目標(biāo)類(lèi)時(shí)執(zhí)行修改
    • 在構(gòu)建時(shí)–在打包和部署應(yīng)用程序之前,您需要在構(gòu)建過(guò)程中添加額外的步驟來(lái)修改已編譯的類(lèi)。

    這些注射方式中的每一種都可能更適合于不同情況。

    三, 它能做什么

    代碼注入工具可以做什么或不能做什么都存在很大差異,其中一些可能性是:

    • 在方法之前/之后/而不是方法中添加代碼–僅成員級(jí)方法還是靜態(tài)方法?
    • 將字段添加到班級(jí)
    • 添加新方法
    • 制作一個(gè)類(lèi)以實(shí)現(xiàn)接口
    • 修改方法體內(nèi)的指令(例如方法調(diào)用)
    • 修改泛型,注釋,訪問(wèn)修飾符,更改常量值,…
    • 刪除方法,字段等



    選定的代碼注入工具

    最著名的代碼注入工具是:

  • 動(dòng)態(tài)Java代理
  • 字節(jié)碼操作庫(kù)ASM
  • JBoss Javassist
  • AspectJ
  • Spring AOP /代理
  • Java EE攔截器


  • Java Proxy,Javassist和AspectJ實(shí)用介紹

    我選擇了三種非常不同的成熟和流行的代碼注入工具,并將它們呈現(xiàn)在我親身經(jīng)歷的真實(shí)示例中。

    無(wú)所不在的動(dòng)態(tài)Java代理

    Java.lang.reflect.Proxy使動(dòng)態(tài)創(chuàng)建接口的代理成為可能,將所有調(diào)用轉(zhuǎn)發(fā)到目標(biāo)對(duì)象。 它不是代碼注入工具,因?yàn)槟荒茉谌魏蔚胤阶⑷胨?#xff0c;必須手動(dòng)實(shí)例化并使用代理而不是原始對(duì)象,并且只能對(duì)接口執(zhí)行此操作,但是如我們所見(jiàn),它仍然非常有用。

    優(yōu)點(diǎn):

    • 它是JVM的一部分,因此隨處可見(jiàn)
    • 您可以對(duì)不兼容的對(duì)象使用相同的代理(更確切地說(shuō)是InvocationHandler) ,從而比平時(shí)更多地重用代碼
    • 您可以節(jié)省精力,因?yàn)槟梢暂p松地將所有調(diào)用轉(zhuǎn)發(fā)到目標(biāo)對(duì)象,而僅修改您感興趣的那些調(diào)用。 如果要手動(dòng)實(shí)現(xiàn)代理,則需要實(shí)現(xiàn)相關(guān)接口的所有方法

    缺點(diǎn):

    • 您只能為接口創(chuàng)建動(dòng)態(tài)代理,如果代碼需要具體的類(lèi),則不能使用它
    • 您必須實(shí)例化并手動(dòng)應(yīng)用它,沒(méi)有神奇的自動(dòng)注入功能
    • 有點(diǎn)太冗長(zhǎng)
    • 它的功能非常有限,它只能在方法之前/之后/周?chē)鷪?zhí)行一些代碼

    沒(méi)有代碼注入步驟-您必須手動(dòng)應(yīng)用代理。

    我正在使用JDBC PreparedStatement的批處理更新來(lái)修改數(shù)據(jù)庫(kù)中的許多數(shù)據(jù),并且由于違反完整性約束而導(dǎo)致其中一個(gè)批處理的處理失敗。 該異常沒(méi)有足夠的信息來(lái)找出導(dǎo)致失敗的數(shù)據(jù),因此我為PreparedStatement創(chuàng)建了一個(gè)動(dòng)態(tài)代理,該代理記住了傳遞給每個(gè)批處理更新的值,并且在失敗的情況下會(huì)自動(dòng)打印該批處理數(shù)字和數(shù)據(jù)。 有了這些信息,我就可以修復(fù)數(shù)據(jù),并保持解決方案就位,這樣,如果再次發(fā)生類(lèi)似的問(wèn)題,我將能夠找到原因并Swift解決。

    該代碼的關(guān)鍵部分:

    LoggingStatementDecorator.java –片段1

    class LoggingStatementDecorator implements InvocationHandler {private PreparedStatement target;...private LoggingStatementDecorator(PreparedStatement target) { this.target = target; }@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {try {Object result = method.invoke(target, args);updateLog(method, args); // remember data, reset upon successful executionreturn result;} catch (InvocationTargetException e) {Throwable cause = e.getTargetException();tryLogFailure(cause);throw cause;}}private void tryLogFailure(Throwable cause) {if (cause instanceof BatchUpdateException) {int failedBatchNr = successfulBatchCounter + 1;Logger.getLogger("JavaProxy").warning("THE INJECTED CODE SAYS: " +"Batch update failed for batch# " + failedBatchNr +" (counting from 1) with values: [" +getValuesAsCsv() + "]. Cause: " + cause.getMessage());}} ...

    筆記:

    要?jiǎng)?chuàng)建代理,您首先需要實(shí)現(xiàn)一個(gè)InvocationHandler及其調(diào)用方法,只要在代理上調(diào)用任何接口的方法,就會(huì)調(diào)用該方法
    您可以通過(guò)java.lang.reflect。*對(duì)象訪問(wèn)有關(guān)該調(diào)用的信息,例如,通過(guò)method.invoke將調(diào)用委派給代理對(duì)象
    我們還有一個(gè)實(shí)用方法,用于為Prepared語(yǔ)句創(chuàng)建代理實(shí)例:

    LoggingStatementDecorator.java –代碼段2

    public static PreparedStatement createProxy(PreparedStatement target) {return (PreparedStatement) Proxy.newProxyInstance(PreparedStatement.class.getClassLoader(),new Class[] { PreparedStatement.class },new LoggingStatementDecorator(target)); };

    筆記:

    • 您可以看到newProxyInstance調(diào)用使用一個(gè)類(lèi)加載器,代理應(yīng)實(shí)現(xiàn)的接口數(shù)組以及應(yīng)將調(diào)用委派給其的調(diào)用處理程序(如果需要,處理程序本身必須管理對(duì)代理對(duì)象的引用)

    然后按以下方式使用:

    Main.java

    ... PreparedStatement rawPrepStmt = connection.prepareStatement("..."); PreparedStatement loggingPrepStmt = LoggingStatementDecorator.createProxy(rawPrepStmt); ... loggingPrepStmt.executeBatch(); ...

    筆記:

    • 您會(huì)看到我們必須使用代理手動(dòng)包裝原始對(duì)象,并在以后繼續(xù)使用代理



    替代解決方案

    可以通過(guò)不同的方式解決此問(wèn)題,例如,通過(guò)創(chuàng)建一個(gè)實(shí)現(xiàn)PreparedStatement的非動(dòng)態(tài)代理,并在記住批處理數(shù)據(jù)的同時(shí)將所有調(diào)用轉(zhuǎn)發(fā)到實(shí)際語(yǔ)句,但是對(duì)于具有許多方法的接口,這將是很多無(wú)聊的鍵入。 調(diào)用方還可以手動(dòng)跟蹤已發(fā)送到準(zhǔn)備好的語(yǔ)句的數(shù)據(jù),但這會(huì)因無(wú)關(guān)的關(guān)注而使邏輯模糊。

    使用動(dòng)態(tài)Java代理,我們可以得到非常干凈且易于實(shí)現(xiàn)的解決方案。

    獨(dú)立的Javassist

    JBoss Javassist是一個(gè)中間代碼注入工具,它提供了比字節(jié)碼操作庫(kù)更高級(jí)別的抽象,并且提供了有限的但仍然非常有用的操作功能。 要注入的代碼以字符串表示,您必須手動(dòng)進(jìn)入將其注入的類(lèi)方法。 它的主要優(yōu)點(diǎn)是修改后的代碼對(duì)Javassist或其他任何東西都沒(méi)有新的運(yùn)行時(shí)依賴(lài)項(xiàng)。 如果您在一家大公司工作,這可能是決定性因素,而在大公司中,由于法律和其他原因,很難部署其他開(kāi)放源代碼庫(kù)(或幾乎任何其他庫(kù)),例如AspectJ。

    優(yōu)點(diǎn):

    • Javassist修改的代碼不需要任何新的運(yùn)行時(shí)依賴(lài)項(xiàng),注入會(huì)在構(gòu)建時(shí)發(fā)生,并且注入的建議代碼本身不依賴(lài)于任何Javassist API
    • 盡管比字節(jié)碼操作庫(kù)更高級(jí),但注入的代碼是用Java語(yǔ)法編寫(xiě)的,盡管包含在字符串中
    • 可以完成您可能需要的大多數(shù)事情,例如“建議”方法調(diào)用和方法執(zhí)行
    • 您可以同時(shí)實(shí)現(xiàn)構(gòu)建時(shí)注入(通過(guò)Java代碼或定制的Ant任務(wù)來(lái)執(zhí)行執(zhí)行/調(diào)用建議 )和加載時(shí)注入(通過(guò)實(shí)現(xiàn)自己的Java 5+代理 [thx to Anton])

    缺點(diǎn):

    • 仍然有些太底層,因此難于使用–您必須處理一些方法的結(jié)構(gòu),并且注入的代碼未經(jīng)語(yǔ)法檢查
    • Javassist沒(méi)有執(zhí)行注入的工具,因此您必須實(shí)現(xiàn)自己的注入代碼-包括不支持根據(jù)模式自動(dòng)注入代碼

    (有關(guān)沒(méi)有Javassist的大多數(shù)缺點(diǎn)的解決方案,請(qǐng)參見(jiàn)下面的GluonJ。)

    使用Javassist,您可以創(chuàng)建一個(gè)類(lèi),該類(lèi)使用Javassist API注入int目標(biāo)代碼,并在編譯后將其作為構(gòu)建過(guò)程的一部分運(yùn)行,例如,就像我曾經(jīng)通過(guò)自定義Ant任務(wù)所做的那樣。

    我們需要在Java EE應(yīng)用程序中添加一些簡(jiǎn)單的性能監(jiān)控,并且不允許我們部署任何未經(jīng)批準(zhǔn)的開(kāi)源庫(kù)(至少在沒(méi)有經(jīng)過(guò)耗時(shí)的審批過(guò)程的情況下)。 因此,我們使用Javassist將性能監(jiān)視代碼注入到我們的重要方法中,以及將重要的外部方法調(diào)用到的地方。

    代碼注入器:

    JavassistInstrumenter.java

    public class JavassistInstrumenter {public void insertTimingIntoMethod(String targetClass, String targetMethod) throws NotFoundException, CannotCompileException, IOException {Logger logger = Logger.getLogger("Javassist");final String targetFolder = "./target/javassist";try {final ClassPool pool = ClassPool.getDefault();// Tell Javassist where to look for classes - into our ClassLoaderpool.appendClassPath(new LoaderClassPath(getClass().getClassLoader()));final CtClass compiledClass = pool.get(targetClass);final CtMethod method = compiledClass.getDeclaredMethod(targetMethod);// Add something to the beginning of the method:method.addLocalVariable("startMs", CtClass.longType);method.insertBefore("startMs = System.currentTimeMillis();");// And also to its very end:method.insertAfter("{final long endMs = System.currentTimeMillis();" +"iterate.jz2011.codeinjection.javassist.PerformanceMonitor.logPerformance(\"" +targetMethod + "\",(endMs-startMs));}");compiledClass.writeFile(targetFolder);// Enjoy the new $targetFolder/iterate/jz2011/codeinjection/javassist/TargetClass.classlogger.info(targetClass + "." + targetMethod +" has been modified and saved under " + targetFolder);} catch (NotFoundException e) {logger.warning("Failed to find the target class to modify, " +targetClass + ", verify that it ClassPool has been configured to look " +"into the right location");}}public static void main(String[] args) throws Exception {final String defaultTargetClass = "iterate.jz2011.codeinjection.javassist.TargetClass";final String defaultTargetMethod = "myMethod";final boolean targetProvided = args.length == 2;new JavassistInstrumenter().insertTimingIntoMethod(targetProvided? args[0] : defaultTargetClass, targetProvided? args[1] : defaultTargetMethod);} }

    筆記:

    • 您可以看到“底層” –您必須顯式處理CtClass,CtMethod之類(lèi)的對(duì)象,顯式添加局部變量等。
    • Javassist在查找要修改的類(lèi)方面非常靈活-它可以搜索類(lèi)路徑,特定文件夾,JAR文件或包含JAR文件的文件夾
    • 您將在編譯過(guò)程中編譯此類(lèi)并運(yùn)行其主要內(nèi)容

    類(lèi)固醇的Javassist:GluonJ

    GluonJ是一個(gè)基于Javassist的AOP工具。 它可以使用自定義語(yǔ)法或Java 5注釋,并且圍繞“修訂器”的概念構(gòu)建。 Reviser是一個(gè)類(lèi)(一個(gè)方面),它可以修改(即修改)特定的目標(biāo)類(lèi)并覆蓋其一個(gè)或多個(gè)方法(與繼承相反,修訂者的代碼實(shí)際上被強(qiáng)加于目標(biāo)類(lèi)內(nèi)部的原始代碼)。

    優(yōu)點(diǎn):

    • 如果使用構(gòu)建時(shí)編織,則沒(méi)有運(yùn)行時(shí)依賴(lài)性(加載時(shí)編織需要GluonJ代理庫(kù)或gluonj.jar)
    • 使用GlutonJ的注釋的簡(jiǎn)單Java語(yǔ)法-盡管自定義語(yǔ)法也很容易理解和易于使用
    • 使用GlutonJ的JAR工具,Ant任務(wù)或在加載時(shí)動(dòng)態(tài)輕松地自動(dòng)織入目標(biāo)類(lèi)
    • 支持構(gòu)建時(shí)和加載時(shí)編織

    缺點(diǎn):

    • 一個(gè)方面只能修改一個(gè)類(lèi),而不能將同一段代碼注入多個(gè)類(lèi)/方法
    • 功率有限–僅在執(zhí)行任何代碼時(shí)或僅在特定上下文中執(zhí)行時(shí)(即從特定的類(lèi)/方法中調(diào)用時(shí)),才提供字段/方法的添加和代碼的執(zhí)行,而不是在目標(biāo)方法周?chē)?

    如果您不需要將同一段代碼注入多個(gè)方法中,那么GluonJ比Javassist更加容易和更好地選擇,并且如果它的簡(jiǎn)單性對(duì)您來(lái)說(shuō)不是問(wèn)題,那么它也比AspectJ更好的選擇。簡(jiǎn)單。

    全能方面

    AspectJ是功能完善的AOP工具,它幾乎可以完成您可能想要的任何事情,包括修改靜態(tài)方法,添加新字段,在類(lèi)的已實(shí)現(xiàn)接口列表中添加接口等。

    AspectJ建議的語(yǔ)法有兩種,一種是Java語(yǔ)法的超集,具有諸如Aspect和Pointcut的其他關(guān)鍵字,另一種稱(chēng)為@AspectJ –是標(biāo)準(zhǔn)Java 5,具有諸如@ Aspect,@ Pointcut,@ Around的批注。 后者也許更容易學(xué)習(xí)和使用,但功能卻不那么強(qiáng)大,因?yàn)樗幌褡远xAspectJ語(yǔ)法那樣具有表現(xiàn)力。

    使用AspectJ,您可以定義要用非常有力的表達(dá)建議的聯(lián)合點(diǎn),但是學(xué)習(xí)它們并使其正確起來(lái)可能并不困難。 對(duì)于AspectJ開(kāi)發(fā),有一個(gè)有用的Eclipse插件– AspectJ開(kāi)發(fā)工具 (AJDT)–但是上次嘗試時(shí),它沒(méi)有我想要的那樣有用。

    優(yōu)點(diǎn):

    • 功能強(qiáng)大,幾乎可以完成您可能需要的所有操作
    • 強(qiáng)大的切入點(diǎn)表達(dá)式,用于定義在何處注入建議以及何時(shí)激活建議(包括一些運(yùn)行時(shí)檢查)–完全啟用DRY,即寫(xiě)入一次并多次注入
    • 編譯時(shí)和加載時(shí)代碼注入(編織)

    缺點(diǎn):

    • 修改后的代碼取決于AspectJ運(yùn)行時(shí)庫(kù)
    • 切入點(diǎn)表達(dá)式非常強(qiáng)大,但是可能很難正確使用它們,盡管AJDT插件可以部分可視化它們的效果,但對(duì)“調(diào)試”它們的支持不多
    • 盡管基本用法非常簡(jiǎn)單(可能會(huì)花一些時(shí)間才能開(kāi)始使用(使用@ Aspect,@ Around和一個(gè)簡(jiǎn)單的切入點(diǎn)表達(dá)式,如我們?cè)谑纠兴?jiàn)))



    曾幾何時(shí),我為具有相關(guān)性的封閉式LMS J2EE應(yīng)用程序編寫(xiě)了一個(gè)插件,以致于無(wú)法在本地運(yùn)行它。 在API調(diào)用期間,應(yīng)用程序內(nèi)部的某個(gè)方法失敗了,但該異常未包含足夠的信息來(lái)跟蹤問(wèn)題的原因。 因此,我需要更改方法以在失敗時(shí)記錄其參數(shù)的值。

    AspectJ代碼非常簡(jiǎn)單:

    LoggingAspect.java

    @Aspect public class LoggingAspect {@Around("execution(private void TooQuiet3rdPartyClass.failingMethod(..))")public Object interceptAndLog(ProceedingJoinPoint invocation) throws Throwable {try {return invocation.proceed();} catch (Exception e) {Logger.getLogger("AspectJ").warning("THE INJECTED CODE SAYS: the method " +invocation.getSignature().getName() + " failed for the input '" +invocation.getArgs()[0] + "'. Original exception: " + e);throw e;}} }

    筆記:

    • 方面是帶有@Aspect批注的普通Java類(lèi),它只是AspectJ的標(biāo)記
    • @Around注釋指示AspectJ執(zhí)行該方法,而不是與表達(dá)式匹配的方法,即代替TooQuiet3rdPartyClass的failingMethod。
    • 周?chē)ㄗh方法需要是公共的,返回一個(gè)對(duì)象,并采用一個(gè)特殊的AspectJ對(duì)象作為參數(shù),該對(duì)象攜帶有關(guān)調(diào)用的信息– ProceedingJoinPoint –并且可以具有任意名稱(chēng)(實(shí)際上,這是簽名的最小形式,它可以更復(fù)雜。)
    • 我們使用ProceedingJoinPoint將調(diào)用委派給原始目標(biāo)(TooQuiet3rdPartyClass的實(shí)例),并在發(fā)生異常的情況下獲取參數(shù)的值
    • 我使用了@Around建議,盡管@AfterThrowing會(huì)更簡(jiǎn)單,更合適,但這可以更好地顯示AspectJ的功能,并且可以與上述動(dòng)態(tài)Java代理示例進(jìn)行很好的比較

    由于我無(wú)法控制應(yīng)用程序的環(huán)境,因此無(wú)法啟用加載時(shí)編織,因此不得不在構(gòu)建時(shí)使用AspectJ的Ant任務(wù)來(lái)編織代碼,重新打包受影響的JAR并將其重新部署到服務(wù)器。

    替代解決方案

    好吧,如果您不能使用調(diào)試器,那么您的選擇就非常有限。 我唯一想到的替代解決方案是反編譯該類(lèi)(非法!),將日志記錄添加到該方法中(前提是反編譯成功),重新編譯它,然后將原始.class替換為修改后的.class。

    黑暗的一面

    代碼注入和面向方面的編程非常強(qiáng)大,有時(shí)對(duì)于故障排除和作為應(yīng)用程序體系結(jié)構(gòu)的常規(guī)部分來(lái)說(shuō)都是必不可少的,例如我們可以看到,例如在Java EE的Enterprise Java Beans中,諸如事務(wù)管理和安全性檢查等業(yè)務(wù)問(wèn)題是注入到POJO中(盡管實(shí)現(xiàn)實(shí)際上更可能使用代理)或在Spring中。

    但是,由于可能會(huì)降低可理解性,因此需要付出一定的代價(jià),因?yàn)檫\(yùn)行時(shí)行為和結(jié)構(gòu)與您根據(jù)源代碼所期望的不同(除非您知道還要檢查方面的源代碼,或者除非進(jìn)行了注入)通過(guò)對(duì)目標(biāo)類(lèi)(例如Java EE的@Interceptors )的注釋進(jìn)行顯式顯示。 因此,您必須仔細(xì)權(quán)衡代碼注入/ AOP的優(yōu)缺點(diǎn)-盡管合理使用它們不會(huì)比接口,工廠等掩蓋程序流。 關(guān)于掩蓋代碼的爭(zhēng)論可能經(jīng)常被高估了 。

    如果您想看一下AOP的例子,請(qǐng)查看Glassbox的源代碼 ,它是JavaEE性能監(jiān)視工具(為此,您可能需要一張地圖 ,以免丟失太多)。

    花式使用代碼注入和AOP

    在故障排除過(guò)程中,代碼注入的主要應(yīng)用領(lǐng)域是日志記錄,通過(guò)提取并以某種方式傳達(dá)有關(guān)它的有趣運(yùn)行時(shí)信息,可以更準(zhǔn)確地了解應(yīng)用程序正在做什么。 但是,AOP除了簡(jiǎn)單或復(fù)雜的日志記錄以外,還有許多有趣的用途,例如:

    • 典型示例:Caching等人(例如: 在JBoss Cache中的AOP上 ),事務(wù)管理,日志記錄,安全性實(shí)施,持久性,線程安全,錯(cuò)誤恢復(fù),方法的自動(dòng)實(shí)現(xiàn)(例如toString,equals,hashCode),遠(yuǎn)程處理
    • 基于角色的編程 (例如OT / J ,使用BCEL)或數(shù)據(jù),上下文和交互體系結(jié)構(gòu)的實(shí)現(xiàn)
    • 測(cè)試中
      • 測(cè)試覆蓋率–注入代碼以記錄測(cè)試運(yùn)行期間是否已執(zhí)行某行
      • 突變測(cè)試 ( μJava , Jumble )–向應(yīng)用程序注入“隨機(jī)”突變并驗(yàn)證測(cè)試是否失敗
      • 模式測(cè)試 –通過(guò)AOP自動(dòng)驗(yàn)證代碼中正確實(shí)施了架構(gòu)/設(shè)計(jì)/最佳實(shí)踐建議
      • 通過(guò)注入異常來(lái)模擬硬件/外部故障
    • 幫助實(shí)現(xiàn)Java應(yīng)用程序的零周轉(zhuǎn)– JRebel對(duì)框架和服務(wù)器集成插件使用類(lèi)似于AOP的方法 –即其插件 使用Javassist進(jìn)行“二進(jìn)制修補(bǔ)”
    • 解決問(wèn)題并避免使用AOP模式進(jìn)行猴子編碼,例如Worker Object Creation(通過(guò)Runnable和ThreadPool / task隊(duì)列將直接調(diào)用轉(zhuǎn)變?yōu)楫惒秸{(diào)用)和Wormhole(使調(diào)用方的上下文信息對(duì)被調(diào)用方可用,而不必傳遞它們)遍歷所有層作為參數(shù),并且沒(méi)有ThreadLocal)–在《 AspectJ in Action》一書(shū)中進(jìn)行了描述
    • 處理遺留代碼–覆蓋對(duì)構(gòu)造函數(shù)的調(diào)用實(shí)例化的類(lèi)(可以使用此類(lèi)和類(lèi)似的類(lèi)來(lái)打破緊密耦合與可行的工作量), 確保向后兼容 o, 教導(dǎo)組件對(duì)環(huán)境變化做出正確反應(yīng)
    • 保留API的向后兼容性,同時(shí)不阻止其演化能力,例如,在縮小/擴(kuò)展返回類(lèi)型( Bridge Method Injector –使用ASM)時(shí)添加向后兼容方法,或者通過(guò)重新添加舊方法并按照以下方式實(shí)現(xiàn)它們:新的API
    • 將POJO轉(zhuǎn)換為JMX bean



    摘要

    我們已經(jīng)了解到,代碼注入對(duì)于故障排除是必不可少的,尤其是在處理封閉源代碼庫(kù)和復(fù)雜的部署環(huán)境時(shí)。 我們已經(jīng)看到了三種完全不同的代碼注入工具(動(dòng)態(tài)Java代理,Javassist,AspectJ)應(yīng)用于實(shí)際問(wèn)題,并討論了它們的優(yōu)缺點(diǎn),因?yàn)椴煌墓ぞ呖赡苓m用于不同的情況。 我們還提到了不應(yīng)過(guò)度使用代碼注入/ AOP,并查看了一些代碼注入/ AOP的高級(jí)應(yīng)用示例。

    我希望您現(xiàn)在了解代碼注入如何為您提供幫助,并知道如何使用這三個(gè)工具。

    源代碼

    您可以從GitHub 獲取示例的完整文檔源代碼,不僅包括要注入的代碼,還包括目標(biāo)代碼和易于構(gòu)建的支持。 最簡(jiǎn)單的可能是:

    git clone git://github.com/jakubholynet/JavaZone-Code-Injection.git cd JavaZone-Code-Injection/ cat README mvn -P javaproxy test mvn -P javassist test mvn -P aspectj test

    (Maven可能需要花費(fèi)幾分鐘來(lái)下載其依賴(lài)項(xiàng),插件和實(shí)際項(xiàng)目的依賴(lài)項(xiàng)。)

    其他資源

    • Spring對(duì)AOP的介紹
    • dW: AOP @ Work:AOP的神話與現(xiàn)實(shí)
    • AspectJ in Action的第1章 ,第2部分。 ed。

    致謝

    我要感謝所有為我提供這篇文章和演示文稿的人,包括我的大學(xué),JRebel同學(xué)和GluonJ的合著者。 千葉繁

    參考: Holy Java博客上我們JCG合作伙伴 JakubHoly撰寫(xiě)的有關(guān) 使用AspectJ,Javassist和Java Proxy進(jìn)行代碼注入的實(shí)用介紹 。

    相關(guān)文章:

    • Spring和AspectJ的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)
    • 使用Spring AspectJ和Maven進(jìn)行面向方面的編程
    • 使用Spring AOP進(jìn)行面向方面的編程
    • 正確記錄應(yīng)用程序的10個(gè)技巧

    翻譯自: https://www.javacodegeeks.com/2011/09/practical-introduction-into-code.html

    總結(jié)

    以上是生活随笔為你收集整理的使用AspectJ,Javassist和Java Proxy进行代码注入的实用介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。