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
Useful links
- Why @Configurable and @Transactional don’t belong to into the same class
- AspectJ compiler (ajc) vs load-time weaving
- Spring a AOP
- AspectJ pointcut syntax
See Getting started to know how set up IDE for AspectJ compilation.