Spring中的getBean

本文探讨了在Spring框架中如何正确地使用FactoryBean及其返回的对象。通过实例解释了当一个bean实现了FactoryBean接口时,从Spring Bean工厂获取的并非其自身,而是其getObject方法返回的对象。
通常你不大会通过Spring中的ApplicationContext直接取bean,你往往通过IOC的方式在你的action或者service等中得到现成的bean,但是在一些特殊情况下你会这样使用。

我今天就遇到这种情况
我需要在测试的时候刷新缓存,那么我直接取出SqlMapClient,虽然也可以搞个注入什么的,但那样太麻烦,

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>classpath:/moxtv/central/dao/impl/write/maps/sql-map-config.xml</value>
</property>
</bean>



后来我写了如下代码
.......   
SqlMapClient client = (SqlMapClient) SpringBeanProxy
.getBean("sqlMapClient");
.......



一切ok

但是后来我发现org.springframework.orm.ibatis.SqlMapClientFactoryBean
并没有实现SqlMapClient接口,上面那种转型的写法怎么会正确?

通过观察发现org.springframework.orm.ibatis.SqlMapClientFactoryBean 中含有一个getBean方法返回SqlMapClient, 然后这个方法被org.springframework.beans.factory.support.AbstractBeanFactory的 getObjectForSharedInstance方法和StaticListableBeanFactory的getBean方法调用,这两个方法进行调用时都有一个共同的特征就是
if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) ...{
try ...{
return ((FactoryBean) bean).getObject();
}
catch (Exception ex) ...{
throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
}
}


再一看SqlMapClientFactoryBean 果然实现了FactoryBean 接口

总结如下:
如果一个bean属于 FactoryBean,那么从spring的bean工厂取出来的时候,并不是它本身,而是它的getObject返回的对象.所以表面上的那种转型不匹配就不足为怪了
<think>我们面对的是一个使用实例初始化块(非静态初始化块)来初始化三个final字段的场景。这些字段通过调用各自类的静态getInstance方法(内部可能调用SpringgetBean)来赋值。 问题在于:这种写法在测试中会遇到问题(因为静态初始化可能依赖Spring上下文,而测试可能未启动Spring),并且代码风格上可能不够优雅。 优化目标: 1. 避免在实例初始化块中直接调用静态方法(尤其是可能依赖外部环境的静态方法),以提高可测试性。 2. 遵循依赖注入原则,将依赖外部创建的对象通过构造器或setter注入。 优化方案: 方案一:改为构造器注入 将这三个字段的初始化改为通过构造器传入,这样在测试时可以传入模拟对象。 方案二:使用setter注入(如果类需要默认构造器) 方案三:如果项目允许,可以使用Spring的注解(如@Autowired)进行字段注入,但这样会引入Spring依赖,且测试时仍需处理依赖。 具体步骤(以构造器注入为例): 原代码: private final IndexConfigUtil indexConfigUtil; private final HiddenAttributeConfig hiddenAttributeConfig; private final Clustering clustering; { hiddenAttributeConfig = HiddenAttributeConfig.getInstance(); indexConfigUtil = IndexConfigUtil.getInstance(); clustering = Clustering.getInstance(); } 优化后: private final IndexConfigUtil indexConfigUtil; private final HiddenAttributeConfig hiddenAttributeConfig; private final Clustering clustering; public ClassTreeManager(long workSpaceId, WorkSpaceType type, HiddenAttributeConfig hiddenAttributeConfig, IndexConfigUtil indexConfigUtil, Clustering clustering) { // 其他初始化 this.hiddenAttributeConfig = hiddenAttributeConfig; this.indexConfigUtil = indexConfigUtil; this.clustering = clustering; } 这样,在创建ClassTreeManager对象时,外部需要传入这三个依赖的对象。而在Spring环境中,可以通过配置Bean的方式注入。 如果这三个对象是单例的,我们可以在Spring配置中先获取它们的实例,然后注入到ClassTreeManager中。 在Spring配置中(例如使用Java配置): @Configuration public class AppConfig { @Bean public HiddenAttributeConfig hiddenAttributeConfig() { return HiddenAttributeConfig.getInstance(); } // 同样配置其他两个 @Bean public ClassTreeManager classTreeManager() { return new ClassTreeManager(workSpaceId, WorkSpaceType.DFI_DXS_5000, hiddenAttributeConfig(), indexConfigUtil(), clustering()); } } 或者使用自动装配(如果HiddenAttributeConfig等已经是Spring管理的Bean): @Service public class ClassTreeManager { private final HiddenAttributeConfig hiddenAttributeConfig; private final IndexConfigUtil indexConfigUtil; private final Clustering clustering; @Autowired public ClassTreeManager(HiddenAttributeConfig hiddenAttributeConfig, IndexConfigUtil indexConfigUtil, Clustering clustering) { this.hiddenAttributeConfig = hiddenAttributeConfig; this.indexConfigUtil = indexConfigUtil; this.clustering = clustering; } } 这样,在测试时,我们可以轻松地传入模拟对象,而不需要依赖于静态方法。 如果因为某些原因不能改变构造器(比如已有大量代码使用原构造器),我们可以保留原构造器,并标记为@Deprecated,同时添加一个新的构造器用于注入。然后逐步迁移。 另外,对于实例初始化块中的代码,如果无法立即重构,也可以考虑使用一个初始化方法,并在Spring初始化后调用(但不如构造器注入干净)。 总结:推荐使用构造器注入,这样代码更清晰,易于测试。 注意:如果HiddenAttributeConfig.getInstance()内部是通过Spring的ApplicationContext.getBean来获取实例,那么这种重构不会影响功能,只是将获取方式从类内部转移到了外部(由Spring容器管理)。 重构后测试代码示例: @Test public void test() { HiddenAttributeConfig mockHAC = mock(HiddenAttributeConfig.class); IndexConfigUtil mockICU = mock(IndexConfigUtil.class); Clustering mockClustering = mock(Clustering.class); ClassTreeManager manager = new ClassTreeManager(workSpaceId, WorkSpaceType.DFI_DXS_5000, mockHAC, mockICU, mockClustering); // 然后测试manager的其他方法 } 这样,测试时就不需要处理静态初始化块带来的问题了。 因此,优化后的代码将更加灵活,易于测试,符合依赖注入原则。 §§相关问题§§1. 如何在不改变原有类结构的情况下,使ClassTreeManager的依赖可配置以方便测试?2. 如果HiddenAttributeConfig.getInstance()方法内部依赖了Spring上下文,在非Spring环境中如何重构以避免Spring依赖?3. 使用构造器注入时,如何处理循环依赖问题?4. 对于已经存在的使用原构造器的代码,如何逐步迁移到新的依赖注入方式?5.Spring环境中,如何确保重构后的ClassTreeManager正确地从Spring容器获取依赖? </think>### 优化静态依赖初始化的方案 针对使用 `getInstance()` 调用 Spring `getBean()` 的初始化代码,以下是优化的重构方案: #### 方案一:改用构造器注入(推荐) ```java private final IndexConfigUtil indexConfigUtil; private final HiddenAttributeConfig hiddenAttributeConfig; private final Clustering clustering; // 重构为依赖注入的构造器 public ClassTreeManager(long workSpaceId, WorkSpaceType type, HiddenAttributeConfig hiddenAttributeConfig, IndexConfigUtil indexConfigUtil, Clustering clustering) { this.workSpaceId = workSpaceId; this.type = type; this.hiddenAttributeConfig = hiddenAttributeConfig; // 注入依赖 this.indexConfigUtil = indexConfigUtil; // 注入依赖 this.clustering = clustering; // 注入依赖 } ``` #### 方案二:使用 Spring 的 `@Autowired`(如适用) ```java @Autowired // Spring 自动注入 private HiddenAttributeConfig hiddenAttributeConfig; @Autowired private IndexConfigUtil indexConfigUtil; @Autowired private Clustering clustering; // 删除实例初始化块 ``` #### 方案三:延迟初始化封装器 ```java private final Supplier<HiddenAttributeConfig> hiddenConfigSupplier; private final Supplier<IndexConfigUtil> indexConfigSupplier; private final Supplier<Clustering> clusteringSupplier; public ClassTreeManager(long workSpaceId, WorkSpaceType type) { this(workSpaceId, type, HiddenAttributeConfig::getInstance, IndexConfigUtil::getInstance, Clustering::getInstance); } // 注入函数式接口 public ClassTreeManager(long workSpaceId, WorkSpaceType type, Supplier<HiddenAttributeConfig> hiddenConfigSupplier, Supplier<IndexConfigUtil> indexConfigSupplier, Supplier<Clustering> clusteringSupplier) { this.hiddenConfigSupplier = hiddenConfigSupplier; this.indexConfigSupplier = indexConfigSupplier; this.clusteringSupplier = clusteringSupplier; } // 使用时按需获取 public void someMethod() { HiddenAttributeConfig config = hiddenConfigSupplier.get(); // ... } ``` ### 重构优势说明 1. **解耦与可测试性**: - 消除对 Spring 容器的直接依赖 - 测试时可传入 Mock 对象 ```java // 测试示例 ClassTreeManager manager = new ClassTreeManager(1, WorkSpaceType.DFI_DXS_5000, mock(HiddenAttributeConfig.class), mock(IndexConfigUtil.class), mock(Clustering.class)); ``` 2. **初始化时机控制**: - 避免类加载时强制初始化(原实例块在对象创建时执行) - 支持按需初始化(方案三) 3. **设计模式优化**: - 符合依赖倒置原则(DIP) - 支持策略模式替换实现 ### 最佳实践建议 1. **兼容现有代码**: ```java // 保留原有构造器(标记为@Deprecated) @Deprecated public ClassTreeManager(long workSpaceId, WorkSpaceType type) { this(workSpaceId, type, HiddenAttributeConfig::getInstance, IndexConfigUtil::getInstance, Clustering::getInstance); } ``` 2. **Spring 整合技巧**: ```java @Bean public ClassTreeManager classTreeManager( HiddenAttributeConfig hac, IndexConfigUtil icu, Clustering clustering) { return new ClassTreeManager(defaultWorkSpaceId, DEFAULT_TYPE, hac, icu, clustering); } ``` 3. **防御性编程**: ```java public ClassTreeManager(/*参数*/) { this.hiddenAttributeConfig = Objects.requireNonNull( hiddenAttributeConfig, "HiddenAttributeConfig必须非空"); // 其他非空检查 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值