为了账号安全,请及时绑定邮箱和手机立即绑定

继承接口方法的 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负责查找“正常”情况下的所有方法执行,即同时实现接口和相应方法的(基)类。

建议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 关注
  • 107 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信