Когда проект включает инструменты Spring dev, bean-компоненты Spring, созданные ASM, не могут внедрять поля - PullRequest
0 голосов
/ 03 апреля 2019

Я хочу создать некоторые динамические bean-компоненты, использующие ASM, это основной класс:

package com.google.asmtest;

import javax.annotation.Resource;

import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;


@SpringBootApplication
@ComponentScan(value = {
    "com.google.asmtest", 
},excludeFilters = {
        @org.springframework.context.annotation.ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Bean2.class)
})
public class AsmTestApplication {

    private static ApplicationContext applicationContext;

    @Resource
    public void setApplicationContext(ApplicationContext applicationContext) {
        AsmTestApplication.applicationContext = applicationContext;
    }


    public static void main(String[] args) throws Exception {

        SpringApplication.run(AsmTestApplication.class, args);
        AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();

        Class<?> bean3Type = ByteClassLoader.defineClass("com.google.asmtest.Bean2", Bean2Dump.dump());
        autowireCapableBeanFactory.createBean(bean3Type);



    }

}

Bean2, компонент, который я хочу создать динамически:

package com.google.asmtest;

import javax.annotation.Resource;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(2)
@Component
public class Bean2 {
    @Resource
    private Bean1 bean1;
}

Bean1:

package com.google.asmtest;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(1)
@Component
public class Bean1 {
}

ByteClassLoader:

package com.google.asmtest;

public class ByteClassLoader extends ClassLoader {
    public static Class<?> defineClass(String name, byte[] bs) {
        return new ByteClassLoader().defineClass(name, bs, 0, bs.length);
    }

}

Bean2Dump генерируется плагином Eclipse ASM, этот класс может создавать байт-код Bean2:

package com.google.asmtest;

import java.util.*;
import org.objectweb.asm.*;

public class Bean2Dump implements Opcodes {

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "com/google/asmtest/Bean2", null, "java/lang/Object", null);

        cw.visitSource("Bean2.java", null);

        {
            av0 = cw.visitAnnotation("Lorg/springframework/core/annotation/Order;", true);
            av0.visit("value", new Integer(2));
            av0.visitEnd();
        }
        {
            av0 = cw.visitAnnotation("Lorg/springframework/stereotype/Component;", true);
            av0.visitEnd();
        }
        {
            fv = cw.visitField(ACC_PRIVATE, "bean1", "Lcom/google/asmtest/Bean1;", null, null);
            {
                av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true);
                av0.visitEnd();
            }
            fv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(10, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "Lcom/google/asmtest/Bean2;", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

Pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.google</groupId>
    <artifactId>AsmTest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>AsmTest</name>
    <description>test manager project. </description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>




    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Когда я включаю spring-boot-devtools, основной класс выдает следующее исключение, появляется сообщение об исключении [Bean с именем 'bean1', как ожидается, будет иметь тип 'com.google.asmtest.Bean1', нона самом деле был тип 'com.google.asmtest.Bean1']:

Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.google.asmtest.Bean2': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'bean1' is expected to be of type 'com.google.asmtest.Bean1' but was actually of type 'com.google.asmtest.Bean1'
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:324)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1378)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:575)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:304)
    at com.google.asmtest.AsmTestApplication.main(AsmTestApplication.java:35)
    ... 5 more
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'bean1' is expected to be of type 'com.google.asmtest.Bean1' but was actually of type 'com.google.asmtest.Bean1'
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:392)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:525)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:496)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:630)
    at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:180)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
    ... 10 more

Когда я удаляю spring-boot-devtools, программа работает нормально. Как я могу решить эту проблему?

Edit: теперь я знаю, как решить эту проблему, я изменил код ByteClassLoader, чтобы определить класс, используя ClassLoader текущего потока, а не новый экземпляр ClassLoader, и удалил Bean2.java из исходного пути, тогда программа работает нормально, новый код ByteClassLoader:

package com.google.asmtest;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ByteClassLoader {
    public static Class<?> defineClass(String name, byte[] bs) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        //return new ByteClassLoader().defineClass(name, bs, 0, bs.length);

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Method defineClass = ClassLoader.class
                .getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        return (Class<?>) defineClass.invoke(classLoader, name, bs, 0, bs.length);
    }

}


...