方法注入

前言

依赖注入其实只有两种:构造器注入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-methodreplaced-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 一次,导致所有引用都指向同一个原型实例)。

  • ​工作原理:​

    1. 你在 XML 配置中(或使用 @Lookup注解)声明一个 <lookup-method>
    2. 目标 bean 定义一个abstract方法(或在带@Lookup注解的类中用具体方法调用),其返回类型是你想​每次获取新实例的那个 bean​(通常是原型作用域)。
    3. ​Spring 在运行时动态生成目标 bean 的 CGLIB 子类​ 。这个子类会覆盖你声明的那个abstract(或者带有@Lookup的)方法。
    4. 每次调用该方法时,子类实现会​向容器请求​getBean())该方法的返回类型对应 bean 的一个​新实例​prototype)或​特定实例​request, session等非单例作用域)。
  • ​关键特征:​

    • 核心在于​每次调用都获取新对象​
    • 被声明为 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 源代码​ ​的情况下,用​​自定义的逻辑​​替换掉该方法的执行体。

  • ​工作原理:​

    1. 你定义一个实现了 org.springframework.beans.factory.support.MethodReplacer接口的类。这个接口有一个 reimplement方法,它接收被调用方法的原始目标对象、方法对象、参数数组,并需要返回一个结果(类型需匹配或兼容原方法)。
    2. 在 XML 配置中(​没有直接的注解替代品​ ),使用 <replaced-method>元素将目标 bean 中的某个具体方法名与你的 MethodReplacer实现关联起来。
    3. ​Spring 在运行时创建目标 bean 的 CGLIB 代理。​
    4. 当调用被替换的方法时,代理会​拦截​ 该调用,并​将执行转发给你提供的 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 显得过于复杂或不适用。
    • ​核心问题是改变方法的行为。​

​简单决策树:​

  1. ​你的 Bean 是单例,但每次调用某个方法都需要一个新的"帮手"对象吗?(通常是原型)​ -> ​选 lookup-method
  2. ​你需要修改一个现有方法内部的实现逻辑,而不改变调用它的源代码吗?​ -> ​选 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配置类中的方法,因为在这种情况下,容器不负责创建实例,因此无法动态创建运行时生成的子类

相关推荐
SugarFreeOixi8 分钟前
Idea打包可执行jar,MANIFEST.MF文件没有Main-Class属性:找不到或无法加载主类
java·jar
Mr Aokey10 分钟前
从BaseMapper到LambdaWrapper:MyBatis-Plus的封神之路
java·eclipse·mybatis
小白学大数据13 分钟前
Java爬虫性能优化:多线程抓取JSP动态数据实践
java·大数据·爬虫·性能优化
yuqifang27 分钟前
写一个简单的Java示例
java·后端
用户20187928316735 分钟前
限定参数范围的注解之 "咖啡店定价" 的故事
android·java
泽虞43 分钟前
C语言深度语法掌握笔记:底层机制,高级概念
java·c语言·笔记
白露与泡影1 小时前
彻底解决SpringCloud TCP连接过多未释放问题~
tcp/ip·spring·spring cloud
视觉CG1 小时前
【JS】扁平树数据转为树结构
android·java·javascript
哈基米喜欢哈哈哈1 小时前
Netty入门(二)——网络传输
java·开发语言·网络·后端
老虎06271 小时前
Java基础面试题(1)—Java优势(JVM,JRE,JIT,Java类,方法)
java·开发语言·jvm