I am trying to to extend a 3rd lib code with new capabilities.
And since I only need to inject some code around one method from one class, I figured I can either:
seems sloppy, and will definitely require lots of work whenever I try to upgrade to a newer version of the lib, not to mention licensing nightmare
)seems cleaner
) and inject my extra testsIn case any of you wants to know, that one class is not a spring bean and is used deep in the code, so I can not simply extend it and override/wrap that method easily, it would require at least a couple extra layers of extend&override/wrap. So AspectJ & AOP seems like the better way to go.
I managed to set my project up with some plugin to invoke ajc
and weave the code with my desired jar in the -inpath
param. And the only problem is that ajc
seems to weave everything (or at least duplicate it);
So what I need basically is to ask AJC
to simply wave that class from that jar, and not the whole jar !
As you have noticed, the AspectJ compiler always outputs all files found in weave dependencies (in-JARs), no matter if they are changed or not. This behaviour cannot be changed via command line, AFAIK. So you need to take care of packaging your JARs by yourself.
Here is a sample project incl. Maven POM showing you how to do that. I have chosen a rather stupid example involving Apache Commons Codec:
Sample application:
The application base64-encodes a text, decodes it again and prints both texts to console.
package de.scrum_master.app;
import org.apache.commons.codec.binary.Base64;
public class Application {
public static void main(String[] args) throws Exception {
String originalText = "Hello world!";
System.out.println(originalText);
byte[] encodedBytes = Base64.encodeBase64(originalText.getBytes());
String decodedText = new String(Base64.decodeBase64(encodedBytes));
System.out.println(decodedText);
}
}
Normally the output looks like this:
Hello world!
Hello world!
No surprises here. But now we define an aspect which manipulates the results returned from the third party library, replacing each character 'o' (oh) by '0' (zero):
package de.scrum_master.aspect;
import org.apache.commons.codec.binary.Base64;
public aspect Base64Manipulator {
byte[] around() : execution(byte[] Base64.decodeBase64(byte[])) {
System.out.println(thisJoinPoint);
byte[] result = proceed();
for (int i = 0; i < result.length; i++) {
if (result[i] == 'o')
result[i] = '0';
}
return result;
}
}
BTW, if you would just use call()
instead of execution()
here, there would be no need to actually weave into third party code. But anyway, you asked for it, so I am showing you how to do it.
Maven POM:
<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.stackoverflow</groupId>
<artifactId>aspectj-weave-single-3rd-party-class</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.source-target.version>1.8</java.source-target.version>
<aspectj.version>1.8.10</aspectj.version>
<main-class>de.scrum_master.app.Application</main-class>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>${java.source-target.version}</source>
<target>${java.source-target.version}</target>
<!-- IMPORTANT -->
<useIncrementalCompilation>false</useIncrementalCompilation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration>
<!--<showWeaveInfo>true</showWeaveInfo>-->
<source>${java.source-target.version}</source>
<target>${java.source-target.version}</target>
<Xlint>ignore</Xlint>
<complianceLevel>${java.source-target.version}</complianceLevel>
<encoding>${project.build.sourceEncoding}</encoding>
<!--<verbose>true</verbose>-->
<!--<warn>constructorName,packageDefaultMethod,deprecation,maskedCatchBlocks,unusedLocals,unusedArguments,unusedImport</warn>-->
<weaveDependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</weaveDependencies>
</configuration>
<executions>
<execution>
<!-- IMPORTANT -->
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<configuration>
<mainClass>${main-class}</mainClass>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>remove-unwoven</id>
<!-- Phase 'process-classes' is in between 'compile' and 'package' -->
<phase>process-classes</phase>
<goals>
<goal>clean</goal>
</goals>
<configuration>
<!-- No full clean, only what is specified in 'filesets' -->
<excludeDefaultDirectories>true</excludeDefaultDirectories>
<filesets>
<fileset>
<directory>${project.build.outputDirectory}</directory>
<includes>
<include>org/apache/commons/codec/**</include>
<include>META-INF/**</include>
</includes>
<excludes>
<exclude>**/Base64.class</exclude>
</excludes>
</fileset>
</filesets>
<!-- Set to true if you want to see what exactly gets deleted -->
<verbose>false</verbose>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>
<organization>
<name>Scrum-Master.de - Agile Project Management</name>
<url>http://scrum-master.de</url>
</organization>
</project>
As you can see I am using <weaveDependencies>
in the AspectJ Maven plugin (which translates to -inpath
for the AspectJ compiler) in combination with a special execution of the Maven Clean plugin that deletes all unneeded classes and the META-INF directory from the original JAR.
If the you run mvn clean package exec:java
you see:
[INFO] ------------------------------------------------------------------------
[INFO] Building aspectj-weave-single-3rd-party-class 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
(...)
[INFO] --- aspectj-maven-plugin:1.9:compile (default) @ aspectj-weave-single-3rd-party-class ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
(...)
[INFO] --- maven-clean-plugin:2.5:clean (remove-unwoven) @ aspectj-weave-single-3rd-party-class ---
[INFO] Deleting C:\Users\Alexander\Documents\java-src\SO_AJ_MavenWeaveSingle3rdPartyClass\target\classes (includes = [org/apache/commons/codec/**, META-INF/**], excludes = [**/Base64.class])
(...)
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ aspectj-weave-single-3rd-party-class ---
[INFO] Building jar: C:\Users\Alexander\Documents\java-src\SO_AJ_MavenWeaveSingle3rdPartyClass\target\aspectj-weave-single-3rd-party-class-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- exec-maven-plugin:1.5.0:java (default-cli) @ aspectj-weave-single-3rd-party-class ---
Hello world!
execution(byte[] org.apache.commons.codec.binary.Base64.decodeBase64(byte[]))
Hell0 w0rld!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
And this is what my target/classes
directory looks like after the build:
As you can see, there is only one Apache Commons class file left which goes into the created JAR.