聊聊如何优雅替换第三方提供的spring bean

前言

前阵子业务部门接手供方的项目过来运维,在这个项目中,供方提供了一个springboot starter,但这个starter不满足业务部门需求的,业务部门的研发本想基于这个starter进行扩展,但发现其中有个核心类,用了 @Primary注解,示例形如下

  @Bean
    @Primary
    public ThirdpartyRepository thirdpartyRepository(){
        return new ThirdpartyRepository();
    }

这样导致他们无法使用他们自定义的类,于是业务部门就找上了我们部门,看我们这边有没有什么法子,今天就来聊聊这个话题,如何优雅的替换第三方提供的spring bean

如何替换第三方提供的spring bean

方案一:通过类替换

具体步骤是将要替换的第三方类拷贝到本项目中,且包名类名和第三方类保持一模一样,然后在拷贝后的类中,添加自己的业务逻辑

该方案主要是利用了类的加载顺序,即本项目的class会比第三方的class优先加载

方案二:利用spring的扩展点进行替换

如果对spring比较了解的话,就会知道一个object对象变成spring bean,常规操作是会通过BeanDefinition转换成bean对象,因此我们要将第三方的bean替换成我们的bean,我们可以通过修改第三方的BeanDefinition,那如何修改呢?我们通过一个具体示例来说明

1、模拟第三方starter

public class ThirdpartyService {

    private ThirdpartyRepository thirdpartyRepository;

    public ThirdpartyService(ThirdpartyRepository thirdpartyRepository) {
        this.thirdpartyRepository = thirdpartyRepository;
    }

    public String getThirdparty(){
        return thirdpartyRepository.getThirdparty();
    }
}

public class ThirdpartyRepository {
    public String getThirdparty() {

        return "Hello Third party Repository";
    }
}

@Configuration
public class ThirdpartyAutoConfiguration {


    @Bean
    @Primary
    public ThirdpartyRepository thirdpartyRepository(){
        return new ThirdpartyRepository();
    }

    @Bean
    public ThirdpartyService thirdpartyService(ThirdpartyRepository thirdpartyRepository){
        return new ThirdpartyService(thirdpartyRepository);
    }
}

2、模拟我们扩展的类

@Repository
public class CustomRepository extends ThirdpartyRepository {

    @Override
    public String getThirdparty() {
        return "Hello Custom Repository";
    }
}

3、先模拟一下测试效果

@SpringBootApplication
public class ThirdpartyTestApplication implements ApplicationRunner {

    @Autowired
    private ThirdpartyService thirdpartyService;

    @Autowired
    private ApplicationContext applicationContext;

    public static void main(String[] args) {
        SpringApplication.run(ThirdpartyTestApplication.class);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(thirdpartyService.getThirdparty());
        System.out.println(applicationContext.getBeansOfType(ThirdpartyRepository.class));
    }
}

控制台输出如下


22ff35b07a673c2b76b2bf04d4ded642_fdec51b21b0a58a13516a3fb27a0a11b.png

会发现走的还是第三方的spring bean逻辑

4、修改第三方的spring beanDefinition

public class ThirdpartyBeanReplaceBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private DefaultListableBeanFactory defaultListableBeanFactory;

    private AtomicBoolean isAlreadyReplace = new AtomicBoolean(false);

    private final ThirdpartyBeanReplaceProperty thirdpartyBeanReplaceProperty;

    public ThirdpartyBeanReplaceBeanPostProcessor(ThirdpartyBeanReplaceProperty thirdpartyBeanReplaceProperty) {
        this.thirdpartyBeanReplaceProperty = thirdpartyBeanReplaceProperty;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if(thirdpartyBeanReplaceProperty.isBeanReplace() && !CollectionUtils.isEmpty(thirdpartyBeanReplaceProperty.getReplaceBeans()) && !isAlreadyReplace.get()){
            thirdpartyBeanReplaceProperty.getReplaceBeans().forEach(thirdpartyReplaceBeanHolder -> {
                defaultListableBeanFactory.removeBeanDefinition(thirdpartyReplaceBeanHolder.getReplaceBeanName());
                BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
                beanDefinition.setBeanClassName(thirdpartyReplaceBeanHolder.getReplaceBeanClassName());
                defaultListableBeanFactory.registerBeanDefinition(thirdpartyReplaceBeanHolder.getReplaceBeanName(),beanDefinition);
                logger.info("replace bean:{} to bean:{}",thirdpartyReplaceBeanHolder.getReplaceBeanName(),thirdpartyReplaceBeanHolder.getReplaceBeanClassName());
                isAlreadyReplace.set(true);
            });
        }

        return SmartInstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);

    }

注: 修改BeanDefinition,需要先执行删除beanName,再添加的BeanDefinition的步骤,来达到更新的效果,不能直接进行替换,否则会报错

 defaultListableBeanFactory.removeBeanDefinition(thirdpartyReplaceBeanHolder.getReplaceBeanName());
                BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
                beanDefinition.setBeanClassName(thirdpartyReplaceBeanHolder.getReplaceBeanClassName());
                defaultListableBeanFactory.registerBeanDefinition(thirdpartyReplaceBeanHolder.getReplaceBeanName(),beanDefinition);

修改后我们再次测试验证下

8626101c7c8e7d574a6b5a5688b14e76_5859c680d93248beccf09b3363f71a0f.png

发现已经是走了我们的逻辑

总结

上述提供了2种方案来实现第三方的spring bean替换,其中方案一具有普适性,即在非spring项目中,也能使用,但是就是不大优雅,尤其当这个类被很多项目引用,各个项目就得额外加入该类,而且为了让这个类生效,必须在业务项目中额外引入和业务项目关系不是很大的包名。第二种方式比较适用在spring项目中,但就是有局限性,只能使用在spring项目中,但相对优雅

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-thirdparty-bean-replace

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容