继承接口方法的 aspectj 切入点
aspectj pointcut for inherited interface methods
我想用 aspectj 拦截所有 java.sql.DataSource.getConnection 方法,
我使用了这个切入点:
"execution(public java.sql.Connection javax.sql.DataSource+.getConnection(..))"
它工作正常。
但是我遇到了一些 classes ,例如 org.apache.tomcat.jdbc.pool.DataSource 是在 class 层次结构中实现的,这个切入点不起作用,其中 DataSource 方法在 class 中不实现 DataSource 的层次结构,只有最顶层 class 实现 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 中复制了您的情况,如下所示:
Base class 实现了 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; }
}
Subclass 实现接口 DataSource
,从基础 class:
继承方法
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
,运行时检查也会发生 classes 碰巧实现这些非常特殊的方法模式。不过这种情况应该很少见。
有一种涉及 ITD(类型间声明)的替代方法:您可以让基础 class 直接实现接口,然后 return 使用更高效的原始切入点。在基于注释的语法中,它会像这样:
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 在 class 加载期间完成方面,这将不再有效。您必须使用 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
接口。
一点免责声明:只有当所有接口方法确实存在于基 class 中时,这种方法才有效,幸运的是 org.apache.tomcat.jdbc.pool.DataSourceProxy
就是这种情况,即您可以相应地调整我的方面。如果基础class只实现部分预期的接口方法,你也可以通过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_DataSourceConnectionAspect879111:(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_DataSourceConnectionAspect879111:(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
负责查找 "normal" 案例的所有方法执行,即(基础)classes 都实现了接口和相应的方法。
- Advice
interceptDynamically
处理(奇异的)rest,即实际实例实现接口的方法执行,但该方法是在(基础)class [=108= 中定义的]not 实现接口。与您自己的纯动态解决方案的不同之处在于,我在这里明确排除了可以静态确定的情况。
现在,如果我们将我的 DataSourceConnectionAspect
与这个 AlternativeSolutionAspect
进行比较,这意味着什么?首先让我添加另一个示例 class 以使其更清楚:
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(..)
。如果他们只是做一些事情而不使用 super 调用,那么当然每个方法调用只会有一个日志行。
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,每个方法调用我们只会输出一行日志。
P.S.: 你自己的解决方案在超级调用的情况下也会匹配两次,以防万一你想知道。但它会动态匹配两次,使其变慢。
我想用 aspectj 拦截所有 java.sql.DataSource.getConnection 方法, 我使用了这个切入点:
"execution(public java.sql.Connection javax.sql.DataSource+.getConnection(..))"
它工作正常。 但是我遇到了一些 classes ,例如 org.apache.tomcat.jdbc.pool.DataSource 是在 class 层次结构中实现的,这个切入点不起作用,其中 DataSource 方法在 class 中不实现 DataSource 的层次结构,只有最顶层 class 实现 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 中复制了您的情况,如下所示:
Base class 实现了 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; }
}
Subclass 实现接口 DataSource
,从基础 class:
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
,运行时检查也会发生 classes 碰巧实现这些非常特殊的方法模式。不过这种情况应该很少见。
有一种涉及 ITD(类型间声明)的替代方法:您可以让基础 class 直接实现接口,然后 return 使用更高效的原始切入点。在基于注释的语法中,它会像这样:
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 在 class 加载期间完成方面,这将不再有效。您必须使用 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
接口。
一点免责声明:只有当所有接口方法确实存在于基 class 中时,这种方法才有效,幸运的是 org.apache.tomcat.jdbc.pool.DataSourceProxy
就是这种情况,即您可以相应地调整我的方面。如果基础class只实现部分预期的接口方法,你也可以通过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_DataSourceConnectionAspect879111:(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_DataSourceConnectionAspect879111:(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
负责查找 "normal" 案例的所有方法执行,即(基础)classes 都实现了接口和相应的方法。 - Advice
interceptDynamically
处理(奇异的)rest,即实际实例实现接口的方法执行,但该方法是在(基础)class [=108= 中定义的]not 实现接口。与您自己的纯动态解决方案的不同之处在于,我在这里明确排除了可以静态确定的情况。
现在,如果我们将我的 DataSourceConnectionAspect
与这个 AlternativeSolutionAspect
进行比较,这意味着什么?首先让我添加另一个示例 class 以使其更清楚:
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(..)
。如果他们只是做一些事情而不使用 super 调用,那么当然每个方法调用只会有一个日志行。
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,每个方法调用我们只会输出一行日志。
P.S.: 你自己的解决方案在超级调用的情况下也会匹配两次,以防万一你想知道。但它会动态匹配两次,使其变慢。