0x0 背景
最近在測試從項目外部加載class進來踩麦,然后通過BeanFactoryPostProcessor注入到spring容器中球切,結(jié)果出了一點問題:外部加載的class中@Autoware和@Resource注解都無法注入屬性虹曙!
報錯如下:
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'testService' is expected to be of type 'com.fly.architecture.service.TestService' but was actually of type 'com.fly.architecture.service.TestService'
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:395) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
可以看到迫横,所需注入的屬性類是com.fly.architecture.service.TestService
,實際注入的屬性類是com.fly.architecture.service.TestService
根吁,看起來完全一樣员淫!
完整代碼如下:
@Component
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private static final String CLASS_PATH = "C:\\Users\\code\\";
private Class<?> compileBeanClass() throws IOException, ClassNotFoundException {
//自定義一個類加載器,加載class
URL url = new URL("file:///" + CLASS_PATH );
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
return urlClassLoader.loadClass("TestController");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("調(diào)用了自定義的BeanFactoryPostProcessor加載bean");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory;
Class<?> beanClass;
try {
beanClass = compileBeanClass();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
throw new BeanCreationException(e.getMessage());
}
//將加載的class設(shè)置為bean definition击敌,然后注入到spring容器中
AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(beanClass);
registry.registerBeanDefinition(beanClass.getSimpleName(), beanDefinition);
}
}
0x1 原因
進入debug介返,從報錯的地方下斷點,發(fā)現(xiàn)所需的類和bean的類雖然名字相同沃斤,但是Class對象卻不是同一個:
從圖中可以看出圣蝎,兩個對象雖然名稱一致,但是hashCode以及內(nèi)部的ClassLoader都不相同衡瓶,因此spring在判斷注入的bean類型和所需類型是否相同時徘公,發(fā)現(xiàn)兩者不一致!
0x2 解決方法
將類加載的地方哮针,改成如下方式关面,利用class loader的上級委托機制,實現(xiàn)不重復(fù)加載相同類:
//正確做法十厢,先獲取當(dāng)前類的classloader
ClassLoader classLoader = getClass().getClassLoader();
//這里很重要等太,需要設(shè)置當(dāng)前classloader為父加載器,否則會加載重復(fù)的類進來
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, classLoader);
return urlClassLoader.loadClass("TestController");