很多时候我们需要根据不同的条件在容器中加载不同的Bean,或者根据不同的条件来选择是否在容器中加载某个Bean,这就是Bean的加载控制,一般我们可以通过编程式或注解式两种不同的方式来完成Bean的加载控制
为了后续演示方便,这里先准备以下环境:
1)新建一个maven项目,导入依赖:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.9version>
dependency>
2)新建一个包com.itheima.bean,包中包含后续测试需要用到的一些实体类
public class Cat {
}
public class Dog {
}
public class Mouse {
}
3)在com.itheima包下新建一个类SpringConfig作为配置类,内容如下:
public class SpringConfig {
}
4)在com.itheima包下新建一个App类,内容固定如下,该代码的主要作用是读取SpringConfig类,然后将当前容器中所有的Bean的定义名进行打印
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beans = ctx.getBeanDefinitionNames();
for (String bean : beans) {
System.out.println(bean);
}
}
}
整体目录结构如下:

现在运行App,打印结果如下:

可以看到现在容器中除了一些后处理器和我们通过容器加载的配置类以外,就没有其他的Bean了
我们可以通过ImportSelector完成对Bean加载的控制,对ImportSelector不了解的朋友可以阅读我的上一篇文章:【Spring】Bean加载方式,当然不了解也不影响使用,毕竟也是固定模板。
在包下新建一个类,名为MyImportSelector,实现ImportSelector并重写selectImports()方法
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//返回值表示需要加载到容器中的Bean全限定名数组
return new String[0];
}
}
假设我们现在的需求是,如果当前项目中存在全限定名为com.itheima.bean.Dog的类,那么就将一个Cat类的Bean加载到容器中,如果不存在,就什么都不做。编写代码如下:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
try {
//尝试加载com.itheima.bean.Dog
Class<?> clazz = Class.forName("com.itheima.bean.Dog");
//加载时如果没有出现异常,说明项目中是存在这个类的,将一个Cat类型的Bean加载到容器中
if(clazz != null) {
return new String[]{"com.itheima.bean.Cat"};
}
} catch (ClassNotFoundException e) {
//加载时如果出现了异常,说明类不存在,则直接返回一个空数组
return new String[0];
}
return null;
}
}
在SpringConfig类上导入MyImportSelector
@Import(MyImportSelector.class)
public class SpringConfig {
}
此时项目中是存在com.itheima.bean.Dog,那么如果我们运行再次App类,应该是能打印出一个Cat类型的Bean的。来看一下结果:

结果正如所料
我们稍微变动一下需求,假设我们现在的需求是,如果当前项目中存在全限定名为com.itheima.bean.Wolf的类,那么就将一个Cat类的Bean加载到容器中,如果不存在,就将一个Dog类的Bean加载到容器中,那么我们只需要修改一下MyImportSelector即可:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
try {
//尝试加载com.itheima.bean.Dog
Class<?> clazz = Class.forName("com.itheima.bean.Wolf");
//加载时如果没有出现异常,说明项目中是存在这个类的,将一个Cat类型的Bean加载到容器中
if(clazz != null) {
return new String[]{"com.itheima.bean.Cat"};
}
} catch (ClassNotFoundException e) {
//加载时如果出现了异常,说明类不存在,则直接返回一个空数组
return new String[]{"com.itheima.bean.Dog"};
}
return null;
}
}
因为我们现在的项目中是没有Wolf类的,所以这此容器中应该是不会出现Cat的Bean而是会出现Dog的Bean的,我们再次运行一下App类来验证一下结果:

上面这种方式确实能实现Bean的加载控制,但是未免也太麻烦了些,有没有更简单的方式呢?
SpringBoot为我们提供了一套专门用作Bean加载控制的注解,注意是SpringBoot,Spring虽然也提供了对应的注解,但是最后还是要回归到自己写代码上。
因为这里用到了SpringBoot,因此我们需要导入SpringBoot的依赖,为了防止版本冲突,之前导入的Spring的依赖就可以删掉了
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.5.4version>
dependency>
删掉SpringConfig类的@Import注解,在类下添加一个使用@Bean修饰的方法,将一个Cat类的Bean加载到容器中,Bean名称为tom
public class SpringConfig {
@Bean
public Cat tom(){
return new Cat();
}
}
再次启动App类,可以看到tom已经被加载到容器中了

那么我们应该通过注解方式实现对Bean的加载控制呢?
在SpringBoot中为我们提供了一系列进行条件控制的注解,如下图所示:

其中每一个注解都代表着一个条件控制方式,接下让我们来演示一下其中几个比较常用的:
1)@ConditionalOnClass:如果当前环境中存在某个类,就加载Bean
public class SpringConfig {
@Bean
@ConditionalOnClass(name = "com.itheima.bean.Wolf")
public Cat tom(){
return new Cat();
}
}
由于这里我们只想看看名为tom的Bean有没有加载,因此可以将App类的代码改为:
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
try {
ctx.getBean("tom");
System.out.println("tom已被加载");
}catch (NoSuchBeanDefinitionException e){
System.out.println("tom未被加载");
}
}
}
因为当前环境中并不存在com.itheima.bean.Wolf,因此名为tom的Bean并不会被加载:

2)@ConditionalOnMissingClass:如果当前环境中不存在某个类,就加载Bean
public class SpringConfig {
@Bean
@ConditionalOnMissingClass(name = "com.itheima.bean.Wolf")
public Cat tom(){
return new Cat();
}
}
运行App,打印结果如下:

3)@ConditionalOnMissingClass:如果容器中存在指定名称Bean,就加载Bean
public class SpringConfig {
@Bean
@ConditionalOnBean(name="jerry") //如果容器中存在名称为jerry的Bean,才加载
public Cat tom(){
return new Cat();
}
}
现在直接运行App,由于我们现在的容器中是没有名为jerry的Bean的,因此结果肯定就是tom未被加载了

修改一下我们之前的Mouse,在类上打上@Component注解,并指定名称为jerry,然后在SpringConfig类上导入Mouse类:
@Component("jerry")
public class Mouse {
}
@Import(Mouse.class)
public class SpringConfig {
@Bean
@ConditionalOnBean(name="jerry") //如果容器中存在名称为jerry的Bean,才加载
public Cat tom(){
return new Cat();
}
}
此时再重新启动App类,结果就不一样了:

那么假设我们现在有个需求,就是当容器中存在名为jerry的Bean且环境中不存在Dog类,tom才加载,应该怎样去完成?代码如下:
@Import(Mouse.class)
public class SpringConfig {
@Bean
@ConditionalOnBean(name="jerry") //如果容器中存在名称为jerry的Bean,才加载
@ConditionalOnMissingClass("com.itheima.bean.Dog")//如果环境中不存在Dog类,才加载
public Cat tom(){
return new Cat();
}
}
如果同时打了多个注解,那么只有同时满足所有条件,Bean才会被加载,现在很显然第二个条件是不满足的,我们启动一下App类:

4)@ConditionalOnWebApplication:如果当前环境是Web环境才加载Bean,否则不加载
@Bean
@ConditionalOnWebApplication
public Cat tom(){
return new Cat();
}
由于我们当前只是一个普通的java程序,因此tom并不会被加载

与之相对的还有@ConditionalOnNotWebApplication,即当前环境不是web环境才加载。
一些常用的注解就先演示到这里,需要注意的是,这些注解除了打在带有@Bean的方法上以外,还可以打在实体类上,演示如下:
@Component("tom")//Bean名称同样定义为tom
@ConditionalOnNotWebApplication//当前环境非web才加载
public class Cat {
}
将SpringConfig中的内容清空,并导入Cat.class
@Import(Cat.class)
public class SpringConfig {
}
运行App类

类似的加载控制注解还有很多,各位可以自己去尝试一下。