Spring父子容器

一、痛点

当前开发工程以来的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类为刚写的⽗配置⽂件 ParentSpringConfiguration
    GenericApplicationContext 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));
    //当前上下⽂的⽗容器 get
    log.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。

你可能感兴趣的:(spring,java,spring,boot)