1. Introduction

AOP, or aspect-oriented programming is a very common technique, especially in Java Web development. And the most popular AOP frameworks are Spring AOP and AspectJ respectively.

2. Spring AOP vs AspectJ

Spring AOP is based on the Spring IoC implementation, which addresses most common requirements, but it is not a complete AOP solution. It has even less to offer for objects that are not managed by the Spring container. AspectJ, on the other hand, aims to provide a complete AOP solution and is therefore more complex.

2.1 Weave-in method

The two weaving methods are extremely different, and this is the essential difference between them, they implement proxies in different ways.

AspectJ is woven in before runtime and is divided into three categories.

  • compile-time weaving
  • post-compile weaving
  • Load-time weaving

So it requires the support of AspectJ compiler (ajc).

Spring AOP is runtime weaving and uses two main techniques: JDK Dynamic Proxy and CGLIB Proxy. For interfaces JDK Proxy is used, while CGLIB is used for inheritance.

Spring Aop Process

2.2 Joinpoints

Because of the difference in the way weaving is done, the supported Joinpoints are also different. Methods like final and static methods can’t be changed by dynamic proxies, so Spring AOP can’t support them. But AspectJ weaves in the actual code directly before running, so it is much more powerful.

Joinpoint Spring AOP Supported AspectJ Supported
Method Call No Yes
Method Execution Yes Yes
Constructor Call No Yes
Constructor Execution No Yes
Static initializer execution No Yes
Object initialization No Yes
Field reference No Yes
Field assignment No Yes
Handler execution No Yes
Advice execution No Yes

2.3 Performance

Compile weave is much faster than runtime weave, Spring AOP uses the proxy pattern to create the corresponding proxy class at runtime, which is not as efficient as AspectJ.

3. Using AspectJ in Spring Boot

Because AspectJ is more powerful and will be used more often in projects, only its integration with Spring Boot is described here.

3.1 Adding dependencies

Import the following dependencies, with Lombok and aspectj added on top of Spring Boot.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
  </dependency>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${aspectj.version}</version>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

3.2 Objects proxied by AOP aspects

To verify the functionality of AOP, we add a TestController that has a method to handle Get requests and also calls private member methods and static methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @GetMapping("/hello")
    public String hello() {
        log.info("------hello() start---");
        test();
        staticTest();
        log.info("------hello() end---");
        return "Hello, pkslow.";
    }

    private void test() {
        log.info("------test() start---");
        log.info("test");
        log.info("------test() end---");
    }

    private static void staticTest() {
        log.info("------staticTest() start---");
        log.info("staticTest");
        log.info("------staticTest() end---");
    }
}

3.3 Configuring Aspect

The configuration aspects are as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Aspect
@Component
@Slf4j
//@EnableAspectJAutoProxy
public class ControllerAspect {

    @Pointcut("execution(* com.pkslow.springboot.controller..*.*(..))")
    private void testControllerPointcut() {

    }

    @Before("testControllerPointcut()")
    public void doBefore(JoinPoint joinPoint){
        log.info("------pkslow aop doBefore start------");
        String method = joinPoint.getSignature().getName();
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        log.info("Method: {}.{}" ,declaringTypeName, method);
        log.info("------pkslow aop doBefore end------");
    }

    @Around("testControllerPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("------pkslow aop doAround start------");
        long start = System.nanoTime();
        Object obj = joinPoint.proceed();
        long end = System.nanoTime();
        log.info("Execution Time: " + (end - start) + " ns");
        log.info("------pkslow aop doAround end------");

        return obj;
    }
}

@Pointcut defines which classes and methods will be proxied, here all the methods under controller are configured.

And @Before and @Around define some processing logic. @Before prints the method name, while @Around does a timing.

Note: It is not necessary to configure @EnableAspectJAutoProxy.

3.4 maven plugins

Because it is necessary to weave in the code at compile time, so you need the support of maven plugin: https://github.com/mojohaus/aspectj-maven-plugin

Just configure the pom.xml file.

Then execute the command to package.

1
mvn clean package

This will display some woven-in information on the console, roughly as follows.

1
2
3
4
5
6
[INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:14) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:14) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:28) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:28) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))

When you see the above information, it means that the code was successfully woven in, and you can check the generated class file for details.

Generated class file

You can see that there is a lot of code that we did not write, but generated by weaving in.

3.5 Execution and Testing

After successful compilation, we execute the code. If you are executing through IDEA, you don’t need to build again before running, because the package has already been built through maven. Build through IDEA’s own compiler, it may not weave in. Or choose ajc as the compiler.

Testing.

1
GET http://localhost:8080/test/hello

The log output from the console is as follows, successfully implementing the AOP function.

Console output logs

3.6 Some of the errors encountered

1
ajc Syntax error, annotations are only available if source level is 1.5 or greater

The following plugin needs to be configured.

1
2
3
<complianceLevel>${java.version}</complianceLevel>
<source>${java.version}</source>
<target>${java.version}</target>

You may also encounter the error that Lombok is not recognized, configure the following to solve the problem.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>aspectj-maven-plugin</artifactId>
  <version>1.14.0</version>
  <configuration>
    <complianceLevel>${java.version}</complianceLevel>
    <source>${java.version}</source>
    <target>${java.version}</target>
    <proc>none</proc>
    <showWeaveInfo>true</showWeaveInfo>
    <forceAjcCompile>true</forceAjcCompile>
    <sources/>
    <weaveDirectories>
      <weaveDirectory>${project.build.directory}/classes</weaveDirectory>
    </weaveDirectories>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>compile</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The complete code can be found here.

Reference: https://www.pkslow.com/archives/spring-aop-vs-aspectj