亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

繼承接口方法的 aspectj 切入點

繼承接口方法的 aspectj 切入點

慕田峪9158850 2023-06-21 14:50:51
我想用aspectj攔截所有java.sql.DataSource.getConnection方法,我使用了這個切入點:"execution(public java.sql.Connection javax.sql.DataSource+.getConnection(..))"效果很好。但是我遇到了一些類,例如 org.apache.tomcat.jdbc.pool.DataSource 是在類層次結構中實現的,這個切入點不起作用,其中 DataSource 方法位于層次結構中的一個類中,該層次結構不實現 DataSource ,只有最頂層的類實現了DataSource:class BaseDataSource {    public Connection getConnection() throws SQLException {        return null;    }    public Connection getConnection(String username, String password) throws SQLException {        return null;    }   implements all DataSource Methods...}class MyDataSource extends BaseDataSource implements java.sql.DataSource{           //does not implement DataSource methods}BaseDataSource不實現DataSource,但具有所有DataSource方法的實現。我發現唯一有效的切入點是:execution(public java.sql.Connection *.getConnection(..)) && target(javax.sql.DataSource)我的問題是否有更好的方法以及這個切入點的性能是否可能最差?
查看完整描述

1 回答

?
蝴蝶刀刀

TA貢獻1801條經驗 獲得超8個贊

我在MCVE中復制了您的情況,如下所示:

基類實現DataSource方法,但不是接口:

package de.scrum_master.app;


import java.io.PrintWriter;

import java.sql.Connection;

import java.sql.SQLException;

import java.sql.SQLFeatureNotSupportedException;

import java.util.logging.Logger;


public class BaseClass {

? public PrintWriter getLogWriter() throws SQLException { return null; }

? public void setLogWriter(PrintWriter out) throws SQLException {}

? public void setLoginTimeout(int seconds) throws SQLException {}

? public int getLoginTimeout() throws SQLException { return 0; }

? public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; }

? public <T> T unwrap(Class<T> iface) throws SQLException { return null; }

? public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; }

? public Connection getConnection() throws SQLException { return null; }

? public Connection getConnection(String username, String password) throws SQLException { return null; }

}

子類實現接口DataSource,從基類繼承方法:


package de.scrum_master.app;


import javax.sql.DataSource;


public class SubClass extends BaseClass implements DataSource {}

驅動程序應用:


package de.scrum_master.app;


import java.sql.SQLException;


public class Application {

? public static void main(String[] args) throws SQLException {

? ? System.out.println("Aspect should not kick in");

? ? new BaseClass().getConnection();

? ? new BaseClass().getConnection("user", "pw");


? ? System.out.println("Aspect should kick in");

? ? new SubClass().getConnection();

? ? new SubClass().getConnection("user", "pw");

? }

}

方面:


此方面使用您當前正在使用的切入點。


package de.scrum_master.aspect;


import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;


@Aspect

public class DataSourceConnectionAspect {

? @Before("execution(public java.sql.Connection *.getConnection(..)) && target(javax.sql.DataSource)")

? public void myAdvice(JoinPoint thisJoinPoint) {

? ? System.out.println(thisJoinPoint);

? }

}

控制臺日志:


Aspect should not kick in

Aspect should kick in

execution(Connection de.scrum_master.app.BaseClass.getConnection())

execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

這里沒有什么意外,一切都按預期進行。在我看來,這是一種有效的方法。當然,方面代碼將被編織到每個匹配的方法中,如果真的適用,public java.sql.Connection *.getConnection(..))將進行運行時檢查,另請參見輸出:target(javax.sql.DataSource)javap


Compiled from "BaseClass.java"

public class de.scrum_master.app.BaseClass {

? (...)


? public java.sql.Connection getConnection() throws java.sql.SQLException;

? ? Code:

? ? ? ?0: aload_0

? ? ? ?1: instanceof? ? #76? ? ? ? ? ? ? ? ?// class javax/sql/DataSource

? ? ? ?4: ifeq? ? ? ? ? 21

? ? ? ?7: invokestatic? #70? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.aspectOf:()Lde/scrum_master/aspect/DataSourceConnectionAspect;

? ? ? 10: getstatic? ? ?#58? ? ? ? ? ? ? ? ?// Field ajc$tjp_0:Lorg/aspectj/lang/JoinPoint$StaticPart;

? ? ? 13: aload_0

? ? ? 14: aload_0

? ? ? 15: invokestatic? #64? ? ? ? ? ? ? ? ?// Method org/aspectj/runtime/reflect/Factory.makeJP:(Lorg/aspectj/lang/JoinPoint$StaticPart;Ljava/lang/Object;Ljava/lang/Object;)Lorg/aspectj/lang/JoinPoint;

? ? ? 18: invokevirtual #74? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.myAdvice:(Lorg/aspectj/lang/JoinPoint;)V

? ? ? 21: aconst_null

? ? ? 22: areturn


? public java.sql.Connection getConnection(java.lang.String, java.lang.String) throws java.sql.SQLException;

? ? Code:

? ? ? ?0: aload_1

? ? ? ?1: astore? ? ? ? 4

? ? ? ?3: aload_2

? ? ? ?4: astore? ? ? ? 5

? ? ? ?6: aload_0

? ? ? ?7: instanceof? ? #76? ? ? ? ? ? ? ? ?// class javax/sql/DataSource

? ? ? 10: ifeq? ? ? ? ? 31

? ? ? 13: invokestatic? #70? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.aspectOf:()Lde/scrum_master/aspect/DataSourceConnectionAspect;

? ? ? 16: getstatic? ? ?#79? ? ? ? ? ? ? ? ?// Field ajc$tjp_1:Lorg/aspectj/lang/JoinPoint$StaticPart;

? ? ? 19: aload_0

? ? ? 20: aload_0

? ? ? 21: aload? ? ? ? ?4

? ? ? 23: aload? ? ? ? ?5

? ? ? 25: invokestatic? #82? ? ? ? ? ? ? ? ?// Method org/aspectj/runtime/reflect/Factory.makeJP:(Lorg/aspectj/lang/JoinPoint$StaticPart;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Lorg/aspectj/lang/JoinPoint;

? ? ? 28: invokevirtual #74? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.myAdvice:(Lorg/aspectj/lang/JoinPoint;)V

? ? ? 31: aconst_null

? ? ? 32: areturn


? (...)

}

即,如果當前實例不是DataSource. 但這種情況應該很少見。


有一種涉及 ITD(類型間聲明)的替代方案:您可以使基類直接實現接口,然后返回使用更高效的原始切入點。在基于注釋的語法中,它會像這樣:


package de.scrum_master.aspect;


import javax.sql.DataSource;


import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.DeclareParents;


@Aspect

public class DataSourceConnectionAspect {

? @DeclareParents("de.scrum_master.app.BaseClass")

? private DataSource dataSource;


? @Before("execution(public java.sql.Connection javax.sql.DataSource+.getConnection(..))")

? public void myAdvice(JoinPoint thisJoinPoint) {

? ? System.out.println(thisJoinPoint);

? }

}

不幸的是,對于我用來測試它的 AspectJ 版本,AspectJ 編譯器拋出異常。這可能是一個錯誤,我稍后會調查并報告給維護者。更新:我為這個問題創建了AspectJ bug ticket #550494 。更新 2:該錯誤已在 AspectJ 1.9.5 中修復。


但如果您只使用本機 AspectJ 語法,它就可以工作。唯一的壞消息是,如果您使用javac + LTW 并依賴 AspectJ weaver 在類加載期間完成方面,這將不再有效。您必須使用 AspectJ 編譯器ajc以本機語法編譯切面。


package de.scrum_master.aspect;


import javax.sql.DataSource;


import de.scrum_master.app.BaseClass;


public aspect DataSourceConnectionAspect {

? declare parents: BaseClass implements DataSource;


? before() : execution(public java.sql.Connection javax.sql.DataSource+.getConnection(..)) {

? ? System.out.println(thisJoinPoint);

? }

}

現在控制臺日志更改為:


Aspect should not kick in

execution(Connection de.scrum_master.app.BaseClass.getConnection())

execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

Aspect should kick in

execution(Connection de.scrum_master.app.BaseClass.getConnection())

execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

當然,“Aspect should not kick in”在這里不再適用,因為現在我們確實期望它會啟動,當然,因為BaseClass現在直接實現了DataSource接口。


一點免責聲明:只有當所有接口方法都確實存在于基類中時,這種方法才有效,幸運的是,org.apache.tomcat.jdbc.pool.DataSourceProxy您可以相應地調整我的方面。如果基類僅實現部分預期的接口方法,您也可以通過 ITD 以本機語法添加它們,但我不打算在這里詳細說明,我的答案已經很長了。


最后但并非最不重要的一點是,新方法的字節碼如下所示:


Compiled from "BaseClass.java"

public class de.scrum_master.app.BaseClass implements javax.sql.DataSource {

? (...)


? public java.sql.Connection getConnection() throws java.sql.SQLException;

? ? Code:

? ? ? ?0: getstatic? ? ?#58? ? ? ? ? ? ? ? ?// Field ajc$tjp_0:Lorg/aspectj/lang/JoinPoint$StaticPart;

? ? ? ?3: aload_0

? ? ? ?4: aload_0

? ? ? ?5: invokestatic? #64? ? ? ? ? ? ? ? ?// Method org/aspectj/runtime/reflect/Factory.makeJP:(Lorg/aspectj/lang/JoinPoint$StaticPart;Ljava/lang/Object;Ljava/lang/Object;)Lorg/aspectj/lang/JoinPoint;

? ? ? ?8: astore_1

? ? ? ?9: invokestatic? #70? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.aspectOf:()Lde/scrum_master/aspect/DataSourceConnectionAspect;

? ? ? 12: aload_1

? ? ? 13: invokevirtual #74? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.ajc$before$de_scrum_master_aspect_DataSourceConnectionAspect$1$19879111:(Lorg/aspectj/lang/JoinPoint;)V

? ? ? 16: aconst_null

? ? ? 17: areturn


? public java.sql.Connection getConnection(java.lang.String, java.lang.String) throws java.sql.SQLException;

? ? Code:

? ? ? ?0: aload_1

? ? ? ?1: astore? ? ? ? 4

? ? ? ?3: aload_2

? ? ? ?4: astore? ? ? ? 5

? ? ? ?6: getstatic? ? ?#77? ? ? ? ? ? ? ? ?// Field ajc$tjp_1:Lorg/aspectj/lang/JoinPoint$StaticPart;

? ? ? ?9: aload_0

? ? ? 10: aload_0

? ? ? 11: aload? ? ? ? ?4

? ? ? 13: aload? ? ? ? ?5

? ? ? 15: invokestatic? #80? ? ? ? ? ? ? ? ?// Method org/aspectj/runtime/reflect/Factory.makeJP:(Lorg/aspectj/lang/JoinPoint$StaticPart;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Lorg/aspectj/lang/JoinPoint;

? ? ? 18: astore_3

? ? ? 19: invokestatic? #70? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.aspectOf:()Lde/scrum_master/aspect/DataSourceConnectionAspect;

? ? ? 22: aload_3

? ? ? 23: invokevirtual #74? ? ? ? ? ? ? ? ?// Method de/scrum_master/aspect/DataSourceConnectionAspect.ajc$before$de_scrum_master_aspect_DataSourceConnectionAspect$1$19879111:(Lorg/aspectj/lang/JoinPoint;)V

? ? ? 26: aconst_null

? ? ? 27: areturn


? (...)

}

如果您比較這兩個javap日志,您不僅會注意到現在它說implements javax.sql.DataSource,而且在舊版本中,這兩種方法有 22/32 字節碼指令,而在新版本中只有 17/27。例如,在舊版本中您會看到instanceof #76 // class javax/sql/DataSource. 在新版本中instanceof不再需要檢查。


您可以自行決定這是否值得您使用 ITD 和本機語法。無論如何,我個人使用本機語法和ajc,所以我會這樣做。如果您以前從未使用過 AspectJ 編譯器并且專門使用 LTW,那么決定可能會有所不同。是否會有可衡量的性能提升是另一個問題。我假設在涉及 SQL 數據庫調用的場景中,可能不是 AspectJ 會消耗您的性能。;-) 我只是想知道并回答您的問題。


更新:沒有 ITD 的替代解決方案


根據您的評論,您希望避免 ITD,盡管我認為這是一個干凈而優雅的解決方案。但還有一種方法可以優化切入點匹配和性能,如下所示:


package de.scrum_master.aspect;


import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;


@Aspect

public class AlternativeSolutionAspect {

? @Pointcut("execution(public java.sql.Connection getConnection(..))")

? private static void getConnection() {}


? @Pointcut("within(javax.sql.DataSource+)")

? private static void withinDataSource() {}


? @Pointcut("target(javax.sql.DataSource)")

? private static void targetDataSource() {}


? @Before("withinDataSource() && getConnection()")

? public void interceptStatically(JoinPoint thisJoinPoint) {

? ? System.out.println("[static] " + thisJoinPoint);

? }


? @Before("!withinDataSource() && getConnection() && targetDataSource()")

? public void interceptDynamically(JoinPoint thisJoinPoint) {

? ? System.out.println("[dynamic] " + thisJoinPoint);

? }

}

解釋:


建議interceptStatically負責查找“正?!鼻闆r下的所有方法執行,即同時實現接口和相應方法的(基)類。

建議interceptDynamically負責(外來的)其余部分,即實際實例實現接口的方法執行,但該方法是在未實現接口的(基)類中定義的。與您自己的純動態解決方案的區別在于,這里我明確排除了可以靜態確定的情況。

DataSourceConnectionAspect現在,如果我們將 my與 this進行比較,那意味著什么AlternativeSolutionAspect?首先讓我添加另一個示例類以使其更清楚:


package de.scrum_master.app;


import java.sql.Connection;

import java.sql.SQLException;


import javax.sql.DataSource;


public class SubClassOverridingMethods extends BaseClass implements DataSource {

? @Override

? public Connection getConnection() throws SQLException {

? ? return super.getConnection();

//? ? return null;

? }


? @Override

? public Connection getConnection(String username, String password) throws SQLException {

? ? return super.getConnection(username, password);

//? ? return null;

? }

}

現在我們通過額外的方法調用來擴展驅動程序應用程序:


package de.scrum_master.app;


import java.sql.SQLException;


public class Application {

? public static void main(String[] args) throws SQLException {

? ? System.out.println("Aspect should not kick in without ITD, but should with ITD");

? ? new BaseClass().getConnection();

? ? new BaseClass().getConnection("user", "pw");


? ? System.out.println("Aspect should kick in");

? ? new SubClass().getConnection();

? ? new SubClass().getConnection("user", "pw");


? ? System.out.println("Aspect should kick in");

? ? new SubClassOverridingMethods().getConnection();

? ? new SubClassOverridingMethods().getConnection("user", "pw");

? }

}

其余的仍然像我上面的例子一樣。


控制臺日志DataSourceConnectionAspect:


Aspect should not kick in without ITD, but should with ITD

execution(Connection de.scrum_master.app.BaseClass.getConnection())

execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

Aspect should kick in

execution(Connection de.scrum_master.app.BaseClass.getConnection())

execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

Aspect should kick in

execution(Connection de.scrum_master.app.SubClassOverridingMethods.getConnection())

execution(Connection de.scrum_master.app.BaseClass.getConnection())

execution(Connection de.scrum_master.app.SubClassOverridingMethods.getConnection(String, String))

execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

在情況 3 中,您會看到 2 個方法調用的 4 行日志輸出,因為重寫方法調用了super.getConnection(..). 當然,如果他們只是在不使用超級調用的情況下做某事,那么每個方法調用只會有一個日志行。


控制臺日志AlternativeSolutionAspect:


Aspect should not kick in without ITD, but should with ITD

Aspect should kick in

[dynamic] execution(Connection de.scrum_master.app.BaseClass.getConnection())

[dynamic] execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

Aspect should kick in

[static] execution(Connection de.scrum_master.app.SubClassOverridingMethods.getConnection())

[dynamic] execution(Connection de.scrum_master.app.BaseClass.getConnection())

[static] execution(Connection de.scrum_master.app.SubClassOverridingMethods.getConnection(String, String))

[dynamic] execution(Connection de.scrum_master.app.BaseClass.getConnection(String, String))

由于我們在這里不使用 ITD,因此情況 1 不會攔截任何內容。情況 2 是動態攔截的,而在情況 3 中,覆蓋方法可以靜態確定,超級方法可以動態確定。同樣,如果沒有 super 調用,對于情況 3,每個方法調用只會有一行日志輸出。


PS:如果您想知道,您自己的解決方案在超級調用的情況下也會匹配兩次。但它兩次都會動態匹配,從而使其速度變慢。


查看完整回答
反對 回復 2023-06-21
  • 1 回答
  • 0 關注
  • 141 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號