Spring Framework 源码学习笔记(二)

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容器初始化完成");

    }
}
复制代码

执行测试,控制台输出如下

image.png

测试运行时如何根据操作系统的不同来实例化不同的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

image.png

image.png

在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被注入

image.png

Section 02 - @Import

@Import

  • 手动添加组件到IoC容器
  • 使用ImportSelector,自定义返回组件,返回组件就是要注入容器的Bean
  • 使用ImportBeanDefinitionRegistrar返回自定义组件,返回组件就是要注入容器的Bean

在配置类中将Bean注册进IoC容器的几种方式

  1. 通过方法+@Bean注解方式注册进容器中,方法返回类型为Bean的类型,方法名默认为Bean的id,也可以通过@Bean("Bean ID")方式修改Bean id,通常用来导入第三方的组件
  2. 通过包扫描@ComponentScan + 组件注解@Component(包括了@Controller,@Service,@Repository)方式注册进IoC容器,通常用来导入controller,service包中的Controller类和Service类,SSM框架中通常的使用方式是将@ComponentScan注解用配置文件代替
  3. @Import可以快速将组件注册进容器中,使用@Import导入后,Bean的默认ID为类的全路径名,也可以自定义实现ImportSelectorImportBeanDefinitionRegistrar手动将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容器中 image.png

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,查看控制台打印

image.png

Plus:Debug报错原因

设置断点 image.png 启动debug,然后step into

image.png 连续两次step into,下图中classNames就是要返回的类名数组,这里为空,因为自定义类中返回的就是null

image.png 再连续两次Step Into,就来到了报错的地方,

image.png 可以看出报错的根本原因就是自定义类中返回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接口注册到容器中的

image.png

使用自定义类实现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()方法中

image.png image.png image.png

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中 image.png beanDefinitionMap是一个Map类型的数据结构 image.png

使用自定义的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也存在 image.png

在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实例化对象

image.png 可以看出容器中不存在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 image.png

修改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对象不想等,说明是多例的

image.png

猜你喜欢

转载自juejin.im/post/7042920785391910925