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.

实现

到目前为止我看过的内容(没有深入了解其中任何一个):

那么,有什么解决方案吗?

您可以使用 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 上,答案是在评论中而不是在答案中提出的。所以我从上面的评论中挑选出在我看来至少是最有希望进一步追求的方法:

使用像ASM or cglib这样的字节码操作库。