ByteBuddy @Entity annotation not visible to Spring

2019-06-04 03:25发布

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.

1条回答
仙女界的扛把子
2楼-- · 2019-06-04 03:47

The "problem" is that Spring looks at the original classes and not at the loaded ones. Byte Buddy registers a Java agent that transforms classes when they are loaded but retains the original class files what Spring Boot does not recognize.

Another problem is that Spring investigates the jar file upon start up and before classes are loaded. This means that the Byte Buddy agent was not even active yet when Spring collects its entities.

Instead, Spring should investigate loaded classes as class loaders might not even provide class files but I assume they try to retain the class loading order what causes this outcome.

The only alternative would be to require Byte Buddy to rewrite any relevant jar file upon start up. You would need to parse any ressource on the class path and redefine the jars containing the file. On shutdown, you should reset the state by copying back the original jar state.

Ideally, Spring would add an option to scan by looking at loaded classes instead of parsing class files as both approaches have up and downsides. From my experience, looking at loaded classes is more performant anyways as it avoids the IO duplication so the option could benefit use cases beyond agent recognition.

查看更多
登录 后发表回答