直送入主站问题总结
一:jar资源文件重名导致的bean加载失败
问题背景:gse-service-sdp.jar的murch-plugin.xml定义了
<springService>
<configFilePath>spring/spring-context.xml</configFilePath>
</springService>
spring/spring.xml,内容如下:
但是,在SDP启动初始化bean时,却无法加载图中三个Service
解决思路:更换spring.xml --->spring-context-international-publish.xml,原因是在其他jar中存在同名的spring/spring.xml,导致应用加载时,加载了其他jar中的spring.xml,无视正确的gse-service-sdp.jar。
通过一个demo重现该问题,demo工程引入了2个jar:config.jar & nuto.jar,这2个jar里都有resource/spring.xml
但是里面定义的bean是不同的,那么如果使用下面这个代码读取spring.xml,究竟读取的是哪一个spring.xml
正确答案是config.jar,原因是spring按照jar加载顺序查找spring.xml,这里的加载顺序(按照abcd...名称)是config.jar -->nuto.jar,首先找到了config.jar里的spring.xml,直接返回该资源,后面的nuto.jar里的xml忽略。走读源码:
1、寻找资源由AbstractBeanDefinitionReader#loadBeanDefinitions进入
在getResources时会返回资源,如果定义的是classpath:resource/spring.xml,Spring会根据前缀classpath返回一个ClassPathResource对象
2、在URLClassLoader,把jar中的spring.xml当做URL访问
3、类加载器调用顺序:AppClassLoader -->ExtClassLoader,先由AppClassLoader的parent:ExtClassLoader找,若ExtClassLoader getResource没找到,ExtClassLoader通过getBootstrapResource寻找。ExtClassLoader一无所获,最后AppClassLoader中的兜底
在URLClassLoader(AppClassLoader继承了URLClassLoader)& URLClassPath(搜索class & resource),首先找到jar:file:/Users/xiude/config.jar!/resource/spring.xml,直接返回
如果所有jar(config & nuto.jar)的spring.xml加载,需要使用classpath*
此时查找资源,匹配的前缀是classpath*,会扫描所有jar的classpath
此时所有jar下的spring.xml都被当做Resource加载进来了。
二:bean注入失败
问题背景:在GlspServiceClient使用@Resource,GlspServiceClient注入失败
解决思路:在xml配置文件中添加
也可以使用 <context:annotation-config/> 替代CommonAnnotationBeanPostProcessor,因为<context:annotationconfig/> 将隐式地向 Spring 容器注册这 4 个 BeanPostProcessor
-
AutowiredAnnotationBeanPostProcessor
-
CommonAnnotationBeanPostProcessor
-
PersistenceAnnotationBeanPostProcessor
-
RequiredAnnoationBeanPostProcessor
或者范围更广的使用 <context:component-scan base-package="main"/> ,效果一样。
RequiredAnnoationBeanPostProcessor:检查带有@Required注解的所有Bean属性是否设置;
PersistenceAnnotationBeanPostProcessor:当field上使用了@PersistenceUnit和@PersistenceContext
众所周知,@Resourc 默认按字段名注入,没有则按照类型;@Autowired 默认按照类型注入,没有则按照名称注入。例如Spring注入时,首先在applicationContext.xml中找Bean id="toStringService",没有的话再找Bean id="toStringServiceA",由于按照名称,applicationContext定义若干个相同类型的bean没问题
@Resource(name="toStringServiceA")
ToStringService toStringService;
<bean id="toStringService" class="impl.ToStringServiceImpl">
<property name="serviceName" value="This is toStringService "></property>
</bean>
<bean id="toStringServiceA" class="impl.ToStringServiceImpl">
<property name="serviceName" value="This another toStringService"></property>
</bean>
如果使用@Autowired,字段名称可以随意起,但是配置中只能有一个bean
@Autowired
ToStringService toStringService;
<bean id="toStringServiceA" class="impl.ToStringServiceImpl">
<property name="serviceName" value="This another toStringService"></property>
</bean>
当处理beanManager时,beanManager含有@Resource toStringService,会被封装成InjectionMetadata(impl.BeanManager, impl.BeanManager.toStringService),被加入injectionMetadataCache(beanManager,InjectionMetadata)。此时只是完成了元数据的匹配,toStringService还没有没实例化,走到第line 553行populateBean方法后才会实例化
如果不想使用Resource || Autowire完成bean注入,可以使用default-autowire
default-autowire 常见的有byName、byType、constructor,下面主要阐述byName、byType的区别
byName按照待注入对象名称匹配,setToStringService 必须与 toStringService 保持一致,否则注入失败
<bean id="toStringService" class="impl.ToStringServiceImpl">
<property name="serviceName" value="This is toStringService "></property>
</bean>
byType按照类型匹配,set方法的命名无需和对象名称保持一致,只要都是ToStringService类型即可
但是,如果在配置文件中定义了2个相同类型的bean,会导致Spring无法判定注入哪一个bean报错,这种问题在byName是不会存在的,因为byName是按照bean id 来注入的
<bean id="toStringService" class="impl.ToStringServiceImpl">
<property name="serviceName" value="This is toStringService "></property>
</bean>
<bean id="toStringServiceA" class="impl.ToStringServiceImpl">
<property name="serviceName" value="This is another toStringService"></property>
</bean>
三:没有添加component-scan导致找不到bean
问题背景:在ServiceCacheManager中定义了glspCacheReadClient,但是使用glspCacheReadClient报错
解决思路:
在spring-context-international-publish.xml添加
正确使用@Componet 时,不用在applicationContext.xml再写<bean id="..." class="...">
1、如果在一个类上添加Component(),默认告诉beanFactory,存储一个bean id="beanManager";如果想修改bean id,使用Component(”XXX“),即 bean id="XXX";使用方式:
BeanManager beanManager = (BeanManager)context.getBean("beanManager");
@Component()
//@Component("beanManager")
public class BeanManager {
@Resource
//@Autowired
ToStringService toStringService;
}
@Component("StringService")
//@Scope("prototype")
public class ToStringServiceImpl implements ToStringService {}
Spring会将BeanManager.toStringService和StringService建立起映射关系,所以名称随意起,不用担心匹配不上。映射由BeanFactory维护
四:Cglib复制引发的NPE
问题背景:复制GlobalServivceQueryTO对象,在LIne 13报错
解决思路:
GlobalServiceQueryTO 存在这个方法,cglib复制时误认为sellGlobal为一个字段,但实际该类中不存在sellGlobal,Copy时,会根据isSellGlobal && boolean截取is后面字符,即SellGlobal。Cglib会认为sellGlobal为一个字段,这样调用该字段的geSellGlobal || setSellGlobal 方法时,肯定返回member = null,再使用member.modifier时导致NPE.因此方法上避免使用boolean is开头的命名。
java.beans.Introspector, name.substring(2)截取到SellGlobal,java.beans.PropertyDescriptor 中的baseName=SellGlobal;
// The base name of the method name which will be prefixed with the
// read and write method. If name == "foo" then the baseName is "Foo"
private String baseName;
baseName非字段名,而是为了方便getter/setter,字段名首字母大写!
若有收获,就赏束稻谷吧
0 颗稻谷