Chapter 02 @Conditional,@Import,@FactoryBean
Section 01 - @Conditional
@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被注入
Section 02 - @Import
@Import
- 手动添加组件到IoC容器
- 使用ImportSelector,自定义返回组件,返回组件就是要注入容器的Bean
- 使用ImportBeanDefinitionRegistrar返回自定义组件,返回组件就是要注入容器的Bean
在配置类中将Bean注册进IoC容器的几种方式
- 通过方法+@Bean注解方式注册进容器中,方法返回类型为Bean的类型,方法名默认为Bean的id,也可以通过@Bean("Bean ID")方式修改Bean id,通常用来导入第三方的组件
- 通过包扫描@ComponentScan + 组件注解@Component(包括了@Controller,@Service,@Repository)方式注册进IoC容器,通常用来导入controller,service包中的Controller类和Service类,SSM框架中通常的使用方式是将@ComponentScan注解用配置文件代替
- @Import可以快速将组件注册进容器中,使用@Import导入后,Bean的默认ID为类的全路径名,也可以自定义实现ImportSelector,ImportBeanDefinitionRegistrar手动将Bean注册到容器中,所有的Bean的注册可以使用BeanDefinitionRegistry接口,他的实现类DefaultListableBeanFactory实现了registerBeanDefinition()方法,将Bean放入一个Map中
在entity包中新增实体类Role,User,使用了lombok的@Data注解,自动生成getter/setter/toString方法
@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条件限制成功
Section 03 - FactoryBean
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对象不想等,说明是多例的