我想使用ASM创建一些动态bean,这是主类:
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,我要动态创建的bean:
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时,主类会引发以下异常,该异常消息为[名为'bean1'的Bean的类型应为'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时,程序运行正常,如何解决此问题?
修改: 现在,我知道如何解决这个问题,我更改了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);
}
}