直送入主站问题总结

本文探讨了Spring框架中因资源文件重名导致的Bean加载失败问题,并提供了解决方案。同时,文章还讨论了Bean注入失败的原因及解决方法,包括使用不同注解的影响。此外,还介绍了未正确配置component-scan导致的问题及其解决思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

直送入主站问题总结

一:jar资源文件重名导致的bean加载失败

问题背景:gse-service-sdp.jar的murch-plugin.xml定义了

<springService>

<configFilePath>spring/spring-context.xml</configFilePath>

</springService>

74879f2d-3d4e-4e1e-99d8-ff4eb7896a8f.png

spring/spring.xml,内容如下:

75763d06-23d6-49b6-bffd-b641d315530b.png

但是,在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

d760185a-d71a-4f5a-8f45-0a6e593b9143.png

但是里面定义的bean是不同的,那么如果使用下面这个代码读取spring.xml,究竟读取的是哪一个spring.xml

e5667015-bf0f-4eb8-9429-254223a55503.png

正确答案是config.jar,原因是spring按照jar加载顺序查找spring.xml,这里的加载顺序(按照abcd...名称)是config.jar -->nuto.jar,首先找到了config.jar里的spring.xml,直接返回该资源,后面的nuto.jar里的xml忽略。走读源码:

1、寻找资源由AbstractBeanDefinitionReader#loadBeanDefinitions进入

189b2460-c8fd-4e5a-a7d6-dbd6fc4e3492.png

在getResources时会返回资源,如果定义的是classpath:resource/spring.xml,Spring会根据前缀classpath返回一个ClassPathResource对象

c1c85e4a-cc40-4c53-9b2d-b771c3b94320.png

2、在URLClassLoader,把jar中的spring.xml当做URL访问

ca67d6ea-652a-4f13-889b-d93b151487da.png

3、类加载器调用顺序:AppClassLoader -->ExtClassLoader,先由AppClassLoader的parent:ExtClassLoader找,若ExtClassLoader getResource没找到,ExtClassLoader通过getBootstrapResource寻找。ExtClassLoader一无所获,最后AppClassLoader中的兜底

1eaeb948-cc0a-471a-b580-0386b23d34e3.png

在URLClassLoader(AppClassLoader继承了URLClassLoader)& URLClassPath(搜索class & resource),首先找到jar:file:/Users/xiude/config.jar!/resource/spring.xml,直接返回

6ed9a741-f166-4e62-b930-3f3f9c4c213a.png

如果所有jar(config & nuto.jar)的spring.xml加载,需要使用classpath*

456e07d6-2f7c-44ce-a28a-8b3ea74a192c.png

此时查找资源,匹配的前缀是classpath*,会扫描所有jar的classpath

10adc3a0-f218-4a2f-9498-12c584e60b34.png

8dc856a6-ff39-4687-a69d-32c98e1fe4c6.png

此时所有jar下的spring.xml都被当做Resource加载进来了。

二:bean注入失败

问题背景:在GlspServiceClient使用@Resource,GlspServiceClient注入失败

3f53f284-ae2f-4b56-8f54-18026b05ccb1.png

解决思路:在xml配置文件中添加

eb572a6c-b3d5-480d-8c7f-5049560fa76d.png

也可以使用 <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

e3b09093-2c66-4814-b5fc-76af487d9771.png

众所周知,@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>

d82351a2-f608-4631-b0bc-a52330769dde.png

d72932f2-e9ea-43c7-8f42-1de6cf8d9269.png

当处理beanManager时,beanManager含有@Resource toStringService,会被封装成InjectionMetadata(impl.BeanManager, impl.BeanManager.toStringService),被加入injectionMetadataCache(beanManager,InjectionMetadata)。此时只是完成了元数据的匹配,toStringService还没有没实例化,走到第line 553行populateBean方法后才会实例化

6ca80352-7ebf-4d58-8db8-3f3e71b09b25.png

如果不想使用Resource || Autowire完成bean注入,可以使用default-autowire

ae21a2ef-caa3-41cf-8e96-f67e5d0f7ef1.png

default-autowire 常见的有byName、byType、constructor,下面主要阐述byName、byType的区别

byName按照待注入对象名称匹配,setToStringService 必须与 toStringService 保持一致,否则注入失败

b2e36504-3021-4bd2-9099-5f087c1a7ee2.png

    <bean id="toStringService" class="impl.ToStringServiceImpl">
        <property name="serviceName" value="This is toStringService "></property>
    </bean>

byType按照类型匹配,set方法的命名无需和对象名称保持一致,只要都是ToStringService类型即可

3c1c7d27-3928-44f4-87d7-9339da583adb.png

但是,如果在配置文件中定义了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报错

a1d1475d-df7b-4a08-943c-fa140be10608.png

30ac2df9-3a64-4058-bde1-00bbc2769b31.png

解决思路:

在spring-context-international-publish.xml添加

ee09321b-bb03-40b8-9111-76b01b4d71ab.png

正确使用@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维护

94af9087-935f-4298-99a5-6b72fb7beb4f.png

四:Cglib复制引发的NPE

问题背景:复制GlobalServivceQueryTO对象,在LIne 13报错

252c8b65-651b-498f-86ca-9e01b698a15b.png

e4080d36-d845-4ea4-8c13-bd61a52fbb8e.png

解决思路:

GlobalServiceQueryTO 存在这个方法,cglib复制时误认为sellGlobal为一个字段,但实际该类中不存在sellGlobal,Copy时,会根据isSellGlobal && boolean截取is后面字符,即SellGlobal。Cglib会认为sellGlobal为一个字段,这样调用该字段的geSellGlobal || setSellGlobal 方法时,肯定返回member = null,再使用member.modifier时导致NPE.因此方法上避免使用boolean is开头的命名。

64abf276-968b-4c61-85bb-e23f5bb3f5a8.png

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,字段名首字母大写!

2d283576-7bcb-4d9c-bee5-dc0cbec0a502.png

若有收获,就赏束稻谷吧

0 颗稻谷

转载于:https://my.oschina.net/u/2302503/blog/1859090

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值