简体   繁体   中英

Is it possible to weave some classes from a jar while excluding the rest of the classes?

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:

  1. Fork the project and apply my changes ( 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 )
  2. Use [AOP] to intercept that one method from that one class inside a huge jar ( seems cleaner ) and inject my extra tests

In 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM