java - 继承接口(interface)方法的aspectj切入点

标签 java aop aspectj pointcut

我想用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)


我的问题是,是否有更好的方法,而切入点是否可能对性能造成最坏的影响?

最佳答案

我在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))


当然,“方面不应该插入”在这里不再适用,因为现在我们确实希望它可以插入,因为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与此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中的静态方法可以是静态确定的,超级方法是动态确定的。同样,如果没有超级调用,情况3的每个方法调用将只有一行日志输出。

附注:如果您想知道,在超级呼叫的情况下,您自己的解决方案也会匹配两次。但是它会动态匹配两次,从而使其变慢。

关于java - 继承接口(interface)方法的aspectj切入点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57678474/

相关文章:

java - joinPoint.proceed() 有什么作用?

java - 如何捕获从@Around 抛出的连接点中的异常

java - 如何使用 Maven - intellij IDEA 在纯 java 项目中尝试 Dagger 2

java - 如何使用AOP拦截File、FileReader、FileWriter、FileInputStream、FileOutputStream的构造函数?

java - 错误 :(21, 87) 错误:找不到符号变量 send_to

java - 我如何在 Spring 3 中将 @Aspect 与 @Controller 结合起来?

java - 注释处理期间的 GWT RfValidator NPE

java - 建议中有关先前建议的信息

java - 在不引用 JPanel 本身的情况下处理多个 UI JPanel 中的 Swing 操作

java - 包含不解析模板文件