1

我正在尝试转换类,以便 Spring 可以看到转换后的注释。这将允许我动态注入 @Entity 注释,以便 Spring Boot 将其注册为托管类型以供数据使用。

注释转换有效,但 Spring Boot 似乎在文件 jar 级别执行包扫描,缺少转换后的版本。这意味着 Spring 看不到注释,因为它正在分析 JAR 本身内的类文件的输入流。

初始spring候选组件扫描如下:

     public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);

        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);

        if (isCandidateComponent(metadataReader)) {

        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

getResources 调用最终以 PathMatchingResourcePatternResolver 结束 - doFindAllClassPathResources

在这种情况下,Springs 类加载器是否超出了 ByteBuddy 的范围?

ClassLoader cl = getClassLoader();
    Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));

加载资源后,Spring 加载类元数据(缺少注释)

MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);

上面的 getMetadataReader 方法调用最终结束于

final class SimpleMetadataReader implements MetadataReader 

它使用 ASM ClassReader 来访问类和注释元数据。这显然没有找到Bytebuddy放置的@Entity注解。

我不确定是否应该以某种方式将 Classloader 链接到 Bytebuddy 或覆盖 Springs SimpleMetadataReader 以让我自己的实现由 ByteBuddy 支持。

有什么建议么?我正在使用 AgentBuilder 来转换注释并在 spring boot 启动之前运行它。

public static void main(String[] args) {
    EntityAgent.install(ByteBuddyAgent.install());
    InversionContainer.startInversion(args);
}

为了完整性,我的 ByteBuddy Impl:

**
* 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()));
    }
}

我回顾了Unable to Instrument apache httpclient using javaagent for spring boot uber jar application

如果您需要更多实施细节,请告诉我。我想干净地将 bytebuddy 与 spring 集成,这样我就可以使用 spring 组件注释来检测类。

4

1 回答 1

1

“问题”是 Spring 查看原始类而不是加载的类。Byte Buddy 注册了一个 Java 代理,该代理在加载类时转换类,但保留 Spring Boot 无法识别的原始类文件。

另一个问题是 Spring 在启动时和加载类之前会调查 jar 文件。这意味着当 Spring 收集它的实体时,Byte Buddy 代理甚至还没有激活。

相反,Spring 应该调查加载的类,因为类加载器甚至可能不提供类文件,但我假设它们试图保留类加载顺序是什么导致了这种结果。

唯一的选择是要求 Byte Buddy 在启动时重写任何相关的 jar 文件。您需要解析类路径上的任何资源并重新定义包含该文件的 jar。关机时,您应该通过复制回原始 jar 状态来重置状态。

理想情况下,Spring 会通过查看加载的类而不是解析类文件来添加一个扫描选项,因为这两种方法都有优点和缺点。根据我的经验,无论如何,查看加载的类的性能更高,因为它避免了 IO 重复,因此该选项可以使用例受益,超出代理识别。

于 2016-05-24T12:20:03.083 回答