Spring Bean 的重写与覆盖

在基于 Spring 框架开发的时候,有的时候我们依赖的库会内置一些被 Spring 管理的 Bean。

场景1

接口 Test 有一个实现类 TestImpl ,在 TestImpl上 使用了 @Service 或者 @Component 注解,我们在其他地方使用的时候是通过 @Autowired 来注入 Test 接口的方式来使用的。
这个 Test 和实现是被封装后的一个 jar 包库,现在新的代码工程在依赖这个 jar 包之后,假设 Spring 会自动将 Test 及其实现类进行初始化并载入 Spring 容器的管理。
现在我们发现这个 TestImpl 里面有一个方法不能满足我们的需求,我们需要对其中一个方法进行重写,此时我们的第一想法就是写一个 CustomTestImpl extends TestImpl 然后重写需要重写的那个方法。
如果你对这个 CustomTestImpl 添加 @Component 注解,则启动服务的时候必然会报错,出现 Test 接口的注入失败,因为 Spring 容器发现 Test 接口有2个实现类,它不知道要注入哪一个,而我们的实际想法是使用新实现的 CustomTestImpl 类。
基于这个场景,可以在 CustomTestImpl 类上添加一个注解 @Primary,这个注解就是告诉 Spring 容器,在一个接口有多个实现类时,自动注入接口的时候主要默认选择的的是哪一个实例对象。
如果你的需求场景和这个场景契合,那么你可以通过这个注解来处理。

场景2

某Java类 TestBean1 implements TestTestBean2 implements Test,并且在 Configuration 类中进行了如下配置:

@Bean
public Test getTestBean1(){
    return new TestBean1();
}
@Bean
public Test getTestBean2(){
    return new TestBean2();
}

注意这里并没有添加 @ConditionalOnMissingBean 注解,在这种情况下。其他地方使用 @Autowired private List<Test> testList; 来注入使用,然后循环 testList 集合调用 Test 接口中的某个方法。

这种场景是不是很熟悉,在Spring源码中有很多处理都是采用这种接口集合的模式来实现的。此时如果我们想对 TestBean1 中的方法进行重写,应该怎么办呢?
如果按照上面一个场景使用 @Primary 是不能达到我们的目标的,因为注入的 List<Test> 会出现3个 Test 实例Bean对象。
解决这个问题,我们需要使用新实现的类完全替换掉容器中的类,绝对的偷梁换柱,比如 CustomTestBean1 extends TestBean1 进行方法重写,然后这个类不添加 @Component 注解,再使用 BeanDefinitionRegistryPostProcessor 接口来实现,具体代码示例如下:

// 这里不需要任何注解
public CustomTestBean1 extends TestBean1 {

}

@Configuration
public class TestBean1Configuration implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        String beanName = StringUtils.uncapitalize(TestBean1.class.getSimpleName());

        // 先移除原来的bean定义
        beanDefinitionRegistry.removeBeanDefinition(beanName);

        // 注册我们自己的bean定义
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(CustomTestBean1.class);
        // 如果有构造函数参数, 有几个构造函数的参数就设置几个  没有就不用设置
//        beanDefinitionBuilder.addConstructorArgValue("构造参数1");
//        beanDefinitionBuilder.addConstructorArgValue("构造参数2");
//        beanDefinitionBuilder.addConstructorArgValue("构造参数3");
        // 设置 init方法 没有就不用设置
//        beanDefinitionBuilder.setInitMethodName("init");
        // 设置 destory方法 没有就不用设置
//        beanDefinitionBuilder.setDestroyMethodName("destory");
        // 将Bean 的定义注册到Spring环境
        beanDefinitionRegistry.registerBeanDefinition(StringUtils.uncapitalize(CustomTestBean1.class.getSimpleName()), beanDefinitionBuilder.getBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
    
}

场景2 适用于几乎任何 Spring Bean,包括 Controller 你也可以这样做(比如继承已有的 Controller 重写方法添加方法都可以)

如下 Controller 被继承重写的代码示例:

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/hello")
    public boolean hello() {
        return true;
    }

    @GetMapping("/world")
    public String world() {
        return "world";
    }

}

CustomTestController 继承 TestController 后重写world方法,然后再添加一个print方法

public class CustomTestController extends TestController {

    @Override
    public String world() {
        return "world-extend";
    }

    @GetMapping("/print")
    public String print() {
        return "print-extend";
    }
}

测试访问 http://localhost:8080/test/hello 输出父类的 true
测试访问 http://localhost:8080/test/world 输出子类重写后的 world-extend
测试访问 http://localhost:8080/test/print 输出子类新添加的 print-extend

Controller 被继承和重写后,类上的相关注解和方法上的相关注解都不需要拷贝,只需要重点关心 Override 的方法体即可,这样可以保证如果基础包升级后的 mapping path 发生变更不受影响。如果你非要给示例中子类的 world() 方法添加一个不同的 @GetMapping("/world2") 那么Path /world2 将会覆盖并取代 /world,你再访问 /world 接口就是404,访问 /world2 将返回子类重写的方法的返回结果


(END)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

catoop

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值