@Conditional:根据条件选择性注入Bean
在config包下新增一个配置类ConditionalBeanConfig
@Configuration
public class ConditionalBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
@Bean("peter")
public Person peter(){
System.out.println("peter被实例化");
Person person = new Person();
person.setName("peter");
person.setAge(18);
return person;
}
@Bean("thor")
public Person thor(){
System.out.println("thor被实例化");
Person person = new Person();
person.setName("thor");
person.setAge(3000);
return person;
}
}
新增一个ConditionalBeanTest
public class ConditionalBeanTest {
@Test
public void testConditionalBean(){
ApplicationContext context = new AnnotationConfigApplicationContext(ConditionalBeanConfig.class);
System.out.println("IoC容器初始化完成");
}
}
执行测试,控制台输出如下
测试运行时如何根据操作系统的不同来实例化不同的Bean?即如何选择性的注入Bean,这就需要用到@Conditional注解,定义选择条件需要实现Condition接口
在config包中新增两个自定义的条件类WinCondition和MacCondition,实现Condition中的matches方法
public class WinCondition implements Condition {
/**
* 过滤条件,返回true or false
* @param context 判断条件可以使用的上下文
* @param metadata 注解信息
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否为Win系统
// 获取到IoC容器正在使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);
// 符合条件,返回true
if (osName.contains("Windows")){
return true;
}
return false;
}
}
public class MacCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);
if (osName.contains("Mac")){
return true;
}
return false;
}
}
Tips FactoryBean与BeanFactory的区别? FactoryBean:用来把Bean通过注册进入到容器中 BeanFactory:用来从容器中获取实例化后的Bean,ApplicationContext就是间接继承了BeanFactory
在ConditionalBeanConfig中要注册进容器的Bean增加条件@Conditional注解,表示根据条件进行选择性注入,stark无论那种系统都会注入,没有任何条件,peter注入前会先判断操作系统,只有在操作系统为Win才会注入,thor只有在操作系统为Mac才会注入
@Configuration
public class ConditionalBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
@Conditional(WinCondition.class)
@Bean("peter")
public Person peter(){
System.out.println("peter被实例化");
Person person = new Person();
person.setName("peter");
person.setAge(18);
return person;
}
@Conditional(MacCondition.class)
@Bean("thor")
public Person thor(){
System.out.println("thor被实例化");
Person person = new Person();
person.setName("thor");
person.setAge(3000);
return person;
}
}
执行测试,查看控制台打印,条件注入执行成功,只有stark和thor被注入
@Import
@Data
public class User {
private String username;
private String password;
}
@Data
public class Role {
private Long id;
private Long roleId;
private String roleName;
private String roleDesc;
}
在config包中新增ImportBeanConfig,@Import的使用方式是在配置类上,value是一个数组,可以添加多个类的字节码
@Configuration
@Import(value = {Role.class, User.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
}
新增测试类ImportBeanTest,获取容器中所有Bean的ID,执行测试查看容器中的Bean
public class ImportBeanTest {
@Test
public void getBeanByImport(){
ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class);
System.out.println("IoC容器初始化完成");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
控制台成功打印,User和Role成功注入IoC容器中
ImportSelector是一个接口,也可以将Bean注册到容器中,importSelectors方法返回的数据就是要注册到IoC容器的组件的全路径名数组,需要自定义实现 这里直接实现ImportSelector类中的selectImports,不做任何修改
public class CustImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return null;
}
}
在ImportBeanConfig中的@Import注解中加入CustImportSelector.class
@Configuration
@Import(value = {Role.class, User.class, CustImportSelector.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
}
执行ImportBeanTest,查看控制台打印
Plus:Debug报错原因
设置断点
启动debug,然后step into
连续两次step into,下图中classNames就是要返回的类名数组,这里为空,因为自定义类中返回的就是null
再连续两次Step Into,就来到了报错的地方,
可以看出报错的根本原因就是自定义类中返回null导致的,因此importSelectors方法的返回不能为null ImportSelector接口的正确使用 首先新增两个实体类
修改CustImportSelector中importSelectors方法
public class CustImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.citi.entity.Product","com.citi.entity.Category"};
}
}
再次执行ImportBeanTest,stark是通过@Bean注解注册到容器中,Role和User是通过@Import注入到容器中,Product和Category是通过自定义类实现ImportSelect接口注册到容器中的
使用自定义类实现ImportBeanDefinitionRegistrar接口注入Bean 新增一个Order实体类
@Data
public class Order {
private Integer id;
private String orderNo;
private Integer userId;
private Integer totalPrice;
}
自定义类实现ImportBeanDefinitionRegistrar,registerBeanDefinition()方法需要传入一个BeanDefination,BeanDefination是一个接口,从源码中可以看出RootBeanDefinition间接继承了BeanDefinition,因此可以实例化一个RootBeanDefinition传入registerBeanDefinition()方法中
public class CustImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
boolean containProduct = registry.containsBeanDefinition("com.citi.entity.Product");
boolean containCategory = registry.containsBeanDefinition("com.citi.entity.Category");
// 同时为true,则注入Order
if (containCategory && containProduct){
BeanDefinition orderDefinition = new RootBeanDefinition(Order.class);
registry.registerBeanDefinition("order",orderDefinition);
}
}
}
源码中DefaultListableBeanFactory实现了registerBeanDefinition,将Bean put到一个beanDefinitionMap中
beanDefinitionMap是一个Map类型的数据结构
使用自定义的CustImportBeanDefinitionRegistrar类, 在ImportBeanConfig类上的@Import注解中的value数组中加入自定义的
@Configuration
@Import(value = {Role.class, User.class, CustImportSelector.class,CustImportBeanDefinitionRegistrar.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
}
执行测试,当Product和Category的实例化的实例化对象存在时,order也存在
在ImportBeanConfig类上的@Import注解中的value数组中删除自定义的ImportSelect类,也就是说不再往容器中注册Product和Category
@Configuration
@Import(value = {Role.class, User.class,CustImportBeanDefinitionRegistrar.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
}
这时候在执行测试,查看是容器中否有order实例化对象
可以看出容器中不存在Product和Category实例化对象,也就不存在Order的实例化对象,说明CustImportBeanDefinitionRegistrar条件限制成功
FactoryBean接口也可以实现将Bean注册到容器中,但是要实现该接口的方法,Person就是要导入的Bean
public class CustFactoryBean implements FactoryBean<Person> {
@Override
public Person getObject() throws Exception {
Person person = new Person();
person.setName("peter");
person.setAge(18);
return person;
}
@Override
public Class<?> getObjectType() {
return Person.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
使用CustFactoryBean的方式有两种,可以在@Import注解的vlaue数组中使用,也可以在配置类中添加方法返回CustFactoryBean,并在方法上添加@Bean的方式,其实就是把自定义的CustFactoryBean注入到容器中,从容器中获取CustFactoryBean,在调用getObejct()方法获取Person Bean
@Configuration
public class ImportBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
@Bean("custFactoryBean")
public CustFactoryBean custFactoryBean(){
return new CustFactoryBean();
}
}
或者
@Configuration
@Import(value = {CustFactoryBean.class})
public class ImportBeanConfig {
@Bean("stark")
public Person stark(){
System.out.println("stark被实例化");
Person person = new Person();
person.setName("stark");
person.setAge(40);
return person;
}
}
创建FactoryBeanTest,先获取CustFactoryBean,在调用该类的getObject方法获取Person Bean
public class FactoryBeanTest {
@Test
public void getBeanByImport(){
ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class);
System.out.println("IoC容器初始化完成");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
CustFactoryBean custFactoryBean = (CustFactoryBean) context.getBean(CustFactoryBean.class);
Person person = null;
try {
person = (Person) custFactoryBean.getObject();
} catch (Exception e){
e.printStackTrace();
}
System.out.println(person);
}
}
控制台成功打印CustFactoryBean 和 Person实例化对象peter
修改CustFactoryBean的isSingleton方法,即从单例改为多例
@Override
public boolean isSingleton() {
return false;
}
在FactoryBeanTest中增加一个测试方法,获取两个Person实例化对象,查看是否为同一对象
@Test
public void getMultiInstanceByCustFactoryBean(){
ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class);
System.out.println("IoC容器初始化完成");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
CustFactoryBean custFactoryBean = (CustFactoryBean) context.getBean(CustFactoryBean.class);
Person person = null;
Person person1 = null;
try {
person = (Person) custFactoryBean.getObject();
person1 = (Person) custFactoryBean.getObject();
} catch (Exception e){
e.printStackTrace();
}
System.out.println(person);
System.out.println(person == person1);
}
控制台输出false,两个Person对象不想等,说明是多例的