Spring依赖管理核心技巧与应用 - 实践

以下内容是 Spring Framework 官方文档中关于依赖管理(Dependency Management)的几个核心章节,主要包括:

  • 1.4.3 Using depends-on
  • 1.4.4 Lazy-initialized Beans
  • 1.4.5 Autowiring Collaborators
  • 1.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-on B,则:
    • 初始化时: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 IDsetUserDao(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 注入进去。

⚠️ 局限性 & 缺点:

  1. 不能自动装配基本类型(int, String, boolean 等)
  2. 模糊性问题:如果有两个相同类型的 Bean,byType 就会抛异常
    No unique bean of type X found. Expected single matching bean but found 2: bean1, bean2
  3. 可读性差:别人看不懂你的 Bean 到底依赖了谁
  4. 工具难以分析:文档生成工具无法提取依赖信息

✅ 如何解决歧义?

方法说明
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 {
@Autowired
private 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 {
@Lookup
protected 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 {
@Autowired
private 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>单例中获取原型实例替代方案:ObjectFactoryProvider
autowire-candidate="false"排除自动装配候选配合 primary="true" 解决歧义
Method Replacement替换方法实现几乎不用,可用 AOP 替代

✅ 最佳实践建议

  1. 不要滥用 autowiring,尤其是大型项目,显式配置更清晰。
  2. 尽量避免 depends-on,良好的设计应减少这种隐式依赖。
  3. 延迟初始化谨慎使用,除非明确知道会影响启动性能。
  4. 获取 prototype bean 时,优先使用 ObjectFactory<T>Provider<T>,而不是 @Lookup
  5. 多利用 @Primary@Qualifier 来解决自动装配冲突。

如果你正在学习 Spring IOC,这几个知识点属于“进阶内容”,掌握后你会明白:

“原来 Spring 是如何巧妙解决单例与原型协作的问题的。”

如果你想结合 Java 注解方式进一步理解,我可以补充基于 @Configuration@Bean@Autowired 的等价写法。

是否需要?欢迎继续提问!

posted @ 2025-11-20 15:39  gccbuaa  阅读(10)  评论(0)    收藏  举报