0

Actually, it will be more complex question. I want use AspectJ only in test purpose. Have found suggestion to use if() JointPoint and some static boolean field. Also, first I start using aspect as inner static class of my base test method. After some experiments I replaced it to own class, but actually don’t got the result, that I want. So, I just create some test project. Maven pom.xml:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Testing</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <mockito.version>3.11.2</mockito.version>
        <aspectj.version>1.9.7</aspectj.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
                <dependencies>
                    <!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-surefire-provider
-->
                    <dependency>
                        <groupId>org.junit.platform</groupId>
                        <artifactId>junit-platform-surefire-provider</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                </dependencies>
                <!--<configuration>
                    <argLine>-javaagent:${user.home}/.m2/repository/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar</argLine>
                </configuration>-->
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.14.0</version>
                <configuration>
                    <complianceLevel>${maven.compiler.source}</complianceLevel>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <showWeaveInfo>true</showWeaveInfo>
                    <verbose>true</verbose>
                    <Xlint>ignore</Xlint>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <!-- use this goal to weave all your main classes -->
                            <goal>compile</goal>
                            <!-- use this goal to weave all your test classes -->
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Classes: A:

package classes;

public class A {
    private String a = "classes.A";

    public String getA()
    {
        return a;
    }

    public String getFromB()
    {
        return new B().getB();
    }
}

B:

package classes;

public class B {
    private String b = "classes.B";

    public String getB() {
        return b;
    }
}

test class:

package aspectj;

import classes.A;
import classes.B;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;


public class NewTest {
    private static boolean useAspect = false;

    public static boolean isUseAspect() {
        return useAspect;
    }

    @BeforeEach
    void init()
    {
        useAspect = true;
    }

    @Test
    public void changeValue()
    {
        B b = new B();
        System.out.println(b.getB());
    }

    @Test
    public void changeValueInA()
    {
        A a = new A();
        System.out.println(a.getFromB());
    }
}

Aspect class

package aspectj;

import org.aspectj.lang.Aspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectB {

    @Pointcut("if()")
    public static boolean useAspect()
    {
        return NewTest.isUseAspect();
    }

    @Pointcut("call(* classes.B.getB())")
    public void callTestMethod() {}


    @Around("callTestMethod()")
    public String myAdvice(ProceedingJoinPoint point) throws Throwable {
        return "You have been hacked!";
    }
}

Main class:

package classes;

public class TestOutputHere {
    public static void main(String[] args) {
        System.out.println(new A().getFromB());
    }
}

And I got result after running test and main method:

  • changeValue() -> You have been hacked!
  • changeValueInA() -> classes.B
  • main(String[] args) -> classes.B

Second result dont feet for me... So after some experiments and removing test scope for AspectJ dependencies, removing if() JointPoint (we can't use test classes from src) and placing Aspect class in src I got the result:

  • changeValue() -> You have been hacked!
  • changeValueInA() -> You have been hacked!
  • main(String[] args) -> You have been hacked!

Lust result dont feet to me too. And I really don’t want to use aspect for all project. After that I just tried to use some Load-Time weaving with configuration for maven surefire plugin:

<configuration>
 <argLine>
-javaagent:${user.home}/.m2/repository/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
</argLine>
</configuration>

And I got result, that I want:

  • changeValue() -> You have been hacked!
  • changeValueInA() -> You have been hacked!
  • main(String[] args) -> classes.B

So, where the question after thousands of these letters?) Questions are:

  1. Can I get this result with compile weaving and without using AspectJ classLoader?
  2. As I have performance restrictions in real project - how can AspectJ classLoader affect performance of non-test environment in this case?
  3. In case of load-time weaving that I describe - all classes of the project will recompile by AspectJ? Only test? How recompiling work in load-time?

I will greatfull for this answers!

1 Answers1

0

You have several problems in your code:

  • You set useAspect = true before each test, but never reset to false after a test ends. This would bleed context out into other tests where you want the aspect inactive. You should clean that up.

  • The aspect has an if() pointcut depending on a static method in a test class. The test class is unavailable during application runtime under normal circumstances. The static field and its accessor methods (if any) should be in the aspect class itself.

    package aspectj;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    @Aspect
    public class AspectB {
      private static boolean useAspect = false;
    
      public static void setUseAspect(boolean useAspect) {
        AspectB.useAspect = useAspect;
      }
    
      @Pointcut("call(* classes.B.getB()) && if()")
      public static boolean callTestMethod() {
        return useAspect;
      }
    
      @Around("callTestMethod()")
      public String myAdvice(ProceedingJoinPoint point) throws Throwable {
        return "You have been hacked!";
      }
    }
    
    package aspectj;
    
    import classes.A;
    import classes.B;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    
    public class NewTest {
      @BeforeEach
      void init() {
        AspectB.setUseAspect(true);
      }
    
      @AfterEach
      void cleanUp() {
        AspectB.setUseAspect(false);
      }
    
      @Test
      public void changeValue() {
        B b = new B();
        System.out.println(b.getB());
      }
    
      @Test
      public void changeValueInA() {
        A a = new A();
        System.out.println(a.getFromB());
      }
    }
    
  • Probably, your aspect is defined in src/test/java instead of src/main/java, which explains why it is only compiled into test classes and not into application classes. But the latter is what you expect, if a method call from one application class to another should be intercepted. Therefore, you need to move the aspect to the main sources and make aspectjrt have a compile scope, not a test scope.

But in this case where the aspect is supposed to only affect tests, I would recommend to not use compile-time weaving (CTW), because it would mean that the application always needs the AspectJ runtime on its class path (see above), even if the aspect is inactive. CTW only makes sense if at least sometimes during application runtime the aspect is meant to be active, too. Even then, it is debatable if load-time weaving (LTW) might not be the better solution, e.g. if it is a rarely used debugging aspect. CTW is ideal for production aspects. In this case, it seems to be fairly clear that LTW using the Java agent is the right approach. Like you said, you do not even need the ugly static field and the if() pointcut.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • The best way to thank people on SO is to accept and upvote correct answers. – kriegaex Aug 23 '21 at 18:13
  • Yes, I was planning to answer more complexly, but have a luck of time this days. And as you said I really use Aspect from src/test/java instead of src/main/java. The only thing is that You didn't answered about LTW (third question), but I have read about it in another sources and clarify how it works. Thanks again! – Taras Yatsura Aug 26 '21 at 08:02
  • Taras, that is a pretty lame excuse for a developer. In general, if you don't have enough time to write a good question, you should refrain from asking at all and wait until you have more time. You cannot expect good answers to bad questions - garbage in, garbage out. This time, you were lucky that I guessed correctly what you wanted to know. It is also important to focus and not ask multiple questions at once. As for how LTW works, the question was completely unrelated to the problem presented in your code, so I chose not to answer it. Welcome to SO, looking forward to your next question. – kriegaex Aug 26 '21 at 10:02