前言
依赖注入其实只有两种:构造器注入 和setter方法注入 ,@AutoWire自动注入其实是他们的扩展:no没有自动注入,需要手动指定注入,byName、byType是setter注入,constructor是构造器注入。想过没有为什么没有getter注入呢?其时是有的 Getter注入是指使用 getter 方法进行硬编码的"依赖注入"。之所以使用引号,是因为它是硬编码的,并非真正的依赖注入。"注入"依赖项的方式是通过子类化并重写 getter 方法,或者使用反射(通常使用模拟库)替换 getter 来更改 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";
}
}
确定依赖项是普通对象还是工厂,如果老的getter方法每次都生成一个新对象,并且它需要,那么依赖项应该是工厂。其实子类重写就是"工厂方法",它返回了不一样的对象,只是略显"笨拙"。通过子类剥离父类对String("Bonjour le monde")的依赖。站在IOC容器角度,依赖管理交给spring,那么这个子类化就交给spring CGLIB来做。方法注入就增加了对Ioc容器的依赖
方法注入
首先需要理清两者的核心差异。lookup-method主要用于解决无状态Bean依赖有状态Bean的问题,本质是方法级别的对象获取机制。而replaced-method则是方法实现的动态替换,属于行为修改范畴。
从应用场景来看,lookup-method适合那种"每次需要新对象"的情况。比如用户提到的数据库连接场景很典型------单例服务每次操作都需要新的连接对象。这让我想起Spring官方文档里的经典案例:抽象Command类通过lookup获取具体命令实例。
replaced-method则更适合热替换的场景。用户给出的加密算法切换的例子很贴切,特别是在需要运行时动态改变实现时。不过要提醒自己注意,文档里特别强调这种替换需要通过字节码操作实现,所以必须引入CGLIB库。
用户似乎更关注实际使用选择,所以在总结部分需要强化对比维度。突然想到用户可能遇到的经典困惑:为什么非要用lookup而不用直接注入?这就涉及到原型对象的获取限制问题------在单例Bean内部直接注入原型Bean并不会产生新实例。
最后检查技术细节是否准确:①lookup是继承+模板方法 ②replaced依赖MethodReplacer接口 ③两者都需要CGLIB代理。应该没遗漏关键点。用户的问题质量很高,说明ta正在处理具体的框架设计问题。
在 Spring 框架中,lookup-method
和 replaced-method
都是用来覆盖或增强容器管理的 bean 中特定方法行为的机制,但它们解决的问题完全不同,适用于不同的场景。理解它们的区别是选择正确工具的关键。
核心区别总结:
特性 | lookup-method | replaced-method |
---|---|---|
核心目的 | 获取新实例/原型依赖 | 替换方法实现逻辑 |
场景核心需求 | 单例 Bean 每次 调用方法需要新的目标实例 | 完全替换现有 Bean 方法的行为逻辑 |
实现方式 | Spring CGLIB子类化 + 模板方法 | Spring CGLIB 动态代理 + MethodReplacer |
目标方法签名 | 通常声明为abstract |
通常是具体实现的方法 |
典型返回值 | 目标 Bean 类型 (原型) | 任意类型 (可匹配原方法) |
目标 Bean | 必须 是一个原型作用域的 Bean | 可以是任意作用域的 Bean |
应用场景 | 单例依赖原型、"方法注入"、避免单例状态问题 | AOP 不足时的热插拔、复杂条件逻辑、模拟、方法增强 |
详细解析及场景选择:
1. lookup-method (查找方法注入)
-
目的: 允许单例作用域的 bean 在每次调用特定方法时都能获取到一个新的、不同实例(通常是原型作用域)。它解决了在单例 bean 中无法正确注入原型 bean 的问题(因为单例初始化时只注入原型 bean 一次,导致所有引用都指向同一个原型实例)。
-
工作原理:
- 你在 XML 配置中(或使用
@Lookup
注解)声明一个<lookup-method>
。 - 目标 bean 定义一个
abstract
方法(或在带@Lookup
注解的类中用具体方法调用),其返回类型是你想每次获取新实例的那个 bean(通常是原型作用域)。 - Spring 在运行时动态生成目标 bean 的 CGLIB 子类 。这个子类会覆盖你声明的那个
abstract
(或者带有@Lookup
的)方法。 - 每次调用该方法时,子类实现会向容器请求 (
getBean()
)该方法的返回类型对应 bean 的一个新实例 (prototype
)或特定实例 (request
,session
等非单例作用域)。
- 你在 XML 配置中(或使用
-
关键特征:
- 核心在于每次调用都获取新对象。
- 被声明为 lookup 的方法通常是
abstract
(XML 配置)或者用@Lookup
标记(注解配置)。 - 方法的返回类型决定了 Spring 容器要查找和返回哪个 Bean(该 Bean 必须是非单例作用域)。
- Spring 通过生成动态子类来实现(CGLIB)。
-
典型使用场景:
-
单例 Bean 需要依赖原型 Bean: 这是最经典的场景。例如:
- 一个处理请求的单例服务类(
RequestService
),每次处理时需要一个新的 DAO 实例(RequestDao
- prototype),以避免线程安全问题或在请求间维护状态。 - 一个单例的命令处理器(
CommandManager
),每次执行命令时需要创建一个新的、特定类型的命令对象(Command
- prototype)。
- 一个处理请求的单例服务类(
-
方法注入 (Method Injection): 当你需要将一个 Bean 的作用域限制在另一个 Bean 的方法调用范围内时。
-
避免在单例中持有原型 Bean 的引用: 确保单例 Bean 不会意外地缓存或重用原型 Bean。
-
xml配置
java
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
xml
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandManager uses myCommand prototype bean -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
@Lookup注解
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 中一个现有具体方法 的实现逻辑。它允许你在不修改原始 bean 源代码 的情况下,用自定义的逻辑替换掉该方法的执行体。
-
工作原理:
- 你定义一个实现了
org.springframework.beans.factory.support.MethodReplacer
接口的类。这个接口有一个reimplement
方法,它接收被调用方法的原始目标对象、方法对象、参数数组,并需要返回一个结果(类型需匹配或兼容原方法)。 - 在 XML 配置中(没有直接的注解替代品 ),使用
<replaced-method>
元素将目标 bean 中的某个具体方法名与你的MethodReplacer
实现关联起来。 - Spring 在运行时创建目标 bean 的 CGLIB 代理。
- 当调用被替换的方法时,代理会拦截 该调用,并将执行转发给你提供的
MethodReplacer.reimplement()
方法,由它来执行你自定义的逻辑。
- 你定义一个实现了
-
关键特征:
- 核心在于替换方法体实现。
- 被替换的方法通常是具体实现的(非 abstract)。
- 完全重写了方法的业务逻辑。
- 使用
MethodReplacer
接口提供自定义实现。 - Spring 通过生成动态代理(CGLIB)来实现。
-
典型使用场景:
- 行为热插拔 / 定制化: 在不改源码的情况下,替换掉系统核心组件(如 DAO、Service)的某些方法行为,例如在测试环境替换生产环境的数据获取逻辑,或者在运行时根据配置启用/禁用某些功能分支。
- 实现简单的基于配置的 AOP: 当 AOP(面向切面编程)显得过于重量级,或者你只需要替换一个或几个特定方法,且不需要声明式切点(Pointcut)或通知(Advice)的灵活性时。例如,记录方法调用参数、临时添加性能监控、简单的缓存逻辑(需谨慎)。
- 修复或增强第三方库的方法: 如果你不能修改第三方 jar 包中某个类的源代码,但需要改变其某个特定方法的行为。
- 复杂的条件逻辑: 当需要根据复杂的外部状态(配置、运行时环境)动态选择方法实现时(虽然策略模式可能更清晰,但
replaced-method
可以做到)。 - 测试/模拟: 在集成测试中,用
replaced-method
快速模拟依赖方法的复杂实现或外部系统调用。
java
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
xml
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
如何选择合适的机制?
-
选择
lookup-method
当你:- 有一个单例 Bean。
- 这个单例 Bean 需要调用一个方法。
- 每次调用这个方法都需要获得一个全新的对象实例 (这个实例通常是另一个原型作用域的 Bean)。
- 核心问题是获取新对象。
-
选择
replaced-method
当你:- 需要改变(替换) 一个 bean 中某个具体方法的执行代码。
- 不能或不想修改原始 bean 的源代码。
- 需要一种基于配置的、声明式的方式来替换方法逻辑。
- 标准的 AOP 显得过于复杂或不适用。
- 核心问题是改变方法的行为。
简单决策树:
- 你的 Bean 是单例,但每次调用某个方法都需要一个新的"帮手"对象吗?(通常是原型) -> 选
lookup-method
。 - 你需要修改一个现有方法内部的实现逻辑,而不改变调用它的源代码吗? -> 选
replaced-method
。
重要提示:
-
现代 Spring (尤其是纯注解配置) 更倾向于
@Lookup
: 对于 lookup 场景,@Lookup
注解(在方法上)通常是比 XML<lookup-method>
更现代、更便捷的选择。 -
replaced-method
没有直接注解替代: Spring 的核心注解配置中没有 直接替代 XML<replaced-method>
的标准注解。替代方案通常是:- 策略模式 (依赖注入): 将可能变化的行为抽象为接口,将不同的实现注入进来,在方法内部调用接口方法。
- AOP: 使用
@Around
环绕通知完全接管目标方法的执行(可以看作最灵活强大的replaced-method
)。
-
两者都基于代理: 无论是
lookup-method
还是replaced-method
,Spring 底层都需要使用 CGLIB 库(或其他代理技术)来动态生成代理子类或代理对象。这可能会带来一些限制(如类需非 final)和微小的性能开销。
总结:
虽然两者都通过 Spring 容器改变方法行为且涉及动态代理,但 lookup-method
的核心是解决对象获取问题(单例获取原型新实例) ,而 replaced-method
的核心是解决代码实现替换问题 。理解你面临的问题本质是选择合适机制的关键。在现代 Spring 开发中,@Lookup
比 <lookup-method>
更常用,而对于方法实现的动态替换,则更倾向于使用策略模式或完整的 AOP。另一个关键限制是查找方法不适用于工厂方法,特别是不适用于@Bean
配置类中的方法,因为在这种情况下,容器不负责创建实例,因此无法动态创建运行时生成的子类