Java: 在方法调用前后注入代码
Java: Inject code before and after method call
我冒这个问题的风险知道很多其他问题 have been asked 以某种方式触及同一问题。但是它们要么很旧,要么非常具体,要么很难理解,而且答案也非常具体,似乎不适用于我的用例。
我想要实现的目标描述起来很简单,就是在 Java 中的方法调用前后注入代码。这是一个简单的例子。我想要类似这行代码的东西:
method(p1, @ANNOTATION(type=String) p2, p3);
脱糖到
Wrapper<String> w = new Wrapper<>(p2);
method(p1, w, p3);
p2 = w.get();
在更大的语句块的上下文中,具有预定义的 Wrapper
class。我对用于编译代码的构建环境的控制有限,因此任何解决方案都应该可以通过 Gradle plugins/dependencies.
实现
到目前为止我看过的内容(没有深入了解其中任何一个):
- Java 注释 : 在回答类似问题时从未认真考虑过,所以我猜他们无法做到这一点
- Aspect 框架,如 AspectJ 或 Spring Aspects:最常被提及,但它们似乎只能包装整个方法,而不是它们的特定调用
- 字节码操作框架 像 asm 或 cglib:似乎通常能够做到这一点,但非常重量级并且需要一些管道,尤其是。在构建方面,实现它
那么,有什么解决方案吗?
您可以使用 aspectj 来实现您想要的。
假设我们有一个编译后的 jar,其方法类似于您所描述的:
您想要拦截从应用程序中的特定位置(-s)发出的对 logStuff
方法的调用,并更改逻辑以将参数包装在 DummyContainer
对象中。您的代码可能如下所示。
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public static @interface DummyAnnotation {
Class type();
}
//that is your target method
public static void logStuff(@DummyAnnotation(type = String.class) Object data) {
if(data instanceof String) {
System.out.println("String: " + data);
} else {
System.out.println("Not a string " + data);
}
}
虚拟容器class:
public static class DummyContainer {
private String string;
private Class clazz;
public DummyContainer(String string, Class clazz) {
this.clazz = clazz;
this.string = string;
}
@Override
public String toString() {
return "DummyContainer=[clazz: " + clazz + ", string: " + string + "]";
}
}
看点:
@Aspect
public class WrapperAspect {
@Pointcut("call(* "
/**method to intercept*/
+ "com.yourpackage.YourType.logStuff(Object)) && args(param) && "
/**only calls made from within that location
* will be intercepted. Remove it to intercept
* calls from everywhere*/
+ "within(test.Runner)")
public void logStuffPointcut(Object param) {}
@Around("logStuffPointcut(param)")
public void simpleWrap(Object param, ProceedingJoinPoint jp) throws Throwable {
String calledMethodName = jp.getSignature().getName();
Class type = jp.getSignature().getDeclaringType();
Method method = type.getDeclaredMethod(calledMethodName, Object.class);
DummyAnnotation annotation = method.getParameters()[0].getAnnotation(DummyAnnotation.class);
DummyContainer wrapped = new DummyContainer((String) param, (Class)annotation.type());
//code before method call
jp.proceed(new Object[] {wrapped});
//code after method call
}
}
test.Runner
class 中调用 logStuff
的方法
public static void main(String[] args) {
Object instance = "thats a string";
System.out.println("Obj type : " + instance.getClass());
YourType.logStuff(instance);
System.out.println("Obj type : " + instance.getClass());
}
输出:
Obj type : class java.lang.String
Not a string DummyContainer=[clazz: class java.lang.String, string: thats a string]
Obj type : class java.lang.String
为了构建项目,我使用 Maven。这是我的 pom.xml
的构建部分的样子:
...
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
<configuration>
<Xajruntimetarget>1.8</Xajruntimetarget>
<complianceLevel>1.8</complianceLevel>
<weaveDependencies>
<weaveDependency>
<groupId>your.jar.group.id</groupId>
<artifactId>artifactid</artifactId>
</weaveDependency>
</weaveDependencies>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
...
在 SO 上,答案是在评论中而不是在答案中提出的。所以我从上面的评论中挑选出在我看来至少是最有希望进一步追求的方法:
我冒这个问题的风险知道很多其他问题 have been asked 以某种方式触及同一问题。但是它们要么很旧,要么非常具体,要么很难理解,而且答案也非常具体,似乎不适用于我的用例。
我想要实现的目标描述起来很简单,就是在 Java 中的方法调用前后注入代码。这是一个简单的例子。我想要类似这行代码的东西:
method(p1, @ANNOTATION(type=String) p2, p3);
脱糖到
Wrapper<String> w = new Wrapper<>(p2);
method(p1, w, p3);
p2 = w.get();
在更大的语句块的上下文中,具有预定义的 Wrapper
class。我对用于编译代码的构建环境的控制有限,因此任何解决方案都应该可以通过 Gradle plugins/dependencies.
到目前为止我看过的内容(没有深入了解其中任何一个):
- Java 注释 : 在回答类似问题时从未认真考虑过,所以我猜他们无法做到这一点
- Aspect 框架,如 AspectJ 或 Spring Aspects:最常被提及,但它们似乎只能包装整个方法,而不是它们的特定调用
- 字节码操作框架 像 asm 或 cglib:似乎通常能够做到这一点,但非常重量级并且需要一些管道,尤其是。在构建方面,实现它
那么,有什么解决方案吗?
您可以使用 aspectj 来实现您想要的。
假设我们有一个编译后的 jar,其方法类似于您所描述的:
您想要拦截从应用程序中的特定位置(-s)发出的对 logStuff
方法的调用,并更改逻辑以将参数包装在 DummyContainer
对象中。您的代码可能如下所示。
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public static @interface DummyAnnotation {
Class type();
}
//that is your target method
public static void logStuff(@DummyAnnotation(type = String.class) Object data) {
if(data instanceof String) {
System.out.println("String: " + data);
} else {
System.out.println("Not a string " + data);
}
}
虚拟容器class:
public static class DummyContainer {
private String string;
private Class clazz;
public DummyContainer(String string, Class clazz) {
this.clazz = clazz;
this.string = string;
}
@Override
public String toString() {
return "DummyContainer=[clazz: " + clazz + ", string: " + string + "]";
}
}
看点:
@Aspect
public class WrapperAspect {
@Pointcut("call(* "
/**method to intercept*/
+ "com.yourpackage.YourType.logStuff(Object)) && args(param) && "
/**only calls made from within that location
* will be intercepted. Remove it to intercept
* calls from everywhere*/
+ "within(test.Runner)")
public void logStuffPointcut(Object param) {}
@Around("logStuffPointcut(param)")
public void simpleWrap(Object param, ProceedingJoinPoint jp) throws Throwable {
String calledMethodName = jp.getSignature().getName();
Class type = jp.getSignature().getDeclaringType();
Method method = type.getDeclaredMethod(calledMethodName, Object.class);
DummyAnnotation annotation = method.getParameters()[0].getAnnotation(DummyAnnotation.class);
DummyContainer wrapped = new DummyContainer((String) param, (Class)annotation.type());
//code before method call
jp.proceed(new Object[] {wrapped});
//code after method call
}
}
test.Runner
class 中调用 logStuff
public static void main(String[] args) {
Object instance = "thats a string";
System.out.println("Obj type : " + instance.getClass());
YourType.logStuff(instance);
System.out.println("Obj type : " + instance.getClass());
}
输出:
Obj type : class java.lang.String Not a string DummyContainer=[clazz: class java.lang.String, string: thats a string] Obj type : class java.lang.String
为了构建项目,我使用 Maven。这是我的 pom.xml
的构建部分的样子:
... ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.2</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.11</version> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.8.13</version> </dependency> </dependencies> <configuration> <Xajruntimetarget>1.8</Xajruntimetarget> <complianceLevel>1.8</complianceLevel> <weaveDependencies> <weaveDependency> <groupId>your.jar.group.id</groupId> <artifactId>artifactid</artifactId> </weaveDependency> </weaveDependencies> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ... ...
在 SO 上,答案是在评论中而不是在答案中提出的。所以我从上面的评论中挑选出在我看来至少是最有希望进一步追求的方法: