Spring基础——方法注入(Method Injection)

目录

文章所用项目源码参考:java_spring_learn_repo

查找方法注入(Lookup Method)

  • 通常我们项目开发中大多都使用单例模式的Bean,因此Bean之间的依赖大多都是单例与单例之间的,而如果我们想将prototype模式的Bean被注入到单例模式的Bean中,因为单例Bean只会在创建时注入Bean的引用,所以prototype模式的Bean会一直是最开始创建的实例(换句话说就是prototype间接失效了,已经是单例模式了)。如果想要每次调用prototype模式Bean都获取一个新的实例,可以通过方法注入来实现。
  • 在Spring官方给的文档中提供了一种能在每次调用都获取新实例对象的方案,可以通过实现ApplicationContextAware接口,给Bean注入容器,然后再让Bean需要调用新实例时让容器创建一个新的实例。

ApplicationContextAware是Spring框架中的一个接口,用于让Bean实现类获取对Spring应用上下文(ApplicationContext)的引用。通过实现ApplicationContextAware接口,Bean可以在被实例化后,由Spring容器注入应用上下文的引用,从而获取容器中的其他Bean或执行一些与容器相关的操作。

  • 首先创建一个prototype的Bean
java 复制代码
public class Command {
    private String state;

    public void setState(String state) {
        this.state = state;
    }

    public String execute() {
        return this + "对象被执行";
    }
}
  • XML配置Scope为prototype
xml 复制代码
<bean id="command" class="com.nobugnolife.method_injection.Command" scope="prototype"/>
  • 这里通过实现ApplicationContextAware来获取ApplicationContext,然后在对象每次调用process方法时都会让容器获取一个新的Command实例
java 复制代码
public class MyApplicationContextAwareBean implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    // 通过applicationContext创建新的Commmad实例化Bean
    protected Command createCommand() {
        return this.applicationContext.getBean("command", Command.class);
    }

    // 每次调用process时都从容器中获取新的bean实例
    public String process(String state) {
        Command command = createCommand();
        command.setState(state);
        return command.execute();
    }
}
  • 测试process调用是否为新实例
java 复制代码
@Test
public void testMethodInjection(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("methodInjection.xml");
    MyApplicationContextAwareBean mcb = ctx.getBean(MyApplicationContextAwareBean.class);
    System.out.println(mcb.process("发送信息"));
    System.out.println(mcb.process("激活"));
    System.out.println(mcb.process("添加"));
}
  • 这种方法会将Spring的代码框架耦合进项目工程中,因此官方并不推荐通过这种方法解决非单例模式Bean的调用问题,Spring也给出了通过方法注入的解决方案(有其他很多解决方案,本篇文章重点是讲方法注入)。

查找方法注入

  • 查找方法是通过在单例Bean中声明一个抽象方法,该抽象方法会在Spring容器运行时动态生成(通过CGLIB库直接生成字节码)一个子类来实现这个抽象方法,以提供prototypeBean实例。
  • 因为Spring在运行的时候是自动创建子类实现,因此要被子类实现的抽象类不能有final
  • 如果是通过工厂模式实例化Bean的话,查找方法注入是不起作用的,特别是通过@Bean注解的方式,因为此时容器不负责创建实例,所以不能动态创建运行时子类。

基于XML的方法注入

  • 首先需要定义一个查找方法的抽象类,并提供实例化Bean的抽象方法
java 复制代码
public abstract class CommandManager {
    public String process(String commandState) {
        // 通过调用createCommand来获取新的实例Bean
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    protected abstract Command createCommand();
}
  • 在bean配置中设置lookup-method标签
xml 复制代码
<!-- 查找方法注入 -->
<bean id="commandManager" class="com.nobugnolife.method_injection.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

基于注解的方法注入

  • 注解通过Lookup注解对抽象方法进行标记即可
java 复制代码
@Component
public abstract class CommandManager {
    public String process(String commandState) {
        // 通过调用createCommand来获取新的实例Bean
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract Command createCommand();
}

Arbitrary Method Replacement(任意方法替换)

  • 任意方法是指Spring框架中一种高级的AOP(面向切面编程)功能。通过这种方式,你可以在运行时替换类中的方法,并提供自定义的实现逻辑。
  • 要想实现任意方法替换,首先要实现org.springframework.beans.factory.support.MethodReplacer提供的方法
java 复制代码
public class ReplacementCompute implements MethodReplacer {
    public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
        // 获取原方法传入参数
        String input = (String) objects[0];
        // 执行新的指令方法
        return "Compute方法重构:" + input;
    }
}
  • 之后部署初始类,然后指定对应实现方法覆盖的Bean
xml 复制代码
<!-- 定义原方法Bean-->
<bean id="compute" class="com.nobugnolife.method_injection.Compute">
    <!-- 使用replaced-method关联需要覆盖的方法 name为方法名 replacer为指定覆盖方法的bean -->
    <replaced-method name="ComputeValue" replacer="replacementCompute">
        <!-- 重载方法参数的签名,只有当方法重载时有多个变体时才是必要的 -->
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementCompute" class="com.nobugnolife.method_injection.ReplacementCompute"/>
  • 任意方法替换的功能很灵活而且十分强大,但因为过于接近底层,而且会重构代码逻辑,容易造成非常危险的后果,因此尽量不要在开发中运用。
相关推荐
kesifan4 分钟前
JAVA的线程的周期及调度
java·开发语言
李少兄5 分钟前
解决 Spring Boot 中 YAML 配置文件的 `ArrayIndexOutOfBoundsException: -1` 异常
java·spring boot·后端
uup15 分钟前
Java 多线程环境下的资源竞争与死锁问题
java
LiuYaoheng19 分钟前
【Android】RecyclerView 刷新方式全解析:从 notifyDataSetChanged 到 DiffUtil
android·java
Wpa.wk23 分钟前
selenium自动化测试-简单PO模式 (java版)
java·自动化测试·selenium·测试工具·po模式
大猫子的技术日记29 分钟前
[后端杂货铺]深入理解分布式事务与锁:从隔离级别到传播行为
分布式·后端·事务
洛_尘30 分钟前
JAVA第十一学:认识异常
java·开发语言
澪贰1 小时前
从数据中心到边缘:基于 openEuler 24.03 LTS SP2 的 K3s 轻量化云原生实战评测
后端
绝无仅有1 小时前
面试之高级实战:在大型项目中如何利用AOP、Redis及缓存设计
后端·面试·架构
沐浴露z1 小时前
如何应对服务雪崩?详解 服务降级与服务熔断
java·微服务