Spring覆盖容器中Bean的注解如何实现@OverrideBean

 更新时间:2025年01月06日 10:38:45   作者:catoop  
文章介绍了在项目开发中如何通过偷梁换柱的方式重写Spring容器中的内置Bean,并指出了需要注意的两点:1. 对应的Bean应基于接口注入;2. 如果不是基于接口注入,可以使用同包名同类名的方式重写(可能存在潜在问题,不推荐),文章还强调了“基于接口编程”的好处

Spring覆盖容器Bean的注解实现@OverrideBean

项目开发中,有时第三方框架会自动注入Bean到Spring容器中,当我们有修改对应内置Bean实现的需求时,可以采用偷梁换柱的方式来重写内置的Bean,使用这种方式需要注意以下两点:

  • 1、对应的Bean在其他地方使用时,是基于接口注入的。
  • 2、如果不是基于接口注入的Bean,你可能需要同包名同类名的这种方式重写(可能会有问题,不推荐)。

从以上2点我们还可以得出一个结论,那就是“基于接口编程”的好处。

具体实现参考一下代码

(代码片段,仅供参考,根据实际使用场景修改后使用):

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 覆盖Spring容器中的Bean
 *
 * @author shanhy
 * @date 2021/4/25 13:40
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface OverrideBean {

    /**
     * 需要替换的 Bean 的名称
     *
     * @return
     */
    String value();
    
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * 重写Bean的配置类
 *
 * @author shanhy
 * @date 2021/4/25 13:41
 */
@Configuration
public class OverrideBeanConfiguration implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {

    private static final Logger log = LoggerFactory.getLogger(OverrideBeanConfiguration.class);

    private BeanFactory beanFactory;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.debug("searching for classes annotated with @OverrideBean");

        // 自定义 Scanner 扫描 classpath 下的指定注解
        ClassPathOverrideBeanAnnotationScanner scanner = new ClassPathOverrideBeanAnnotationScanner(registry);
        try {
            // 获取包路径
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);

            if (log.isDebugEnabled()) {
                for (String p : packages) {
                    log.debug("Using auto-configuration base package: {}", p);
                }
            }

            // 扫描所有加载的包
            scanner.doScan(StringUtils.toStringArray(packages));
        } catch (IllegalStateException ex) {
            log.debug("could not determine auto-configuration package, automatic OverrideBean scanning disabled.", ex);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    private static class ClassPathOverrideBeanAnnotationScanner extends ClassPathBeanDefinitionScanner {

        ClassPathOverrideBeanAnnotationScanner(BeanDefinitionRegistry registry) {
            super(registry, false);
            // 设置过滤器。仅扫描 @OverrideBean
            addIncludeFilter(new AnnotationTypeFilter(OverrideBean.class));
        }

        @Override
        public Set<BeanDefinitionHolder> doScan(String... basePackages) {
            List<String> overrideClassNames = new ArrayList<>();
            // 扫描全部 package 下 annotationClass 指定的 Bean
            Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

            GenericBeanDefinition definition;
            for (BeanDefinitionHolder holder : beanDefinitions) {
                definition = (GenericBeanDefinition) holder.getBeanDefinition();

                // 获取类名,并创建 Class 对象
                String className = definition.getBeanClassName();
                Class<?> clazz = classNameToClass(className);

                // 解析注解上的 value
                OverrideBean annotation = Objects.requireNonNull(clazz).getAnnotation(OverrideBean.class);
                if (annotation == null || annotation.value().length() == 0) {
                    continue;
                }

                // 使用当前加载的 @OverrideBean 指定的 Bean 替换 value 里指定名称的 Bean
                if (Objects.requireNonNull(getRegistry()).containsBeanDefinition(annotation.value())) {
                    getRegistry().removeBeanDefinition(annotation.value());
                    getRegistry().registerBeanDefinition(annotation.value(), definition);
                    overrideClassNames.add(clazz.getName());
                }
            }
            log.info("found override beans: " + overrideClassNames);

            return beanDefinitions;
        }

        // 反射通过 class 名称获取 Class 对象
        private Class<?> classNameToClass(String className) {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                log.error("create instance failed.", e);
            }
            return null;
        }
    }

}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • mybatis中foreach报错:_frch_item_0 not found的解决方法

    mybatis中foreach报错:_frch_item_0 not found的解决方法

    这篇文章主要给大家介绍了mybatis中foreach报错:_frch_item_0 not found的解决方法,文章通过示例代码介绍了详细的解决方法,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-06-06
  • Java必会的Synchronized底层原理剖析

    Java必会的Synchronized底层原理剖析

    synchronized作为Java程序员最常用同步工具,很多人却对它的用法和实现原理一知半解,以至于还有不少人认为synchronized是重量级锁,性能较差,尽量少用。但不可否认的是synchronized依然是并发首选工具,本文就来详细讲讲
    2022-10-10
  • SpringBoot集成tensorflow实现图片检测功能

    SpringBoot集成tensorflow实现图片检测功能

    TensorFlow名字的由来就是张量(Tensor)在计算图(Computational Graph)里的流动(Flow),它的基础就是前面介绍的基于计算图的自动微分,本文将给大家介绍Spring Boot集成tensorflow实现图片检测功能,需要的朋友可以参考下
    2024-06-06
  • Java axios与spring前后端分离传参规范总结

    Java axios与spring前后端分离传参规范总结

    这篇文章主要介绍了Java axios与spring前后端分离传参规范总结,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-08-08
  • SpringBoot的jar包如何启动的实现

    SpringBoot的jar包如何启动的实现

    本文主要介绍了SpringBoot的jar包如何启动的实现,文中根据实例编码详细介绍的十分详尽,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • SpringBoot获取配置信息的三种方式总结

    SpringBoot获取配置信息的三种方式总结

    这篇文章给大家介绍了SpringBoot获取配置信息的三种方式,@Value属性值注入,绑定配置类和通过 environment获取这三种方式,文中通过代码示例给大家介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2024-01-01
  • ThreadPoolExecutor参数的用法及说明

    ThreadPoolExecutor参数的用法及说明

    这篇文章主要介绍了ThreadPoolExecutor参数的用法及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • SpringBoot整合Guava Cache实现全局缓存的示例代码

    SpringBoot整合Guava Cache实现全局缓存的示例代码

    这篇文章主要介绍了SpringBoot整合Guava Cache实现全局缓存,Guava Cache是Google Guava库中的一个模块,提供了基于内存的本地缓存实现,文中介绍了SpringBoot整合使用Guava Cache的具体步骤,需要的朋友可以参考下
    2024-03-03
  • Java如何使用逆波兰式(后缀表达式)计算表达式的值

    Java如何使用逆波兰式(后缀表达式)计算表达式的值

    这篇文章主要介绍了Java如何使用逆波兰式(后缀表达式)计算表达式的值,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Spring cloud如何实现FeignClient指定Zone调用

    Spring cloud如何实现FeignClient指定Zone调用

    这篇文章主要介绍了Spring cloud如何实现FeignClient指定Zone调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03

最新评论