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.
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.