首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >继承接口方法的aspectj切入点

继承接口方法的aspectj切入点
EN

Stack Overflow用户
提问于 2019-08-27 16:00:49
回答 1查看 1.9K关注 0票数 0

我想用aspectj拦截所有的java.sql.DataSource.getConnection方法,我使用了这个切入点:

代码语言:javascript
运行
复制
"execution(public java.sql.Connection javax.sql.DataSource+.getConnection(..))"

效果很好。但是,我遇到了一些类,例如org.apache.tomcat.jdbc.pool.DataSource是在类层次结构中实现的,而这个切入点不起作用,其中DataSource方法位于不实现DataSource的层次结构中的类中,只有最顶层的类实现了DataSource:

代码语言:javascript
运行
复制
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方法实现。

我发现唯一有效的切入点是:

代码语言:javascript
运行
复制
execution(public java.sql.Connection *.getConnection(..)) && target(javax.sql.DataSource)

我的问题是,是否有一个更好的方法,以及这个切入点可能是最差的表现?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-08-28 02:26:44

我在MCVE中复制了您的情况如下:

实现DataSource 方法的基类,但不实现接口:

代码语言:javascript
运行
复制
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**,的子类从基类继承方法:**

代码语言:javascript
运行
复制
package de.scrum_master.app;

import javax.sql.DataSource;

public class SubClass extends BaseClass implements DataSource {}

驱动程序应用程序:

代码语言:javascript
运行
复制
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");
  }
}

方面:

这个方面使用当前使用的切入点。

代码语言:javascript
运行
复制
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);
  }
}

控制台日志:

代码语言:javascript
运行
复制
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输出:

代码语言:javascript
运行
复制
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 (类型间声明):您可以让基类直接实现接口,然后返回到使用更高效的原始切入点。在基于注释的语法中,这需要如下所示:

代码语言:javascript
运行
复制
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编译器会抛出一个异常。这可能是个bug,我稍后会研究它,并向维护人员报告。更新:为这个问题创建了AspectJ bug票证#550494。更新2:错误在AspectJ 1.9.5中修复。

但是,如果您只使用本机AspectJ语法,它就能工作。唯一的坏消息是,如果在类加载期间使用javac + LTW并依赖AspectJ编织器来完成方面,这将不再有效。您必须使用AspectJ编译器ajc以本机语法编译一个方面。

代码语言:javascript
运行
复制
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);
  }
}

现在控制台日志更改为:

代码语言:javascript
运行
复制
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添加它们,但我不打算在这里详细说明,我的答案已经很长了。

最后,但并非最不重要的是,这是新方法中字节代码的样子:

代码语言:javascript
运行
复制
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,尽管我认为这是一个干净而优雅的解决方案。但也有一种优化切入点匹配和性能的方法如下:

代码语言:javascript
运行
复制
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负责(外来的) rest,即实际实例实现接口的方法执行,但是该方法是在(基类而不是实现接口的)中定义的。与您自己的纯动态解的不同之处在于,在这里,我明确地排除了可以静态确定的情况。

如果我们把我的DataSourceConnectionAspect和这个AlternativeSolutionAspect比较一下,这意味着什么呢?首先,让我添加另一个示例类,以使其更清晰:

代码语言:javascript
运行
复制
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;
  }
}

现在,我们通过其他方法调用扩展驱动程序应用程序:

代码语言:javascript
运行
复制
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**:**的控制台日志

代码语言:javascript
运行
复制
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**:**的控制台日志

代码语言:javascript
运行
复制
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种情况,每个方法调用只会有一行日志输出。

P.S.:你自己的解决方案在超级电话的情况下也会匹配两次,以防你想知道。但这两次都会动态匹配,使得速度变慢。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57678474

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档