Why is aspectj weaving Aspects from my test folder into my source classes (as opposed to the test classes)?

2.6k views Asked by At

I'm running into a strange problem with aspectj-maven-plugin and Eclipse Juno. I have the following plugin defn in the build section of my pom:

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <configuration>
                <showWeaveInfo>true</showWeaveInfo>
                <source>1.7</source>
                <target>1.7</target>
                <Xlint>ignore</Xlint>
                <complianceLevel>1.7</complianceLevel>
                <encoding>UTF-8</encoding>
                <verbose>true</verbose>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjrt</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
        </plugin>

I have an @Aspect declared in my src/test/java/... folder.

If I build from the command line (ie: mvn clean package), my classes are built properly/as expected.

However, when I build from within Eclipse, the aspect declared in my test folder is woven into my output classes folder .class file as opposed to the test-classes folder (as confirmed by using a decompiler).

Is my configuration incorrect? Is there a setup I am missing in my Eclipse project to indicate to ignore the aspects when compiling? If I check my Java Build path config in Eclipse, I see that src/main/java -> target/classes whereas src/test/java-> target/test-classes.

Might this have something to do with the Lifecycle Mapping that is configured? I've got:

Plugin Extension          Mapping                      Source
aspectj:compile           configurator                extension
aspectj:test-compile      configurator                extension

I have AJDT 1.7.3 (org.aspectj) and 2.2.3 (org.eclipse.ajdt) installed (I'm not sure why it installed both versions).


Update 2

I updated my Aspectj-maven-plugin configuration to be the following and overrode the m2e connector settings and it seems to work, but I would love to have 3rd party validation if this works for someone else since it seems that it is editor/configuration dependant.

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjrt</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <id>compile</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                    <sources>
                        <source>
                            <basedir>${project.build.sourceDirectory}</basedir>
                            <includes>
                                <include>**/*.aj</include>
                                <include>**/*.java</include>
                            </includes>
                        </source>
                    </sources>
                    </configuration>
                </execution>
                <execution>
                    <id>test compile</id>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>
                                <basedir>${project.build.testSourceDirectory}</basedir>
                                <includes>
                                    <include>**/*.aj</include>
                                    <include>**/*.java</include>
                                </includes>
                            </source>
                        </sources>
                        <weaveDirectories>
                            <param>${project.build.outputDirectory}</param>
                        </weaveDirectories>
                    </configuration>
                </execution>
            </executions>
            <configuration>
                <verbose>true</verbose>
                <outxml>true</outxml>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <complianceLevel>${java.version}</complianceLevel>
                <showWeaveInfo>true</showWeaveInfo>
            </configuration>
        </plugin>

With the following m2e configuration to override the m2e connector settings that is shipped with the AJDT/m2e 0.14 framework: org.eclipse.m2e lifecycle-mapping 1.0.0 org.codehaus.mojo aspectj-maven-plugin [1.7,) compile org.codehaus.mojo aspectj-maven-plugin [1.7,) test-compile

If this does work for others, the next step is to try and understand how/why the m2e connector fails, and where it needs to be fixed.

I uploaded my test code (including @kriegaex test project) to my github sample project: github.com/benze/aspectj-maven-plugin-defect-example.git . As per the readme, to enable/disable overriding the m2e-connector, enable the override-m2e-lifecycle profile.

1

There are 1 answers

4
kriegaex On

I have just tried to answer your question on the AspectJ users mailing list and just found the same question here. As for this part, ...

I have AJDT 1.7.3 (org.aspectj) and 2.2.3 (org.eclipse.ajdt) installed (I'm not sure why it installed both versions).

The former seems to apply to the actual AspectJ (compiler and runtime) version, in my Eclipse Luna it is 1.8.5. The latter applies to the AJDT plugin components as such, in my IDE it is 2.2.4.


Update:

Your assumption (see also your new mailing list post) concerning M2Eclipse's lifecycle mapping - something I have never heard of before, but just looked into for the first time - being the root cause of the problem seems to be true.

Sorry, this does not really work, see update 2 further below.

Having read this and having looked into the default lifecycle mapping in my installed m2e plugin

<eclipse-dir>/plugins/org.eclipse.m2e.lifecyclemapping.defaults_1.5.0.20140606-0033.jar
    -> lifecycle-mapping-metadata.xml

I realised that there is not default mapping for AspectJ Maven plugin. So I just created one via menu option

Window -> Preferences
    Maven -> Lifecycle Mappings

Eclipse m2e lifecycle mappings menu

with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<lifecycleMappingMetadata>
    <pluginExecution>
      <pluginExecutionFilter>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <versionRange>[1.0,)</versionRange>
        <goals>
          <goal>compile</goal>
          <goal>test-compile</goal>
        </goals>
      </pluginExecutionFilter>
      <action>
        <execute>
          <runOnIncremental>true</runOnIncremental>
        </execute>
      </action>
    </pluginExecution>
  </pluginExecutions>
</lifecycleMappingMetadata>

This fixed your problem (which I could reproduce in my own little demo project) in my Eclipse installation.

I think it makes sense to provide a default mapping for the AspectJ Maven plugin from within M2Eclipse (someone should open a ticket, I guess) or even directly in the AspectJ Maven plugin by providing a default mapping wight in the plugin (which is said to be possible according to the M2E documentation).

Here is my sample project:

Maven project POM:

<?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>de.scrum-master.aspectj</groupId>
    <artifactId>unit-test-specific-aspect</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <aspectj.version>1.8.6</aspectj.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <!-- IMPORTANT -->
                    <useIncrementalCompilation>false</useIncrementalCompilation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.7</version>
                <configuration>
                    <showWeaveInfo>true</showWeaveInfo>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <Xlint>ignore</Xlint>
                    <complianceLevel>${java.version}</complianceLevel>
                    <encoding>UTF-8</encoding>
                    <verbose>true</verbose>
                </configuration>
                <executions>
                    <execution>
                        <id>compile</id>
                        <!-- IMPORTANT -->
                        <phase>process-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <!-- IMPORTANT -->
                        <phase>process-sources</phase>
                        <goals>
                            <goal>test-compile</goal>
                        </goals>
                        <configuration>
                            <weaveDirectories>
                                <param>${project.build.outputDirectory}</param>
                            </weaveDirectories>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjtools</artifactId>
                        <version>${aspectj.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3</version>
                <configuration>
                    <mainClass>de.scrum_master.app.Application</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Driver application:

package de.scrum_master.app;

public class Application {
    public static void main(String... args) {
        System.out.println("Hello world!");
    }
}

Unit test:

package de.scrum_master.app;

import org.junit.Test;

import static org.junit.Assert.*;

public class ApplicationTest {
    @Test
    public void testMain() throws Exception {
        Application.main("foo");
    }

    @Test
    public void testOne() throws Exception {
        System.out.println("Test one");
    }

    @Test
    public void testTwo() throws Exception {
        System.out.println("Test two");
    }
}

Test aspect (located in src/test/java):

package de.scrum_master.app;

public aspect TestAspect {
    before() : execution(* *(..)) {
        System.out.println(thisJoinPoint);
    }
}

Console output for mvn clean package exec:java:

[INFO] ------------------------------------------------------------------------
[INFO] Building unit-test-specific-aspect 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------

(...)

[INFO] --- aspectj-maven-plugin:1.7:compile (compile) @ unit-test-specific-aspect ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[INFO] 
[INFO] --- aspectj-maven-plugin:1.7:test-compile (test-compile) @ unit-test-specific-aspect ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[INFO] Join point 'method-execution(void de.scrum_master.app.Application.main(java.lang.String[]))' in Type 'de.scrum_master.app.Application' (Application.java:4) advised by before advice from 'de.scrum_master.app.TestAspect' (TestAspect.aj:4)
[INFO] Join point 'method-execution(void de.scrum_master.app.ApplicationTest.testMain())' in Type 'de.scrum_master.app.ApplicationTest' (ApplicationTest.java:9) advised by before advice from 'de.scrum_master.app.TestAspect' (TestAspect.aj:4)
[INFO] Join point 'method-execution(void de.scrum_master.app.ApplicationTest.testOne())' in Type 'de.scrum_master.app.ApplicationTest' (ApplicationTest.java:14) advised by before advice from 'de.scrum_master.app.TestAspect' (TestAspect.aj:4)
[INFO] Join point 'method-execution(void de.scrum_master.app.ApplicationTest.testTwo())' in Type 'de.scrum_master.app.ApplicationTest' (ApplicationTest.java:19) advised by before advice from 'de.scrum_master.app.TestAspect' (TestAspect.aj:4)

(...)

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running de.scrum_master.app.ApplicationTest
execution(void de.scrum_master.app.ApplicationTest.testOne())
Test one
execution(void de.scrum_master.app.ApplicationTest.testTwo())
Test two
execution(void de.scrum_master.app.ApplicationTest.testMain())
execution(void de.scrum_master.app.Application.main(String[]))
Hello world!

(...)

[INFO] --- exec-maven-plugin:1.3:java (default-cli) @ unit-test-specific-aspect ---
[WARNING] Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6.
Hello world!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

(...)

So it works in Maven, just like before. But what about running the code from Eclipse via a normal run configuration?

Hello world!

No aspects there, cool! And what about the unit tests in Eclipse?

execution(void de.scrum_master.app.ApplicationTest.testOne())
Test one
execution(void de.scrum_master.app.ApplicationTest.testTwo())
Test two
execution(void de.scrum_master.app.ApplicationTest.testMain())
execution(void de.scrum_master.app.Application.main(String[]))
Hello world!

Tadaa, the aspect is woven into Application.main. Everything works as we want it to from both Eclipse and Maven.


Update 2:

I am so sorry, it only worked because I had not done a full Eclipse rebuild after the Maven build. So obviously the lifecycle mapping is not enough or maybe not even the right path to the solution. I suppose, Eclipse is just unaware of the <weaveDirectories> setting in AspectJ Maven plugin and this is a non-trivial thing to fix.