Search code examples
javaaopaspectj

AspectJ - Child Classes Getting Lost


I'm sure this amounts to Newbie question for AspectJ but reviewing books and web sites, I'm not seeing the answer in terms I recognize.

Summary: In my Client Jar, I am getting compilation errors complaining that methods that were added as a part of "Aspect B" are not present.

I have a setup similar to :

         +---------------------+     
         |                     |     
         |  Client Jar         |     
         |                     |     
         +--+---------------+--+     
            |               |        
            |               |        
 +----------+----+  +-------v-------+
 | JSON Serial / |  |  Other Aspect |
 |  Deser Aspect |  +---------------+
 +-------------+-+  +-------v-------+
               |    |  Potential    |
               |    |   Other Jar   |
               |    +---------------+
               |                     
          +----v-----------+         
          |   Javabean     |         
          |   Aspects      |         
          +----------------+         
          +----v-----------+         
          |   Basic Data   |         
          |    Objects     |         
          +----------------+         

Essentially, I have two levels of aspects where behaviors are being extended in each layer.

Assume the following class in "Basic Data Objects":

BasicDataObjects SampleObject

public class SampleObject {
    private String name;
    private int age;
}

and the following aspect in JavaBean Aspects:

privileged aspect SampleObject_JavaBean {
    public String SampleObject.getName() {
       return this.name;
    }
}

And the following aspect in JSON Aspects:

privileged aspect SampleObject_Json {
    public String SampleObject.getNameValue() {
       return this.name == null ? null : this.name.serialize();
    }
}

Finally, in the actual client jar, I am getting the following as a compilation error:

public void someMethod (SampleObject obj) {
    obj.getNameValue() // <-- This has a compilation error that the getNameValue() is unresolvable.
}

In my Client Jar, I am getting compilation errors that methods that were added as a part of "Aspect B" are not available.

My expectation was that the "Aspect A" generated jar file should have all its own classes compiled as well as all the classes that were provided as a part of "Aspect B".

Aspect B pom:

<project>
    <!-- (...) -->

    <artifactId>aspect-b</artifactId>

        <dependencies>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>lib-data-objects</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>

        <build>
            <plugins>


        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <outxml>true</outxml>
                <complianceLevel>${java.version}</complianceLevel>
                <source>${java.version}</source>
                <target>${java.version}</target>

                <weaveDependencies>
                    <weaveDependency>
                        <groupId>com.example</groupId>
                        <artifactId>lib-data-objects</artifactId>
                    </weaveDependency>
                </weaveDependencies>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.5.1</version>
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
            </configuration>
        </plugin>
    </plugins>
</build>

    <!-- (...) -->
</project>

Aspect A pom:

<project>

<!-- (...) -->

<artifactId>aspect-a</artifactId>

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspectj.version}</version>
    </dependency>

    <dependency>
        <groupId>com.example</groupId>
        <artifactId>lib-data-objects</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>aspect-b</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <outxml>true</outxml>
                <complianceLevel>${java.version}</complianceLevel>
                <source>${java.version}</source>
                <target>${java.version}</target>

                <weaveDependencies>
                    <weaveDependency>
                        <groupId>com.example</groupId>
                        <artifactId>aspect-b</artifactId>
                    </weaveDependency>
                </weaveDependencies>
            </configuration>
        </plugin>
    </plugins>
</build>

<!-- (...) -->

</project>

Client Jar pom:

<project>

<!-- (...) -->

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>aspect-a-wrapper</artifactId>
        <version>${aspect-a.version}</version>
    </dependency>
    <groupId>com.example</groupId>
        <artifactId>lib-data-objects</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <outxml>true</outxml>
                <complianceLevel>${java.version}</complianceLevel>
                <source>${java.version}</source>
                <target>${java.version}</target>

                <weaveDependencies>
                    <weaveDependency>
                        <groupId>com.example</groupId>
                        <artifactId>aspect-a</artifactId>
                    </weaveDependency>
                </weaveDependencies>
            </configuration>
        </plugin>
    </plugins>
</build>

<!-- (...) -->

</project>

Solution

  • As you said in your own answer, in your client JAR pom.xml you have defined dependencies on both

    • the plain data objects library (without aspect code) and
    • the aspect-enhanced data objects library after two stages of weaving (Java Beans and JSON inter-type declarations).

    But you only need the latter, so you should remove the former.

    That you use weaveDependencies in your aspect libraries is okay and necessary because otherwise they will not compile in your scenario: Aspect B needs the data objects library because otherwise it cannot find the classes it should add new methods to. Aspect B needs Aspect A because it wants to use an already aspect-enhanced library to add even more methods via ITD. I don't think that this is a particularly nice setup because Aspect B needs to know about Aspect A, which creates an unnecessary transitive dependency ClientJar -> AspB -> AspA -> DataObj. Maybe it would be wiser to aspect-enhance the data objects library directly and shorten the chain to ClientJar -> DataObj. This is only bad if you really need the plain library without any added ITD methods in other circumstances. Then you could still create a plain and a fully aspect-enhanced version (only one stage of aspect enhancement, not two transitively dependent ones).


    Update, 2015-01-02:

    I had some leftover time and have started looking into this again. What I have ended up with is a nice solution (see GitHub repo) with the following characteristics:

    • There is a parent module aspectj-multi-module, child modules are:
      • data-objects (application class, no aspects), no dependencies
      • aspect-javabean (Java Bean-related ITD aspect), depends on data-objects
      • aspect-json (JSON-related ITD aspect), depends on data-objects
      • data-objects-aspectj (weave aspects into core classes), depends on data-objects, aspect-javabean, aspect-json
      • client-jar (create JAR of JARs containing core + aspect code, sample class with main method using aspect-enhanced core class and AspectJ runtime), depends on data-objects-aspectj
    • Please note that both aspect-{javabean,json} modules only depend on the core module, but no aspect module depends on the other one. Both aspect types are woven into the core code in module data-objects-aspectj via binary weaving.
    • A little trick was used in order to compile the ITD aspects, but excluding the woven core modules from the resulting aspect library because we only want the aspects in the library. In order to understand the trick you need to know the difference between the Ajc (AspectJ compiler)'s inpath and its aspectpath:
      • Whatever is on the inpath will also be in the output directory/JAR. The corresponding AspectJ Maven config setting is <weaveDependency>, section <weaveDependencies>.
      • Whatever is on the aspectpath is only available during compile time and will not end up in the output directory/JAR. The corresponding AspectJ Maven config setting is <aspectLibrary>, section <aspectLibraries>.
    • So, using the semantics mentioned above, the core module is defined as an <aspectLibrary> (even though it does not contain any aspects, don't be confused by the naming). This effectively avoids any woven core classes from being part of aspect libraries. It would not be useful to have multiple versions of woven core classes with mutually exclusive aspects in the aspect library because which one should win in the end? We need a version of our core code with all aspect libraries woven in at the same time, or at least all we choose to configure in another module.
    • The other module just mentioned is data-objects-aspectj, and its sole purpose is to do post-compile, binary aspect weaving, using as its input the pure Java core module and both pure aspect libraries. All of these modules are configured as <weaveDependencies> this time, because all of them should be in the resulting JAR. In order to enable binary weaving in the AspectJ Maven plugin, we need to add one dummy class because the plugin does not compile or weave anything if it does not find any source files in its own module. The dummy class is stripped from the output again via an explicit exclusion configured in the Maven JAR plugin. The exclusion is nice to have, you could also just ignore the dummy class in the output JAR.
    • Last, but not least, there is module client-jar which adds a little driver application with a main method to the whole mix in order to make it runnable and testable from the command line. The driver application uses getter methods created by both the JSON and the Java Beans aspects in order to prove that they really exist and are functional.
    • Another nicety in module client-jar is the creation of a One-JAR (über-JAR) containing all dependencies (even the AspectJ runtime) and a main class configuration. Effectively you can just run the result via: java -jar client-jar/target/client-jar-1.0-SNAPSHOT.one-jar.jar
    • Finally, module client-jar also contains an Exec Maven plugin configuration for directly running the über-JAR via: mvn --projects client-jar exec:exec. This is also nice to have, just like the über-JAR itself, but why make life harder than necessary? ;-)

    Console in-/output:

    $ java -jar client-jar/target/client-jar-1.0-SNAPSHOT.one-jar.jar
    
    SampleObject{name='John Doe', age=33}
    
    Java Bean properties:
      John Doe
      33
    
    JSON properties:
      "Name" : "John Doe"
      "Age" : 33
    

    Update, 2015-01-03:

    I added the capability to create a custom mix of aspects applied to your core code during weaving in module data-objects-aspectj by means of Maven profiles, see changeset 32ee5fc.