Spring依赖管理核心技巧与应用
以下内容是 Spring Framework 官方文档中关于依赖管理(Dependency Management)的几个核心章节,主要包括:
1.4.3 Using depends-on1.4.4 Lazy-initialized Beans1.4.5 Autowiring Collaborators1.4.6 Method Injection
下面我将用通俗易懂、结构清晰的方式为你逐段解释这些概念的本质和实际用途,帮助你真正“理解”它们。
✅ 一、depends-on:控制 Bean 的初始化顺序
📌 核心思想:
有时候,某个 Bean 的创建依赖于另一个 Bean 必须先被初始化完成,即使它没有直接引用那个 Bean。比如数据库驱动注册、静态代码块执行等。
这时就可以使用 depends-on 来强制指定依赖关系。
🔧 示例说明:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
- 虽然
beanOne没有把manager当作属性注入,但 Spring 会确保:- 先创建
manager - 再创建
beanOne
- 先创建
💡 场景举例:
manager可能是一个负责加载配置或注册 JDBC 驱动的类,它的静态块做了关键操作,所以必须在beanOne创建前运行。
🔄 多个依赖怎么写?
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
支持逗号、空格、分号分隔多个 bean 名称。
⚠️ 特别注意:
depends-on不仅影响 初始化顺序,也影响 销毁顺序。- 如果 A
depends-onB,则:- 初始化时:B → A
- 销毁时:A → B (反向)
这保证了资源安全释放。
✅ 二、lazy-init="true":延迟初始化 Bean
📌 默认行为(饿汉式):
Spring 的 ApplicationContext 在启动时就会预先创建所有 singleton bean(单例 Bean),称为“预实例化”。
优点:尽早发现问题(如配置错误、循环依赖等)。
缺点:启动慢,尤其对一些“重量级但不常用”的 Bean(如缓存服务、大数据处理器)。
🔧 解决方案:懒加载
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
- 这个 Bean 不会在容器启动时创建
- 第一次被请求(getBean 或被其他 Bean 引用)时才创建
❗ 一个重要例外:
如果一个 lazy bean 被一个 非 lazy 的 singleton bean 依赖,那么它仍然会在容器启动时被创建!
因为 Spring 必须满足那个非 lazy bean 的依赖。
🏗️ 全局设置懒加载:
<beans default-lazy-init="true"><!-- 所有 bean 默认都是 lazy -->
</beans>
适合性能敏感的应用,在开发环境可关闭以便快速发现问题。
✅ 三、autowiring:自动装配协作 Bean
📌 是什么?
让 Spring 自动帮你找合适的 Bean 注入到当前 Bean 中,而不用手动写 <property ref="...">。
🎯 四种模式对比:
| 模式 | 说明 | 使用场景 |
|---|---|---|
no(默认) | 不自动装配,必须显式用 <ref> | 推荐生产环境使用,清晰可控 |
byName | 根据属性名匹配 Bean ID | 如 setUserDao(UserDao) → 查找 id=“userDao” 的 Bean |
byType | 根据类型匹配唯一 Bean | 类型唯一时很方便 |
constructor | 构造函数参数按类型自动匹配 | 和 byType 类似,用于构造器注入 |
✅ 示例:byName
public class UserService {private UserDao userDao;public void setUserDao(UserDao userDao) { this.userDao = userDao; }
}
<bean id="userService" class="UserService" autowire="byName"/>
<bean id="userDao" class="UserDaoImpl"/> <!-- 名字匹配 -->
→ Spring 自动调用 setUserDao(...) 把 userDao 注入进去。
⚠️ 局限性 & 缺点:
- 不能自动装配基本类型(int, String, boolean 等)
- 模糊性问题:如果有两个相同类型的 Bean,
byType就会抛异常No unique bean of type X found. Expected single matching bean but found 2: bean1, bean2 - 可读性差:别人看不懂你的 Bean 到底依赖了谁
- 工具难以分析:文档生成工具无法提取依赖信息
✅ 如何解决歧义?
| 方法 | 说明 |
|---|---|
autowire-candidate="false" | 排除某个 Bean 被自动装配 |
primary="true" | 设为首选 Bean(当多个候选时优先选它) |
| 改用注解方式(@Autowired + @Qualifier) | 更灵活精确 |
示例:排除自动装配
<bean id="badDao" class="BadUserDao" autowire-candidate="false"/>
这样即使类型匹配,也不会被选中。
全局限制命名模式:
<beans default-autowire-candidates="*Service,*DAO">
只有名字符合模式的 Bean 才能参与自动装配。
✅ 四、方法注入(Method Injection)——高级技巧
🧩 核心问题:
单例 Bean 如何每次都能获取一个新的原型(prototype)Bean?
常见于:命令模式、工厂模式、事务处理等需要“每次新建对象”的场景。
❌ 普通注入不行!
@Service
public class CommandManager {@Autowiredprivate Command command; // 如果 command 是 prototype,这里只会注入一次!
}
→ 因为 CommandManager 是单例,只初始化一次,command 也就固定了。
✅ 解法一:查找方法注入(Lookup Method Injection)
思路:
让 Spring 动态重写(override)一个方法,让它每次都返回新的 Bean 实例。
实现方式(XML):
<!-- 原型 Bean -->
<bean id="myCommand" class="AsyncCommand" scope="prototype"/><!-- 单例管理器 -->
<bean id="commandManager" class="CommandManager"><lookup-method name="createCommand" bean="myCommand"/>
</bean>
Java 类要定义抽象方法:
public abstract class CommandManager {public Object process() {Command cmd = createCommand(); // 实际由 Spring 动态实现return cmd.execute();}protected abstract Command createCommand(); // 抽象方法,Spring 来覆盖
}
Spring 会在运行时通过 CGLIB 生成子类,覆盖 createCommand(),使其每次返回 myCommand 的新实例。
Kotlin 注意事项:
Kotlin 默认类和方法是 final 的,CGLIB 无法继承。你需要:
- 使用
open关键字 - 或者启用 kotlin-spring 插件自动打开类
✅ 解法二:@Lookup 注解(更现代)
@Component
public abstract class CommandManager {@Lookupprotected abstract Command createCommand();public void run() {Command cmd = createCommand(); // 每次都是新实例cmd.execute();}
}
也可以指定名字:
@Lookup("myCommand")
protected abstract Command createCommand();
✅ 推荐使用这种方式,比 XML 更简洁。
✅ 解法三:ObjectFactory / Provider(推荐替代方案)
比起 @Lookup,更推荐使用:
方式1:ObjectFactory<T>
@Service
public class CommandManager {@Autowiredprivate ObjectFactory<Command> commandFactory;public void run() {Command cmd = commandFactory.getObject(); // 每次拿新实例cmd.execute();}
}
方式2:JSR-330 的 Provider<T>
@Autowired
private Provider<Command> commandProvider;public void run() {Command cmd = commandProvider.get(); // 每次 new 一个
}
✅ 优势:
- 不需要抽象类
- 不依赖 CGLIB
- 更容易测试
- 更符合函数式编程风格
✅ 方法替换(Arbitrary Method Replacement)——几乎不用
这是 Spring 提供的一个冷门功能:可以完全替换一个方法的实现。
示例:
你想把 MyValueCalculator.computeValue(String) 方法替换成别的逻辑。
<bean id="myCalc" class="MyValueCalculator"><replaced-method name="computeValue" replacer="myReplacer"><arg-type>String</arg-type></replaced-method>
</bean><bean id="myReplacer" class="ReplacementComputeValue"/>
ReplacementComputeValue 实现 MethodReplacer 接口,提供新的逻辑。
⚠️ 实际上很少用,因为可以用 AOP 或策略模式更好解决。
✅ 总结:一张表帮你理清重点
| 特性 | 作用 | 使用建议 |
|---|---|---|
depends-on | 控制初始化/销毁顺序 | 用于静态初始化依赖(如驱动注册) |
lazy-init="true" | 延迟创建 Bean | 用于重量级、不常用的 Bean |
autowire="byType" | 自动按类型注入 | 开发阶段可用,生产建议显式配置 |
@Lookup / <lookup-method> | 单例中获取原型实例 | 替代方案:ObjectFactory 或 Provider |
autowire-candidate="false" | 排除自动装配候选 | 配合 primary="true" 解决歧义 |
Method Replacement | 替换方法实现 | 几乎不用,可用 AOP 替代 |
✅ 最佳实践建议
- 不要滥用 autowiring,尤其是大型项目,显式配置更清晰。
- 尽量避免
depends-on,良好的设计应减少这种隐式依赖。 - 延迟初始化谨慎使用,除非明确知道会影响启动性能。
- 获取 prototype bean 时,优先使用
ObjectFactory<T>或Provider<T>,而不是@Lookup。 - 多利用
@Primary和@Qualifier来解决自动装配冲突。
如果你正在学习 Spring IOC,这几个知识点属于“进阶内容”,掌握后你会明白:
“原来 Spring 是如何巧妙解决单例与原型协作的问题的。”
如果你想结合 Java 注解方式进一步理解,我可以补充基于 @Configuration、@Bean、@Autowired 的等价写法。
是否需要?欢迎继续提问!
