Apache Commons logo Commons Weaver

Apache Commons Weaver

Occasionally, as Java developers, we encounter a problem whose solution simply cannot be expressed in the Java language. Often, the Java annotation processing tools can be used to great effect, and they should not be dismissed as your first line of defense when you need to generate additional classes. Occasionally, however, our only recourse is to manipulate existing class files. It is these situations which Apache Commons Weaver was designed to address.

Apache Commons Weaver consists of:

The Maven Plugin and Antlib are used for invoking Weaving facilities. Below you will find a graph with a high level overview of Apache Commons Weaver project.

Apache Commons Weaver project

Latest API documentation is here.

Core Framework

The Commons Weaver Processor defines a “weaver module” service provider interface (SPI) as well as the facilities that use the Java ServiceLoader to discover and invoke defined weaver modules for simple filesystem-based bytecode weaving.

Weaver Modules

A number of Weaver Modules are provided by the Commons Weaver project. Typically a weaver module may respect a set of configuration properties which should be documented along with that module.

Maven Plugin

The Commons Weaver plugin for Apache Maven aims to integrate Weaver as smoothly as possible for Maven users. Here is an example of configuring the privilizer module:

  <plugin>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-weaver-maven-plugin</artifactId>
    <version>${commons.weaver.version}</version>
    <configuration>
      <weaverConfig>
        <privilizer.accessLevel>${privilizer.accessLevel}</privilizer.accessLevel>
        <privilizer.policy>${privilizer.policy}</privilizer.policy>
        <privilizer.verify>${privilizer.verify}</privilizer.verify>
      </weaverConfig>
    </configuration>
    <executions>
      <execution>
        <goals>
          <goal>prepare</goal>
          <goal>weave</goal>
        </goals>
      </execution>
    </executions>
    <dependencies>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-weaver-privilizer-api</artifactId>
        <version>${commons.weaver.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-weaver-privilizer</artifactId>
        <version>${commons.weaver.version}</version>
      </dependency>
    </dependencies>
  </plugin>

Antlib

The Commons Weaver Antlib provides tasks and types to facilitate the integration of Commons Weaver into your Apache Ant-based build process. Here the user will make the commons-weaver-antlib jar (which includes the Apache Commons Weaver processor and its dependencies), along with the jar files of the desired modules, available to the Ant build using one of the various mechanisms supported. More information on this is available here. Having done this the basic approach will be to parameterize one of the provided tasks (clean|weave) with a settings element. If both weave and clean tasks are used, defining a reference to the settings object and referencing it using the settingsref attribute is recommended, as seen here:

  <settings id="weavesettings"
            target="target/classes"
            classpathref="maincp">
    <properties>
      <privilizer.accessLevel>${privilizer.accessLevel}</privilizer.accessLevel>
      <privilizer.policy>${privilizer.policy}</privilizer.policy>
      <privilizer.verify>${privilizer.verify}</privilizer.verify>
    </properties>
  </settings>

  <clean settingsref="weavesettings" />
  <weave settingsref="weavesettings" />

Multiple weaving targets (e.g. main vs. test) are of course woven using different settings.

Custom Weaver Modules

As discussed, some modules are provided for common cases, and the developers welcome suggestions for useful modules, but there is no reason not to get started writing your own weaver module (assuming you are sure this is the right solution, or just want to do this for fun) now! When the processor framework invokes your custom Weaver, it will pass in a Scanner that can be used to find the classes you are interested in. Request the original bytecode from the WeaveEnvironment and make your changes (for this task you will save time and frustration using one of the available open source Java bytecode manipulation libraries). Save your changes back to the WeaveEnvironment. Rinse, repeat. Hint: if your Weaver uses configuration parameters to dictate its behavior, it can leave a scannable “footprint” in your woven classes. Then implement the Cleaner SPI to find and delete these in the case that the current configuration is incompatible with the results of an earlier “weaving.”

Examples

The canonical example is the privilizer module.

A simple example could be exposing annotated methods for a REST API. Suppose you want to expose only classes annotated with @WebExposed to your Web REST API.

  package example;

  import java.lang.annotation.ElementType;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.annotation.Target;

  /**
   * Marks methods that interest our weaver module.
   */
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.CLASS)
  public @interface WebExposed {

  }

And your POJO object annotated.

  package example;

  /**
   * Represents a user in our system.
   */
  public class User implements {

      private String name;
      private String surname;
      private Integer age;

      public User() {
          super();
      }

      public User(String name, String surname, Integer age) {
          super();
          this.name = name;
          this.surname = surname;
          this.age = age;
      }

      @WebExposed
      public String getName() {
          return name;
      }

      public void setName(String name) {
          this.name = name;
      }

      @WebExposed
      public String getSurname() {
          return surname;
      }

      public void setSurname(String surname) {
          this.surname = surname;
      }

  }

Now in order to scan your classpath and find the annotated methods normally you would use Java Reflection API or something similar, but the good news is that Apache Commons Weaver abstracts this for you.

  package example;

  import java.io.File;
  import java.lang.annotation.ElementType;
  import java.util.Arrays;
  import java.util.Properties;

  import org.apache.commons.weaver.WeaveProcessor;
  import org.apache.commons.weaver.model.AnnotatedElements;
  import org.apache.commons.weaver.model.ScanRequest;
  import org.apache.commons.weaver.model.ScanResult;
  import org.apache.commons.weaver.model.Scanner;
  import org.apache.commons.weaver.model.WeavableMethod;
  import org.apache.commons.weaver.model.WeaveEnvironment;
  import org.apache.commons.weaver.model.WeaveInterest;
  import org.apache.commons.weaver.spi.Weaver;

  public class MyWeaver implements Weaver {

      @Override
      public boolean process(WeaveEnvironment environment, Scanner scanner) {
          // We want to find methods annotated with @WebExposed.
          WeaveInterest findAnnotation = WeaveInterest.of(WebExposed.class, ElementType.METHOD);
          ScanResult scanResult = scanner.scan(new ScanRequest().add(findAnnotation));
          AnnotatedElements<WeavableMethod<?>> annotatedMethods = scanResult.getMethods();
          for (WeavableMethod<?> method : annotatedMethods) {
              // The API code is out of the scope of this guide, but you can do other things here, 
              // like modifying your class
              System.out.println("Expose method " + method.getTarget().getName() + " in our REST API");
          }
          return true;
      }

      public static void main(String[] args) {
          // If you are using Maven, .. might resolve to your target directory
          File targetDir = new File(MyWeaver.class.getResource("..").getFile());
          //MyWeaver myWeaver = new MyWeaver();
          WeaveProcessor weaveProcessor = new WeaveProcessor(Arrays.asList("test.MyWeaver", "test.User"), targetDir, new Properties());
          weaveProcessor.weave();
      }

  }

Before running the example above you need to tell the ServiceProvider about your custom Weaver. This is done by adding a file to your META-INF directory. If you are using Maven, then creating src/main/resources/META-INF/services/org.apache.commons.weaver.spi.Weaver with

example.MyWeaver
will instruct ServiceLoader to load your Weaver class.

FAQ

  • Q: Why not just use AspectJ?

    A: The original motivation to develop the codebase that evolved into Commons Weaver instead of simply using AspectJ was to avoid the runtime dependency, however small, introduced by the use of AspectJ. Additionally, later versions of AspectJ are licensed under the EPL which can be considered less permissive than the Apache license. Choice is A Good Thing.

  • Q: What is the relationship between Commons Weaver and ASM/Javassist/CGLIB?

    A: Rather than being an alternative to these technologies, Commons Weaver can be thought of as providing a structured environment in which these technologies can be put to use. I.e., the bytecode modifications made by a given Weaver implementation would typically be implemented using one of these (or comparable) libraries.