聊聊如何优雅替换第三方提供的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));
}
}
控制台输出如下
会发现走的还是第三方的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