Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: Inject code before and after method call

I'm risking this question knowing quite a few other questions have been asked that somehow touch the same issue. But they are either quite old, or very specific, or hard to follow, and the answers are also very specific and seem not applicable to my use case.

What I want to achieve is quite simple to describe, it is about injecting code before and after a method call in Java. Here is a simple example. I want something like this line of code:

method(p1, @ANNOTATION(type=String) p2, p3);

to desugar to

Wrapper<String> w = new Wrapper<>(p2);
method(p1, w, p3);
p2 = w.get();

in the context of a larger block of statements, with a pre-defined Wrapper class. I have limited control over the build environment that is used to compile the code, so any solution should be realizable with Gradle plugins/dependencies.

What I've looked at so far (without having a deep understanding of any of them):

  • Java Annotations : are never seriously considered in answers to similar questions, so I guess they are incapable of achieving that
  • Aspect frameworks like AspectJ or Spring Aspects: are mentioned most often, but they only seem to be able to wrap entire methods, not specific invocations of them
  • Bytcode manipulation frameworks like asm or cglib: seem generally capable of doing this, but are quite heavy-weight and require some plumbing, esp. on the build side, to achieve it

So, what could be a solution for this?

like image 252
ThomasH Avatar asked Aug 25 '26 04:08

ThomasH


1 Answers

You could use aspectj to achieve what you want. Lets say we have a compiled jar with a method similar to what you have described: You want to intercept calls to logStuff method made from certain location(-s) in your application and change the logic to wrap the argument in DummyContainer object. Here is how your code might look like.



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

Dummy container 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:



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

Method within test.Runner class that calls 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());
    }

Output:

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

To build the project I use Maven. Here is how the build section of my pom.xml looks like:

    ...
    ...
        <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>
    ...
    ...
like image 169
Mikhail Avatar answered Aug 28 '26 07:08

Mikhail



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!