I am trying to transform classes such that Spring can see the transformed annotations. This would allow me to dynamically inject @Entity annotations such that Spring Boot will register it as a managed type for data use.
Annotation transformation works but Spring Boot appears to be performing package scanning at the file-jar level missing the transformed versions. This means that Spring is not seeing the annotations because it is analyzing the input stream of the class file within the JAR itself.
The initial spring candidate component scanning is as follows:
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The getResources call ultimately ends up at PathMatchingResourcePatternResolver - doFindAllClassPathResources
Is Springs classloader outside the scope of ByteBuddy in this case?
ClassLoader cl = getClassLoader();
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
After loading the resources, Spring loads the class metadata (With missing annotations)
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
The above getMetadataReader method call eventually ends up at
final class SimpleMetadataReader implements MetadataReader
Which uses the ASM ClassReader to visit the class and annotation metadata. This obviously does not find the @Entity annotations placed by Bytebuddy.
I am not sure if I should somehow link the Classloader into Bytebuddy or override Springs SimpleMetadataReader to have my own implementation backed by ByteBuddy.
Any suggestions? I am using the AgentBuilder to transform the annotations and am running it prior to spring boot startup.
public static void main(String[] args) {
EntityAgent.install(ByteBuddyAgent.install());
InversionContainer.startInversion(args);
}
My ByteBuddy Impl for completeness:
**
* Transform all Non-Abstract Classes which extend BaseEntity
* to have the annotation Entity
*/
public class EntityAgent {
/**
* Installs the agent builder to the instrumentation API.
*/
public static void install(Instrumentation inst) {
createAgentBuilder().installOn(inst);
}
/**
* Creates the AgentBuilder that will redefine any class extending BaseEntity
*/
private static AgentBuilder createAgentBuilder() {
return new AgentBuilder.Default()
.with(toSystemError())
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(AgentBuilder.InitializationStrategy.SelfInjection.EAGER)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.type(getClassMatcher())
.transform(getTransformer());
}
/**
* Set Entity annotation on Class
*/
private static AgentBuilder.Transformer getTransformer() {
return (builder, typeDescription, classloader) -> builder.annotateType(AnnotationDescription.Builder.ofType(Entity.class).build());
}
/**
* Find any non-abstract class that extends BaseEntity
*/
private static ElementMatcher.Junction<TypeDescription> get ClassMatcher() {
return ElementMatchers.isSubTypeOf(BaseEntity.class).and(ElementMatchers.not(ElementMatchers.isAbstract()));
}
}
I reviewed Unable to instrument apache httpclient using javaagent for spring boot uber jar application
Let me know if you want more implementation details. I want to cleanly integrate bytebuddy with spring such that I can instrument classes with spring component annotations.