引言:依赖注入的本质思考
依赖注入的核心机制本质上只有两种基本形式:构造器注入和Setter方法注入。Spring框架中的@Autowired
自动注入实际上是这两种方式的智能扩展:
-
no:无自动注入,需要手动指定
-
byName 、byType:基于Setter注入的变体
-
constructor:构造器注入的直接应用
那么,为什么通常没有提到Getter注入呢?实际上,Getter注入在特定场景下是存在的,只不过它以不同的形式呈现。
Getter注入:被忽视的依赖管理方式
Getter注入是指通过Getter方法进行硬编码的"依赖注入"。这里使用引号是因为它本质上是硬编码的,并非传统意义上的依赖注入。其核心思想是通过子类化并重写Getter方法,或者利用反射机制(常见于Mock测试库)来动态替换Getter的实现逻辑。
示例代码分析:
java
public class ClassUnderTest{
public void saySomething(){
System.out.println(getString());
}
protected String getString(){
return "Hello World";
}
}
public class GetFrench extends ClassUnderTest{
@Override
protected String getString(){
return "Bonjour le monde";
}
}
关键洞察:
-
依赖项性质判断:需要明确依赖项是普通对象还是工厂对象
-
工厂模式的自然体现:子类重写本质上就是"工厂方法"模式,返回不同的对象实例
-
IoC容器的角色:在Spring框架中,这种子类化工作由Spring CGLIB代理完成
Spring方法注入机制详解
核心概念辨析
lookup-method 与replaced-method虽然都涉及方法级别的干预,但解决的问题域完全不同:
-
lookup-method:专注于解决无状态Bean依赖有状态Bean的问题,是方法级别的对象获取机制
-
replaced-method:实现方法实现的动态替换,属于行为修改范畴
1. Lookup Method(查找方法注入)
设计目的:
允许单例作用域的Bean在每次调用特定方法时都能获取到新的原型作用域Bean实例,解决单例Bean中无法正确注入原型Bean的经典问题。
实现原理:
-
通过XML配置或
@Lookup
注解声明查找方法 -
Spring运行时动态生成CGLIB子类
-
子类重写目标方法,每次调用时向容器请求新的Bean实例
配置示例:
xml
<!-- 原型作用域的Bean -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- 依赖注入配置 -->
</bean>
<!-- 单例Bean使用lookup-method -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
注解方式:
java
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
2. Replaced Method(方法实现替换)
设计目的:
完全替换现有Bean中具体方法的实现逻辑,在不修改源代码的情况下实现方法行为的动态变更。
实现机制:
-
实现
MethodReplacer
接口定义新的方法逻辑 -
通过XML配置关联目标方法和替换器
-
Spring创建CGLIB代理拦截方法调用
完整示例:
java
// 原始Bean类
public class MyValueCalculator {
public String computeValue(String input) {
// 原始业务逻辑
return "Original: " + input;
}
}
// 方法替换器实现
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object target, Method method, Object[] args) throws Throwable {
String input = (String) args[0];
// 新的业务逻辑
return "Replaced: " + input.toUpperCase();
}
}
xml
<!-- XML配置 -->
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
应用场景对比分析
Lookup Method适用场景
-
单例依赖原型:单例服务需要每次使用新的DAO实例
-
命令模式实现:命令处理器需要创建新的命令对象
-
状态隔离:避免在单例中意外缓存或重用原型Bean
Replaced Method适用场景
-
行为热插拔:运行时动态切换算法或策略
-
第三方库增强:修复或增强无法修改源码的第三方类
-
测试模拟:集成测试中快速模拟复杂依赖
-
轻量级AOP:当标准AOP显得过于重量级时的替代方案
技术实现深度解析
核心实现流程
-
Bean定义合并阶段
-
检查
@Lookup
注解并构建MethodOverrides
-
处理XML中配置的
replaced-method
声明
-
-
Bean实例化阶段
java
// 简化版的实例化流程 createBeanInstance → checkLookupMethods() // 检查Lookup注解 → determineConstructors() // 确定构造器 → instantiateWithMethodInjection() // 使用方法注入实例化 → CglibSubclassCreator.instantiate() // CGLIB子类化
-
CGLIB代理生成
-
创建目标Bean的增强子类
-
设置方法拦截器回调
-
实现方法级别的行为重写
-
拦截器机制
Lookup方法拦截器:
java
private static class LookupOverrideMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
LookupOverride lo = getLookupOverride(method);
// 关键:每次调用都从容器获取新实例
return this.owner.getBean(lo.getBeanName());
}
}
Replace方法拦截器:
java
private static class ReplaceOverrideMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
ReplaceOverride ro = getReplaceOverride(method);
MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
// 关键:使用替换器的实现逻辑
return mr.reimplement(obj, method, args);
}
}
现代Spring开发的最佳实践
选择建议
优先使用Lookup Method当:
-
单例Bean需要每次获取新的原型实例
-
核心需求是对象获取而非行为修改
考虑Replaced Method当:
-
需要完全替换现有方法实现
-
无法修改原始源代码
-
AOP方案过于复杂
现代替代方案
-
@Lookup注解:比XML配置更现代简洁
-
策略模式:通过依赖注入不同的实现来替换行为
-
Spring AOP :使用
@Around
通知实现更灵活的方法拦截
重要限制说明
-
工厂方法不适用 :两种机制都不适用于
@Bean
配置的工厂方法 -
CGLIB要求:目标类不能是final,必须有默认构造器
-
性能考量:动态代理会带来轻微的性能开销
总结
Spring的方法注入机制提供了强大的扩展能力,lookup-method
解决了单例获取原型实例的核心难题,而replaced-method
实现了方法行为的动态替换。理解两者的本质区别和适用场景,能够帮助开发者在合适的场景选择合适的技术方案。
在现代Spring开发中,虽然这些机制的使用频率有所下降,但它们背后的设计思想仍然具有重要的学习价值,特别是在理解Spring IoC容器的工作原理和扩展机制方面。