Некоторое время назад бизнес-отдел взял на себя проект поставщика по эксплуатации и техническому обслуживанию. В этом проекте поставщик предоставил пружинный стартер, но этот стартер не отвечал потребностям бизнес-отдела, который изначально хотел расширить. на этом стартере, но обнаружил, что существует основной класс, который помечен @Primary. Пример следующий.
@Bean
@Primary
public ThirdpartyRepository thirdpartyRepository(){
return new ThirdpartyRepository();
}
Это помешало им использовать свои индивидуальные классы, поэтому бизнес-отдел обратился к нам, чтобы узнать, есть ли какое-либо решение с нашей стороны. Сегодня мы поговорим об этой теме: как элегантно заменить Spring Bean, предоставленный третьими сторонами.
Конкретные шаги заключаются в том, чтобы скопировать сторонний класс, который будет заменен в этот проект, и сохранить имя пакета и имя класса точно такими же, как у стороннего класса, а затем добавить свою собственную бизнес-логику в скопированный класс.
Это решение в основном использует порядок загрузки классов, то есть классы этого проекта будут загружены первыми, чем сторонние классы.
Если вы знаете больше о Spring, вы знаете, что объектный объект становится Spring Bean. Обычной операцией является преобразование его в объект bean-компонента с помощью BeanDefinition. Поэтому нам нужно заменить сторонний bean-компонент на наш bean-компонент. изменить сторонний компонент BeanDefinition, как его изменить? Проиллюстрируем на конкретном примере
1. Имитировать сторонний стартер
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 все еще используется.
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);
После внесения изменений мы еще раз протестируем и проверим.
Я обнаружил, что наша логика пропала
Выше приведены два варианта реализации замены стороннего компонента Spring. Первый вариант является универсальным, то есть его также можно использовать в проектах, не связанных с Spring, но он не очень элегантен, особенно когда на этот класс ссылаются многие проекты. В проект необходимо добавить этот класс дополнительно, и чтобы этот класс вступил в силу, в бизнес-проект необходимо ввести дополнительное имя пакета, не связанное тесно с бизнес-проектом. Второй метод больше подходит для весенних проектов, но у него есть ограничения и его можно использовать только в весенних проектах, но он относительно элегантен.
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-thirdparty-bean-replace