覆盖重写 原有Spring Bean的几种方式

目录

  • 场景
  • 方法1 直接在自己工程中建同包同类名的类进行替换
  • 方法2 采用@Primary注解
  • 方法3 排除需要替换的jar包中的类
  • 方法4 @Bean 覆盖
  • 方法5 使用BeanDefinitionRegistryPostProcessor

场景

什么情况下要覆写原有的Spring Bean ? 例如引入的第三方jar包中的某个类有些问题,然有没有源码提供或者嫌编译源码太费事,这个时间可以考虑覆写原有的类。

方法1 直接在自己工程中建同包同类名的类进行替换

方式简单粗暴,可以直接覆盖掉jar包中的类,spring项目会优先加载自定义的类。
下面是覆盖 flowable框架中的一个类 FlowableCookieFilter,主要是想修改它里面的redirectToLogin方法的业务逻辑。包路径为 org.flowable.ui.common.filter, 直接工程里面新建一样路径一样类名FlowableCookieFilter即可。
覆盖重写 原有Spring Bean的几种方式

方法2 采用@Primary注解

该方法适用于接口实现类,自己创建一个原jar包接口的实现类,然后类上加上@Primary注解,spring则默认加载该类实例化出的Bean。
下面的例子: 一个接口 RemoteIdmService,原先jar包中只有一个实现类 RemoteIdmServiceImpl,现在自己工程里面创建一个 CustomRemoteIdmServiceImpl 继承RemoteIdmService接口,然后发现所有调用RemoteIdmService接口里面的方法实际调用走的是CustomRemoteIdmServiceImpl 里面的方法。

覆盖重写 原有Spring Bean的几种方式
覆盖重写 原有Spring Bean的几种方式

方法3 排除需要替换的jar包中的类

使用 @ComponentScan 里面的 excludeFilters 功能排除调用要替换的类,然后自己写个类继承替换的类即可。
下面的例子是替换掉 jar包中的PersistentTokenServiceImpl类

  1. @SpringBootApplication
  2. @ComponentScan(excludeFilters = {
  3. @ComponentScan.Filter(type =
  4. FilterType.ASSIGNABLE_TYPE, classes = {
  5. PersistentTokenServiceImpl.class})})
  6. public class Application {
  7. public static void main(String[] args) {
  8. new SpringApplication(Test.class).run(args);
  9. }
  10. }
  1. @Service
  2. public class MyPersistentTokenServiceImpl extends PersistentTokenServiceImpl{
  3. @Override
  4. public Token saveAndFlush(Token token) {
  5. // 覆写该方法的业务逻辑
  6. tokenCache.put(token.getId(), token);
  7. idmIdentityService.saveToken(token);
  8. return token;
  9. }
  10. @Override
  11. public void delete(Token token) {
  12. // 覆写该方法的业务逻辑
  13. tokenCache.invalidate(token.getId());
  14. idmIdentityService.deleteToken(token.getId());
  15. }
  16. @Override
  17. public Token getPersistentToken(String tokenId) {
  18. // 覆写该方法的业务逻辑
  19. return getPersistentToken(tokenId, false);
  20. }
  21. }

方法4 @Bean 覆盖

该场景针对,框架jar包中有@ConditionalOnMissingBean注解,这种注解是说明如果你也创建了一个一样的Bean则框架就不自己再次创建这个bean了,这样你可以覆写自己的bean。
原jar包中的配置类:
覆盖重写 原有Spring Bean的几种方式
直接继承要覆盖的类,自己重写里面方法,使用@Component注入到spring中去
覆盖重写 原有Spring Bean的几种方式

方法5 使用BeanDefinitionRegistryPostProcessor

关于 BeanDefinitionRegistryPostProcessor 、 BeanFactoryPostProcessor可以参考下面的博文:
https://blog.csdn.net/ztchun/article/details/90814135

BeanDefinitionRegistryPostProcessor 说白了就是可以在初始化Bean之前修改Bean的属性,甚至替换原先准备要实例化的bean。

实战演示:

假设jar包中有一个类 MyTestService,正常情况下它会被spring自动扫描到注入IOC容器中去。

  1. package com.middol.mytest.config.beantest.register.jar;
  2. import org.springframework.stereotype.Service;
  3. import javax.annotation.PostConstruct;
  4. import javax.annotation.PreDestroy;
  5. /** * @author guzt */
  6. @Service("myTestService")
  7. public class MyTestService {
  8. private String name1;
  9. private String name2;
  10. private String name3;
  11. public MyTestService() {
  12. this.name1 = "";
  13. this.name2 = "";
  14. this.name3 = "";
  15. }
  16. public MyTestService(String name1, String name2, String name3) {
  17. this.name1 = name1;
  18. this.name2 = name2;
  19. this.name3 = name3;
  20. }
  21. @PostConstruct
  22. public void init() {
  23. System.out.println("MyTestService init");
  24. }
  25. @PreDestroy
  26. public void destory() {
  27. System.out.println("MyTestService destroy");
  28. }
  29. public void show() {
  30. System.out.println("------------------------");
  31. System.out.println("我是jar中通过注解@Service主动加入Spring的IOC里面的");
  32. System.out.println("------------------------");
  33. }
  34. public String getName1() {
  35. return name1;
  36. }
  37. public void setName1(String name1) {
  38. this.name1 = name1;
  39. }
  40. public String getName2() {
  41. return name2;
  42. }
  43. public void setName2(String name2) {
  44. this.name2 = name2;
  45. }
  46. public String getName3() {
  47. return name3;
  48. }
  49. public void setName3(String name3) {
  50. this.name3 = name3;
  51. }
  52. }

自己工程中继承该类,并且覆写里面的show中的方法

  1. package com.middol.mytest.config.beantest.register;
  2. import com.middol.mytest.config.beantest.register.jar.MyTestService;
  3. /** * @author guzt */
  4. public class MyTestServiceIpml extends MyTestService {
  5. public MyTestServiceIpml() {
  6. }
  7. public MyTestServiceIpml(String name1, String name2, String name3) {
  8. super(name1, name2, name3);
  9. }
  10. @Override
  11. public void show() {
  12. System.out.println("------------------------");
  13. System.out.println("我是被BeanDefinitionRegistry手动注册到Spring的IOC里面的");
  14. System.out.println("------------------------");
  15. }
  16. }

然后 实现 BeanDefinitionRegistryPostProcessor 接口,修改原来bean定义,主要查看postProcessBeanDefinitionRegistry方法的实现,先清空原bean定义,注册我们自己的bean定义来达到替换的目的。

  1. package com.middol.mytest.config.beantest.register;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.beans.BeansException;
  5. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  6. import org.springframework.beans.factory.support.BeanDefinitionBuilder;
  7. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  8. import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
  9. import org.springframework.stereotype.Component;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import java.util.Map;
  12. /** * @author amdin */
  13. @Component
  14. public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
  15. private Logger logger = LoggerFactory.getLogger(this.getClass());
  16. @Override
  17. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
  18. logger.info("bean 定义查看和修改...");
  19. String beanName = "myTestService";
  20. // 先移除原来的bean定义
  21. beanDefinitionRegistry.removeBeanDefinition(beanName);
  22. // 注册我们自己的bean定义
  23. BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(MyTestServiceIpml.class);
  24. // 如果有构造函数参数, 有几个构造函数的参数就设置几个 没有就不用设置
  25. beanDefinitionBuilder.addConstructorArgValue("构造参数1");
  26. beanDefinitionBuilder.addConstructorArgValue("构造参数2");
  27. beanDefinitionBuilder.addConstructorArgValue("构造参数3");
  28. // 设置 init方法 没有就不用设置
  29. beanDefinitionBuilder.setInitMethodName("init");
  30. // 设置 destory方法 没有就不用设置
  31. beanDefinitionBuilder.setDestroyMethodName("destory");
  32. // 将Bean 的定义注册到Spring环境
  33. beanDefinitionRegistry.registerBeanDefinition("myTestService", beanDefinitionBuilder.getBeanDefinition());
  34. }
  35. @Override
  36. public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
  37. // bean的名字为key, bean的实例为value
  38. Map<String, Object> beanMap = configurableListableBeanFactory.getBeansWithAnnotation(RestController.class);
  39. logger.info("所有 RestController 的bean {}", beanMap);
  40. }
  41. }

写一个 业务类BusinessTestService测试一下,期望结果:所有用到 MyTestService的地方实际调用的变成了MyTestServiceIpml里面的方法。

  1. package com.middol.mytest.config.beantest.register;
  2. import com.middol.mytest.config.beantest.register.jar.MyTestService;
  3. import org.springframework.stereotype.Service;
  4. import javax.annotation.PostConstruct;
  5. import javax.annotation.Resource;
  6. /** * @author guzt */
  7. @Service
  8. public class BusinessTestService {
  9. @Resource
  10. private MyTestService myTestService;
  11. @PostConstruct
  12. public void init() {
  13. System.out.println(myTestService.getName1());
  14. System.out.println(myTestService.getName2());
  15. System.out.println(myTestService.getName3());
  16. // 看看到底是哪一个Bean
  17. myTestService.show();
  18. }
  19. }

控制台打印如下:
覆盖重写 原有Spring Bean的几种方式
可以发现,和我们期望的结果的一样:所有用到 MyTestService的地方实际调用的变成了MyTestServiceIpml里面的方法 !

OVER …

转自:https://www.wangt.cc/2020/10/%E8%A6%86%E7%9B%96%E9%87%8D%E5%86%99-%E5%8E%9F%E6%9C%89spring-bean%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/

posted on 2018-11-05 11:18  duanxz  阅读(6029)  评论(0编辑  收藏  举报