当前位置: 首页 > 知识库问答 >
问题:

继承接口方法的aspectj切入点

潘凯
2023-03-14

我想拦截所有java。sql。数据源。getConnection方法使用aspectj时,我使用了以下切入点:

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

它工作得很好。但我遇到了一些类,例如org。阿帕奇。公猫jdbc。水塘在该切入点不起作用的类层次结构中实现的数据源,其中数据源方法位于不实现数据源的层次结构中的类中,只有最顶层的类实现数据源:

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个答案

江展
2023-03-14

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

基类实现数据源方法,但不实现接口:

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; }
}

实现接口数据源的子类,从基类继承方法:

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

毫无意外,一切正常。在我看来,这是一种有效的方法。当然,方面代码将被编织到每个匹配公共java的方法中。sql。连接*。getConnection(…) 真的适用,将进行运行时检查,另请参阅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

  (...)
}

一、 e.如果当前实例不是数据源,则执行这些非常特殊的方法模式的类也会进行运行时检查。但这应该是罕见的。

还有一种涉及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);
  }
}

不幸的是,在我用来测试它的AeyJ版本中,AeyJ编译器抛出了一个异常。这可能是一个错误,我稍后会调查它并向维护者报告。更新:我为此问题创建了AeyJ错误票证#550494。更新2:该错误已在AeyJ 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))

当然,“方面不应该启动”在这里不再适用,因为现在我们确实希望它启动,当然,因为基类现在直接实现数据源接口。

有一点免责声明:这种方法只有在所有接口方法都确实存在于基类中时才有效,幸运的是,org就是这样。阿帕奇。公猫jdbc。水塘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日志,您不仅会注意到现在它说实现了javax。sql。数据源,但在旧版本中,这两种方法有22/32字节码指令,而在新版本中只有17/27。例如,在旧版本中,您可以看到instanceof#76//classjavax/sql/DataSource。在新版本中,不再需要检查实例。

您可以自己决定这是否值得使用ITD和本机语法。无论如何,我个人使用本机语法和ajc,所以我会这样做。如果您以前从未使用过AeyJ编译器,而是专门使用LTW,那么决定可能会有所不同。是否会有可衡量的性能提升是另一个问题。我假设在涉及SQL数据库调用的场景中,可能不是AeyJ消耗了您的性能。;-)我只是很想知道并回答你的问题。

更新:没有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);
  }
}

说明:

  • Advice以静态方式拦截负责查找“正常”情况下的所有方法执行,即实现接口和相应方法的(基类)
  • Advice动态截取处理(外来的)rest,即方法执行,其中实际实例实现了接口,但方法是在未实现接口的(基类)中定义的。与纯动态解不同的是,这里我明确排除了可以静态确定的情况

现在,如果我们将我的DataSourceConnectionAspect与这个替代SolutionAspect进行比较,这意味着什么?首先让我添加另一个示例类以使其更清晰:

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");
  }
}

其余的就像我上面的例子一样。

数据源连接方面的控制台日志:

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(…) 。如果他们只是做一些事情而不使用超级调用,那么每个方法调用当然只有一个日志行。

替代解决方案方面的控制台日志:

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、 美国:在超级调用的情况下,您自己的解决方案也会匹配两次,以防万一您想知道。但这两次都会动态匹配,速度会变慢。

 类似资料:
  • 问题内容: 人们经常会问诸如此类的AspectJ问题,因此我想在一个我以后可以轻松链接的地方回答它。 我有这个标记注释: 现在,我注释这样的接口和/或方法: 这是一个小的驱动程序应用程序,它也实现了该接口: 现在,当我定义此方面时,我希望它会被触发 为每个构造函数执行带注释的类,并 每次执行带注释的方法。 不幸的是,方面没有打印任何内容,就像类Application和方法two()没有任何@Mar

  • 我试图拦截一个用JAX-RS@POST标注的接口方法。我的切入点适用于所有非接口方法,如果@POST-注释直接位于被调用的方法。 拦截的接口方法: 要匹配方法的PointCut: 接口在v1com.myapp.social.webapi.包中,即使我将方法更改为公共AeyJ也不会拦截调用。 在我的切入点内有什么需要改变的吗?我怎样才能让它工作?

  • 问题内容: 如何编写适用于方法执行的AspectJ切入点,该方法执行会使用注释覆盖接口方法?例如: 仅当带有注释本身时,切入点才匹配。还有另一种方法吗? 问题答案: 正如Nicholas指出的那样,这在AspectJ中是不可能的。这是无法实现的更多证据(摘自http://www.eclipse.org/aspectj/doc/released/adk15notebook/annotations-

  • 问题内容: 我试图了解Java的行为。使用此接口: 我正在重载这样的方法: 当使用以下对象调用方法时: Java为什么使用: 代替 ? 谢谢 问题答案: 因为编译器只知道是的实例。在编译时根据所涉及表达式的编译时类型确定重载,并且is 的编译时类型为。 (将此与覆盖进行比较,在覆盖时,将根据所涉及的实际类型在 执行 时选择方法实现。)

  • 本文向大家介绍PHP接口继承及接口多继承原理与实现方法详解,包括了PHP接口继承及接口多继承原理与实现方法详解的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了PHP接口继承及接口多继承原理与实现方法。分享给大家供大家参考,具体如下: 在PHP的接口中,接口可以继承接口。虽然PHP类只能继承一个父类(单继承),但是接口和类不同,接口可以实现多继承,可以继承一个或者多个接口。当然接口的继承也是

  • 在Spring中将一个方面配置为: 是: } 如果我使用注释实现方法,正在按预期工作。但是如果注释在接口上,它就不起作用。这是正常行为还是我做错了什么?