乐趣区

关于spring:SpringIOCBean

Scope

对于 Spring 容器来说,当咱们把一个 Bean 标记为 @Component 后,它就会主动为咱们创立一个单例(Singleton),即容器初始化时创立 Bean,容器敞开前销毁 Bean。在容器运行期间,咱们调用 getBean(Class) 获取到的 Bean 总是同一个实例。

还有一种 Bean,咱们每次调用 getBean(Class),容器都返回一个新的实例,这种 Bean 称为 Prototype(原型),它的生命周期显然和 Singleton 不同。申明一个 Prototype 的 Bean 时,须要增加一个额定的@Scope 注解:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {...}

注入 List

有些时候,咱们会有一系列接口雷同,不同实现类的 Bean。例如,注册用户时,咱们要对 email、password 和 name 这 3 个变量进行验证。为了便于扩大,咱们先定义验证接口:

public interface Validator {void validate(String email, String password, String name);
}

而后,别离应用 3 个 Validator 对用户参数进行验证:

@Component
public class EmailValidator implements Validator {public void validate(String email, String password, String name) {if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {throw new IllegalArgumentException("invalid email:" + email);
        }
    }
}

@Component
public class PasswordValidator implements Validator {public void validate(String email, String password, String name) {if (!password.matches("^.{6,20}$")) {throw new IllegalArgumentException("invalid password");
        }
    }
}

@Component
public class NameValidator implements Validator {public void validate(String email, String password, String name) {if (name == null || name.isBlank() || name.length() > 20) {throw new IllegalArgumentException("invalid name:" + name);
        }
    }
}

最初,咱们通过一个 Validators 作为入口进行验证:

@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {for (var validator : this.validators) {validator.validate(email, password, name);
        }
    }
}

留神到 Validators 被注入了一个 List<Validator>,Spring 会主动把所有类型为Validator 的 Bean 拆卸为一个 List 注入进来,这样一来,咱们每新增一个 Validator 类型,就主动被 Spring 拆卸到 Validators 中了,十分不便。

因为 Spring 是通过扫描 classpath 获取到所有的 Bean,而 List 是有序的,要指定 List 中 Bean 的程序,能够加上 @Order 注解:

@Component
@Order(1)
public class EmailValidator implements Validator {...}

@Component
@Order(2)
public class PasswordValidator implements Validator {...}

@Component
@Order(3)
public class NameValidator implements Validator {...}

可选注入

默认状况下,当咱们标记了一个 @Autowired 后,Spring 如果没有找到对应类型的 Bean,它会抛出 NoSuchBeanDefinitionException 异样。

能够给 @Autowired 减少一个 required = false 的参数:

@Component
public class MailService {@Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();
    ...
}

这个参数通知 Spring 容器,如果找到一个类型为 ZoneId 的 Bean,就注入,如果找不到,就疏忽。

这种形式非常适合有定义就应用定义,没有就应用默认值的状况。

创立第三方 Bean

如果一个 Bean 不在咱们本人的 package 治理之内,例如ZoneId,如何创立它?

答案是咱们本人在 @Configuration 类中编写一个 Java 办法创立并返回它,留神给办法标记一个 @Bean 注解:

@Configuration
@ComponentScan
public class AppConfig {
    // 创立一个 Bean:
    @Bean
    ZoneId createZoneId() {return ZoneId.of("Z");
    }
}

Spring 对标记为 @Bean 的办法只调用一次,因而返回的 Bean 依然是单例。

初始化和销毁

有些时候,一个 Bean 在注入必要的依赖后,须要进行初始化(监听音讯等)。在容器敞开时,有时候还须要清理资源(敞开连接池等)。咱们通常会定义一个 init() 办法进行初始化,定义一个 shutdown() 办法进行清理,而后,引入 JSR-250 定义的 Annotation:

  • jakarta.annotation:jakarta.annotation-api:2.1.1

在 Bean 的初始化和清理办法上标记 @PostConstruct@PreDestroy

@Component
public class MailService {@Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();

    @PostConstruct
    public void init() {System.out.println("Init mail service with zoneId =" + this.zoneId);
    }

    @PreDestroy
    public void shutdown() {System.out.println("Shutdown mail service");
    }
}

Spring 容器会对上述 Bean 做如下初始化流程:

  • 调用构造方法创立 MailService 实例;
  • 依据 @Autowired 进行注入;
  • 调用标记有 @PostConstructinit()办法进行初始化。

而销毁时,容器会首先调用标记有 @PreDestroyshutdown()办法。

Spring 只依据 Annotation 查找 无参数 办法,对办法名不作要求。

应用别名

默认状况下,对一种类型的 Bean,容器只创立一个实例。但有些时候,咱们须要对一种类型的 Bean 创立多个实例。例如,同时连贯多个数据库,就必须创立多个 DataSource 实例。

如果咱们在 @Configuration 类中创立了多个同类型的 Bean:

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    ZoneId createZoneOfZ() {return ZoneId.of("Z");
    }

    @Bean
    ZoneId createZoneOfUTC8() {return ZoneId.of("UTC+08:00");
    }
}

Spring 会报 NoUniqueBeanDefinitionException 异样,意思是呈现了反复的 Bean 定义。

这个时候,须要给每个 Bean 增加不同的名字:

@Configuration
@ComponentScan
public class AppConfig {@Bean("z")
    ZoneId createZoneOfZ() {return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")
    ZoneId createZoneOfUTC8() {return ZoneId.of("UTC+08:00");
    }
}

能够用 @Bean("name") 指定别名,也能够用 @Bean+@Qualifier("name") 指定别名。

存在多个同类型的 Bean 时,注入 ZoneId 又会报错:

NoUniqueBeanDefinitionException: No qualifying bean of type 'java.time.ZoneId' available: expected single matching bean but found 2

意思是期待找到惟一的 ZoneId 类型 Bean,然而找到两。因而,注入时,要指定 Bean 的名称:

@Component
public class MailService {@Autowired(required = false)
    @Qualifier("z") // 指定注入名称为 "z" 的 ZoneId
    ZoneId zoneId = ZoneId.systemDefault();
    ...
}

还有一种办法是把其中某个 Bean 指定为@Primary

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    @Primary // 指定为次要 Bean
    @Qualifier("z")
    ZoneId createZoneOfZ() {return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")
    ZoneId createZoneOfUTC8() {return ZoneId.of("UTC+08:00");
    }
}

这样,在注入时,如果没有指出 Bean 的名字,Spring 会注入标记有 @Primary 的 Bean。这种形式也很罕用。例如,对于主从两个数据源,通常将主数据源定义为@Primary

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    @Primary
    DataSource createMasterDataSource() {...}

    @Bean
    @Qualifier("slave")
    DataSource createSlaveDataSource() {...}
}

其余 Bean 默认注入的就是主数据源。如果要注入从数据源,那么只须要指定名称即可。

应用 FactoryBean

咱们在设计模式的工厂办法中讲到,很多时候,能够通过工厂模式创建对象。Spring 也提供了工厂模式,容许定义一个工厂,而后由工厂创立真正的 Bean。

用工厂模式创立 Bean 须要实现 FactoryBean 接口。咱们察看上面的代码:

@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {

    String zone = "Z";

    @Override
    public ZoneId getObject() throws Exception {return ZoneId.of(zone);
    }

    @Override
    public Class<?> getObjectType() {return ZoneId.class;}
}

当一个 Bean 实现了 FactoryBean 接口后,Spring 会先实例化这个工厂,而后调用 getObject() 创立真正的 Bean。getObjectType()能够指定创立的 Bean 的类型,因为指定类型不肯定与理论类型统一,能够是接口或抽象类。

因而,如果定义了一个 FactoryBean,要留神 Spring 创立的 Bean 实际上是这个FactoryBeangetObject()办法返回的 Bean。为了和一般 Bean 辨别,咱们通常都以 XxxFactoryBean 命名。

因为能够用 @Bean 办法创立第三方 Bean,实质上 @Bean 办法就是工厂办法,所以,FactoryBean曾经用得越来越少了。

退出移动版