spring-bootaspectjspring-data-mongodbspring-aopload-time-weaving

Aspect-oriented extension for entities in Spring Boot


I have a Spring Boot 2.7.6 application. I have some entities that are managed via various Mongo repositories. I would like to extend my entities with metadata. So I thought I'd build a metadata repository that contains the metadata for all entities and extend the entities with an interface IMetadata that contains a default implementation. This method should then load the data from the metadata repository using an aspect and then return it.

This is what my classes look like:

package io.zzzz.interfaces;
//imports are there

public interface IMetadata {
  @JsonIgnore
  default List<Metadata> getMetadata() {
    return null;
  }


package io.zzzz.entities;
//imports are there

@Getter
@Setter
@NoArgsConstructor
@Configurable
@Document("samples")
public class SampleEntity implements IMetadata {
  @Id private String id;
  // ... many other fields

package io.zzzz;
//imports are there

@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass=true)
@Slf4j
public class MetadataAspect implements ApplicationContextAware {
  @Override
  public void setApplicationContext(ApplicationContext context) throws BeansException {
    metadataService = context.getBean(MetadataService.class);
  }

  @Around("execution(* getMetadata(..))")
  public Object getMetadataAdvice(ProceedingJoinPoint joinPoint) {
    log.debug("Accessing metadata ...");
    var metadataOwner = joinPoint.getTarget();
    var md = metadataService.getMetadataForEntity((IMetadata) metadataOwner);
    log.debug("Accessed metadata.");
    return md;
  }
}

I have added the following dependencies:

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-instrument</artifactId>
      <version>5.3.24</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.7</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.9.7</version>
    </dependency>

I added @EnableLoadTimeWeaving to my application and also added a javaagent to the VM Options -javaagent:<path>/spring-instrument-5.3.24.jar.

Because none of this worked, I created the following aop.xml in META-INF:

<aspectj>
  <weaver>
    <include within="io.zzzz.entities..*"/>
  </weaver>
  <aspects>
    <aspect name="io.zzzz.MetadataAspect"/>
  </aspects>
</aspectj>

I added the VM option -Daj.weaving.verbose=true to extend the logging during startup to see if something happened. I see some weavering, so that should at least basically work. If I formulate the aspect and aop-config a bit more generally, then I also see that, for example, service methods are called and passed through the aspect, but my getMetadata method is not.

I don't know what to do. Maybe my idea won't work at all.


Solution

  • I had hoped that the SampleEntity would be spring-managed by the @Configurable annotation

    No, it is still unmanaged without any @Component, @Service or similar Spring component annotations. As the name implies, it simply makes an unmanaged object configurable, i.e. you can inject Spring beans into it using @Autowired. See this Baeldung tutorial and of course the Spring documentation for more information:

    Please note that you need to activate native Aspect weaving in combination with the spring-aspects aspect library in order for @Configurable to have any effect via AnnotationBeanConfigurerAspect.


    Update: I forgot to mention, that if you want to apply AOP to non-managed classes, you have a choice to either make them managed as mentioned before or to use native AspectJ, which has no such Spring-related limitations and can be applied to POJO code, with or without Spring. AspectJ also does not require or create any proxies.