方法注入

前言

依赖注入其实只有两种:构造器注入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配置类中的方法,因为在这种情况下,容器不负责创建实例,因此无法动态创建运行时生成的子类

相关推荐
努力努力再努力wz6 分钟前
【Linux网络系列】深入理解 I/O 多路复用:从 select 痛点到 poll 高并发服务器落地,基于 Poll、智能指针与非阻塞 I/O与线程池手写一个高性能 HTTP 服务器!(附源码)
java·linux·运维·服务器·c语言·c++·python
努力努力再努力wz9 分钟前
【Linux网络系列】万字硬核解析网络层核心:IP协议到IP 分片重组、NAT技术及 RIP/OSPF 动态路由全景
java·linux·运维·服务器·数据结构·c++·python
LaLaLa_OvO14 分钟前
mybatis 引用静态常量
java·mybatis
yaodong51815 分钟前
Spring 中使用Mybatis,超详细
spring·tomcat·mybatis
Han_han91917 分钟前
常用API:
java·开发语言
splage24 分钟前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
小锋java123430 分钟前
LangChain4j 来了,Java AI智能体开发再次起飞。。。
java·人工智能·后端
敖正炀35 分钟前
BlockingQueue 详解
java
likerhood1 小时前
java中的return this、链式编程和Builder模式
java·开发语言
spring2997921 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端