💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[toc] ## 典型的方法注入 大部分的应用场景,bean都是单例的.当一个单例的bean需要和另一个单例的bean组合在一起,或者一个非单例的bean和另一个非单例的bean组合在一起,典型的处理方式就是把bean设置成另一个bean的属性. 但是,当两个bean的生命周期不一致时就有问题了,假设单例bean A需要使用非单例的bean B,容器只创建A一次,也就是只有一次机会设置A的属性,所以不可能每次使用A的时候提供新的实例B. 一种解决方案就是放弃部分控制反转,可以使bean A实现接口`ApplicationContextAware`来关联容器,然后在每次需要B的时候,通过容器调用`getBean("B")`获取新的实例,如下: ~~~java // a class that uses a stateful Command-style class to perform some processing package fiona.apple; // Spring-API imports import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class CommandManager implements ApplicationContextAware { private ApplicationContext applicationContext; public Object process(Map commandState) { // grab a new instance of the appropriate Command Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } protected Command createCommand() { // notice the Spring API dependency! return this.applicationContext.getBean("command", Command.class); } public void setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } ~~~ 前面的这种方式不可取的,因为业务代码和spring框架耦合在一起,方法注入是spring的高级特性,下面的查找方法的注入方式更干净一点 > 了解方法注入的更多机制参考[blog](https://spring.io/blog/2004/08/06/method-injection/) ## 查找方法的注入 查找方法的注入会覆盖容器管理bean的方法,Spring Framework通过使用CGLIB库中的字节码动态生成覆盖该方法的子类,从而实现了方法注入。 > * 为了动态子类可以正常工作,spring容器中的要被继承的类不能是final的,同时要重写的方法也不能是final的 > * 单元测试类有个`abstract`方法,需要你自行实现 > * 组件扫描也需要具体的类来提取 > * 查找方法的注入不能用于工厂方法,也不能用于配置类的@Bean注解的方法,因为容器不负责创建对象,不能创建运行时子类 > 对上面的示例改造 ~~~ 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(); } ~~~ 需要方法注入的`createCommand`,必须满足下面的方法签名 ~~~ <public|protected> [abstract] <return-type> theMethodName(no-arguments); ~~~ 如果是方法是`abstract`的,动态子类会实现它,否则动态子类会重写原始类的方法,例如: ~~~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> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"> <lookup-method name="createCommand" bean="myCommand"/> </bean> ~~~ `commandManager `需要新的myCommand实例时就调用自己的`createCommand`方法.如果实际需要,请仔细确认发布的bean `myCommand `为原型的,如果是发布为单例的,每次返回的都是同一个实例. 使用注解`@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(); } ~~~ 或者,更多的在返回类型上声明目标,而不是在注解上 ~~~ public abstract class CommandManager { public Object process(Object commandState) { MyCommand command = createCommand(); command.setState(commandState); return command.execute(); } @Lookup protected abstract MyCommand createCommand(); } ~~~ > 另一种注入不同范围的bean方式是`ObjectFactory/ Provider`,参考[Scoped beans as dependencies.](https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-other-injection) > 感兴趣的同学会发现使用的类`ServiceLocatorFactoryBean`,在包`org.springframework.beans.factory.config` > ## 任意方法替换 比查找方法注入还少用的就是任意方法替换 基于xml的配置,使用`replaced-method`,考虑下面这种情况,想要重写方法`computeValue` ~~~ public class MyValueCalculator { public String computeValue(String input) { // some real code... } // some other methods... } ~~~ 一个类实现接口`org.springframework.beans.factory.support.MethodReplacer`,提供一个新的方法 ~~~ /** * 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 ...; } } ~~~ 原始bean的定义和方法重写如下: ~~~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"/> ~~~ 可以使用`<arg-type/>`表明重写方法的参数,当方法有重载的时候,这个是必需的,为了方便使用,类型可以简写,如`java.lang.String` ~~~ java.lang.String String Str ~~~