AOP

Basic information

  • we use Compile-time weaving - it's the most powerful possibility from all aspect-weaving possibilities (e.g. load-time weaving (LTW) doesn't allow to create aspects around private and final methods). Another advantage is that it doesn't have negative performance impact on running application (LTW makes weaving during class initialization). Disadvantage can be that compilation has to be by AspectJ compiler => build script and IDE must be adjust.

    There is blog post about performance comparision: The results are a bit surprising for me for two reasons. First, creating objects marked as @Configurable is less than 4 times slower than creating ordinary objects using new operator.

    • Compile-time weaving is the simplest approach. When you have the source code for an application, ajc will compile from source and produce woven class files as output. The invocation of the weaver is integral to the ajc compilation process. The aspects themselves may be in source or binary form. If the aspects are required for the affected classes to compile, then you must weave at compile-time. Aspects are required, e.g., when they add members to a class and other classes being compiled reference the added members.

    • Post-compile weaving (also sometimes called binary weaving) is used to weave existing class files and JAR files. As with compile-time weaving, the aspects used for weaving may be in source or binary form, and may themselves be woven by aspects.

    • Load-time weaving (LTW) is simply binary weaving defered until the point that a class loader loads a class file and defines the class to the JVM. To support this, one or more "weaving class loaders", either provided explicitly by the run-time environment or enabled through a "weaving agent" are required.

  • we use JDK dynamic proxies (Dynamic Proxies vs. CGLib proxies)
  • Spring AOP and AspectJ runs in parallel - instances created from Spring factory will be controled by Spring. If classes are created outside of Spring IoC container (classes marked with annotation @Configurable) then AspectJ is used
  • pointcuts syntax = AspectJ syntax

    Using aspectj-autoproxy means that @Aspect annotations are recognized, but Spring proxies are still being created, see this quote from the documentation: Do not be misled by the name of the element: using it will result in the creation of Spring AOP proxies. The @AspectJ style of aspect declaration is just being used here, but the AspectJ runtime is not involved.

  • all production code (not test code) is compiled by aspectj with use aspectj-maven-plugin, standard compilation with maven-compiler-plugin is disabled (again for production code only)

    <!-- configure Java compilers -->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
            <!-- http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#configuration-metadata-annotation-processor -->
            <proc>none</proc>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <encoding>UTF-8</encoding>
            <showDeprecation>true</showDeprecation>
            <showWarnings>true</showWarnings>
        </configuration>
        <!-- compiler is disabled because of aspectj compiler -->
        <executions>
            <execution>
                <id>default-compile</id>
                <phase>none</phase>
            </execution>
        </executions>
    </plugin>
    
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.10</version>
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <!-- https://eclipse.org/aspectj/doc/next/devguide/ajc-ref.html -->
            <complianceLevel>${java.version}</complianceLevel>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <Xlint>ignore</Xlint>
            <!--<showWeaveInfo>true</showWeaveInfo>-->
            <!--<forceAjcCompile>true</forceAjcCompile>-->
            <weaveDirectories>
                <weaveDirectory>${project.build.directory}/classes</weaveDirectory>
            </weaveDirectories>
            <aspectLibraries>
                <aspectLibrary>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aspects</artifactId>
                </aspectLibrary>
            </aspectLibraries>
        </configuration>
        <dependencies>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjtools</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
        </dependencies>
    </plugin>
  • dependency on AspectJ libraries and Spring aspects is necessary (configuration from root pom.xml). We can also add dependency on own custom aspects.

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
               <artifactId>spring-aspects</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
        </dependency>
    </dependencies>
  • Spring Boot configuration spring.aop.auto=true in application.properties is equal to the annotation @EnableAspectJAutoProxy
  • when we want to non-managed classes into Spring IoC container then annotation @EnableSpringConfigured is needed

Creating own aspect

There is module wiseporter-logging (not from OpenHub framework) that contains aspect com.openwise.wiseporter.log.EntitySavingAspect - this aspect logs entity before saving:

/**
 * Aspect example.
 *
 * @author <a href="mailto:petr.juza@openwise.cz">Petr Juza</a>
 * @since 0.1
 */
@Aspect
@Configurable
public class EntitySavingAspect {

    @Autowired
    private ApplicationContext ctx;

   public EntitySavingAspect() {
      System.out.println("EntitySavingAspect created ...");
   }

    @Before("com.openwise.wiseporter.commons.aop.CommonPointcuts.preSavingEntity()")
    public void logBeforeSaving(JoinPoint jp) {
        System.out.println("AOP (" + (ctx != null) + "): " + jp.getSignature());
    }
}

where is reference to CommonPointcuts class (module wiseporter-commons):

@Aspect
public class CommonPointcuts {

    /**
     * Join point is pre-saving method in entities.
     */
    @Pointcut(value = "execution(void com.openwise.wiseporter.coreapi.entity.Saveable+.preSave())")
    public void preSavingEntity() {}

    /**
     * Join point is post-saving method in entities.
     */
    @Pointcut(value = "execution(void com.openwise.wiseporter.coreapi.entity.Saveable+.postSave())")
    public void postSavingEntity() {}

    /**
     * Join point is deleting method in entities.
     */
    @Pointcut(value = "execution(void com.openwise.wiseporter.coreapi.entity.Deletable+.delete())")
    public void inDeletingEntity() {}
}


It's necessary to adjust AspectJ compilation to use this aspect in another modules. We need to add dependency to this module with aspect - see adding wiseporter-logging module into aspectLibraries below.

<build>
    <plugins>
        <!-- added dependency to wiseporter-logging -->
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <configuration>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                    <aspectLibrary>
                        <groupId>${project.groupId}</groupId>
                        <artifactId>wiseporter-logging</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
            </configuration>
        </plugin>
     </plugins>
</build>


If aspect itself should be Spring bean (e.g. I need to use auto-wiring to another beans) then aspect has to be marked by annotation @Configurable.

Common AOP hints

Tips for troubleshooting:

  • enable logging
    • aop.xml: weaver options="-verbose -showWeaveInfo -debug" (valid for LTW only)
    • aspectj-maven-plugin<showWeaveInfo>true</showWeaveInfo> - this option allows to log all changes made by ajc compiler
    • ajc command parameters for setting in IDE: -Xlint:ignore -showWeaveInfo -verbose (ajc params)
    • Spring AOP - log levels configuration

      logging.level.org.springframework.aop=TRACE
      logging.level.org.aspectj=TRACE
  • exception of the following type "java.lang.IllegalStateException: Post-processor tried to replace bean instance of type [com.openwise.wiseporter.productcatalogue.products.Category] with (proxy) object of type [org.springframework.beans.factory.aspectj.$Proxy70] - not supported for aspect-configured classes!" tells that problem is with ajc compilation


See Getting started to know how set up IDE for AspectJ compilation.