前言

前阵子业务部门接手供方的项目过来运维,在这个项目中,供方提供了一个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));
    }
}

控制台输出如下

会发现走的还是第三方的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);

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


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

总结

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

demo链接

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


linyb极客之路
330 声望191 粉丝