一、痛点
当前开发工程以来的spring-boot-starter脚手架,配置了很多通用的bean,而部分无法满足自身需求,因此需要自定义bean,这时候就有可能出现自己定义bean和脚手架或者引入的第三方依赖中的某个bean冲突,导致出现bean重复的报错问题。
二、解决方案
方法1:自定义bean的名字
给自己定义的bean起个别名,避免和二、三方包里的bean重名
方法2:@ConditionalOnMissingBean
如果在 spring 上下⽂中找不到 GsonBuilder的 bean,这⾥才会配置。如果 上下⽂已经有相同的 bean 类型,那么这⾥就不会进⾏配置
给自己定义的bean起个别名,避免和二、三方包里的bean重名
方法3:父子容器
父子容器的主要用途之一便是是上下文隔离。spring总的上下文容器有父子之分。父容器和子容器。父容器对子容器可见,子容器对父容器不可见。
三、Spring⽗⼦容器上下⽂隔离实战(方法3)
将公共组件包(如 通⽤log、通⽤缓存)等⾥⾯的 Spring 配置信息通通由 ⽗容器进⾏加载。
将当前⼯程上下⽂中的所有 Spring 配置由 ⼦容器进⾏加载。
⽗容器和⼦容器可以存在相同类型的 bean,并且如果⼦容器存在,则会优先使⽤⼦容器的 bean,我们可以将上⾯代码进⾏如下改造:
在⼯程⽬录下创建⼀个 parent 包,并编写 parent ⽗容器的配置类:
@Slf4j
@Configuration
//将 starter 中的 enable 注解放在⽗容器的配置中
@EnableZookeeper
public class ParentSpringConfiguration {
}
⾃定义实现 SpringApplicationBuilder 类
public class ChildSpringApplicationBuilder extends SpringApplicationBuilder {public ChildSpringApplicationBuilder(Class<?>... sources) {super(sources);}public ChildSpringApplicationBuilder functions() {//初始化⽗容器,class类为刚写的⽗配置⽂件 ParentSpringConfigurationGenericApplicationContext parent = new AnnotationConfigApplicationContext(ParentSpringConfiguration.class);this.parent(parent);return this;}
}
主要作⽤是在启动 Springboot ⼦容器时,先根据⽗配置类 ParentSpringConfiguration 初始化⽗ 容器 GenericApplicationContext。
然后当前 SpringApplicationBuilder 上下⽂将 ⽗容器设置为初始化的⽗容器,这样就完成了⽗⼦容器配置。
starter 中的 GsonBuilder 会在⽗容器中进⾏初始化。
启动 Spring 容器:
@Slf4j
//@EnableZookeeper 此注解放到了 ParentConfiguration中。
@SpringBootApplication
public class ChildSpringServer {public static void main(String[] args) {ConfigurableApplicationContext applicationContext = new ChildSpringApplicationBuilder(ChildSpringServer.class).functions().run(args);log.info("applicationContext: {}", applicationContext);}
}
此时,可以正常启动 spring 容器,我们通过 applicationContext.getBean() 的形式获取 ZookeeperClinet。
public static void main(String[] args) {ConfigurableApplicationContext applicationContext = new ChildSpringApplicationBuilder(ChildSpringServer.class).functions().registerShutdownHook(false).run(args);log.info("applicationContext: {}", applicationContext);//当前上下⽂log.info("zk name: {}", applicationContext.getBean(ZookeeperClient.class));//当前上下⽂的⽗容器 getlog.info("parent zk name: {}", applicationContext.getParent().getBean(ZookeeperClient.class));}
⽇志打印:
zk name: ZookeeperClient(name=From Current Project) //来⾃当前⼯程,⼦容器
parent zk name: ZookeeperClient(name=From Starter.) //来⾃⽗容器
可以看到当前上下⽂拿到的 bean 是当前⼯程配置的 bean,然⽽我们还可以获取到 ⽗容器中配置的 bean,通过先 getParent() (注意NPE),
然后再获取bean,则会获取到 ⽗容器中的 bean。