Search code examples
javaspringaspectjspring-aop

How to make spring inject beans with load time weaving of aspectj?


A reproducible project is placed here. (Branch - aspect_autowire) .Invoke the below command to reproduce the problem

curl --request GET 'http://localhost:8080/hello'`

My aspect is code is like below

@Aspect
@Slf4j
@AllArgsConstructor
@Component
public class DaoExceptionHandler {
  private final Map<Long, String> errorLogMap;

  @AfterThrowing(
      pointcut =
          "execution(* *(..)) &&"
              + " @annotation(com.example.helloworld.utils.HandleDaoException)",
      throwing = "ex")
  public void handle(RuntimeException ex) {
    errorLogMap.put(System.currentTimeMillis(), ex.getMessage());
    log.error("Exception received on dao", ex);
    log.error("Error Log Map Content: {}", errorLogMap);
    throw JooqUtil.mapException(ex);
  }
}

errorLogMap bean is defined like below

@Configuration
@Slf4j
@EnableLoadTimeWeaving
public class AspectConfig {

  @Bean
  public Map<Long, String> errorLogMap() {
    log.info("Initialising Bean");
    return new HashMap();
  }
}

aop.xml is placed in src/main/resources/META-INF and is like below

<aspectj>
  <aspects>
    <aspect name="com.example.helloworld.aspects.DaoExceptionHandler"/>

    <weaver options="-verbose -showWeaveInfo">
      <include within="com.example.helloworld..*"/>
    </weaver>
  </aspects>
</aspectj>

-javaagents are specified like below while running the application

-Djava.rmi.server.hostname=localhost -javaagent:/Users/debrajmanna/.m2/repository/org/aspectj/aspectjweaver/1.9.20/aspectjweaver-1.9.20.jar -javaagent:/Users/debrajmanna/.m2/repository/org/springframework/spring-instrument/6.0.13/spring-instrument-6.0.13.jar

During startup I see logs like below

OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
[AppClassLoader@251a69d7] info AspectJ Weaver Version 1.9.20 built on Tuesday Aug 15, 2023 at 23:35:34 PDT
[AppClassLoader@251a69d7] info register classloader jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7
[AppClassLoader@251a69d7] info using configuration /Users/debrajmanna/code/java/github/spring-boot-hello-world/target/classes/META-INF/aop.xml
[AppClassLoader@251a69d7] info register aspect com.example.helloworld.aspects.DaoExceptionHandler

...

2024-04-28T15:15:20.616+05:30  INFO 87296 --- [           main] c.e.helloworld.HelloWorldApplication     : No active profile set, falling back to 1 default profile: "default"
[AppClassLoader@251a69d7] weaveinfo Join point 'method-execution(void com.example.helloworld.dao.DaoImpl.getError())' in Type 'com.example.helloworld.dao.DaoImpl' (DaoImpl.java:10) advised by afterThrowing advice from 'com.example.helloworld.aspects.DaoExceptionHandler' (DaoExceptionHandler.java)

...

[MethodUtil@3209a8de] info AspectJ Weaver Version 1.9.20 built on Tuesday Aug 15, 2023 at 23:35:34 PDT
[MethodUtil@3209a8de] info register classloader sun.reflect.misc.MethodUtil@3209a8de
[MethodUtil@3209a8de] info using configuration /Users/debrajmanna/code/java/github/spring-boot-hello-world/target/classes/META-INF/aop.xml
[MethodUtil@3209a8de] info register aspect com.example.helloworld.aspects.DaoExceptionHandler

But aspect code is not all getting invoked. I can just see

2024-04-28T15:17:36.104+05:30  WARN 87296 --- [nio-8080-exec-2] c.e.h.controller.HelloWorldController    : Ignoring exception

If I just comment the usage of errorLogMap in DaoExceptionHandler

public class DaoExceptionHandler {
  // private final Map<Long, String> errorLogMap;

  @AfterThrowing(
      pointcut =
          "execution(* *(..)) &&"
              + " @annotation(com.example.helloworld.utils.HandleDaoException)",
      throwing = "ex")
  public void handle(RuntimeException ex) {
    // errorLogMap.put(System.currentTimeMillis(), ex.getMessage());
    log.error("Exception received on dao", ex);
    // log.error("Error Log Map Content: {}", errorLogMap);
    throw JooqUtil.mapException(ex);
  }
}

I can see the aspect code getting invoked.

2024-04-28T15:19:32.100+05:30 ERROR 88735 --- [nio-8080-exec-1] c.e.h.aspects.DaoExceptionHandler        : Exception received on dao

java.lang.IllegalArgumentException: Test
    at com.example.helloworld.dao.DaoImpl.getError(DaoImpl.java:10) ~[classes/:na]
    at com.example.helloworld.controller.HelloWorldController.sendGreetings(HelloWorldController.java:17) ~[classes/:na]
    ...

If I specify @EnableAspectJAutoProxy in place of EnableLoadTimeWeaving in AspectConfig and remove both javaagent then errorLogMap() is getting correctly injected into DaoExceptionHandler and the aspect is also getting invoked.

With @EnabledLoadTimeWeaving I tried adding only aspectjweaver-1.9.20.jar as -javaagent. But it was not not helping. So added spring-instrument.jar also as -javaagent. But that is also not helping. Can someone suggest if it is possible to use spring beans with load-time weaving of aspects?


Solution

  • The Spring documentation for @Configurable might be helpful, like I said. There is also a Baeldung tutorial.

    You need

    • the spring-aspects dependency,
    • @EnableSpringConfigured in your config class,
    • @Autowired in your aspect.

    The Lombok-generated @AllArgsConstructor does not use @Autowired or @Inject, which is why constructor injection does not work. Besides, the @Component annotation is also wrong for native aspects. You only need that for Spring AOP aspects.

    See my pull request for details.

    P.S.: I also removed @EnableLoadTimeWeaving in favour of only -javaagent:/path/to/aspectjweaver.jar, because this way the aspect weaver is attached earlier. Hot-attachment via @EnableLoadTimeWeaving and spring-instrument is nice, but less reliable (not working for all classloaders) and ready for action later.