前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Framework 源码学习笔记(二)

Spring Framework 源码学习笔记(二)

作者头像
RiemannHypothesis
发布2022-08-19 15:55:41
2320
发布2022-08-19 15:55:41
举报
文章被收录于专栏:Elixir

Chapter 02 @Conditional,@Import,@FactoryBean

Section 01 - @Conditional

@Conditional:根据条件选择性注入Bean

在config包下新增一个配置类ConditionalBeanConfig

代码语言:javascript
复制
@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

代码语言:javascript
复制
public class ConditionalBeanTest {

    @Test
    public void testConditionalBean(){
        ApplicationContext context = new AnnotationConfigApplicationContext(ConditionalBeanConfig.class);
        System.out.println("IoC容器初始化完成");

    }
}

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

image.png
image.png

测试运行时如何根据操作系统的不同来实例化不同的Bean?即如何选择性的注入Bean,这就需要用到@Conditional注解,定义选择条件需要实现Condition接口

在config包中新增两个自定义的条件类WinCondition和MacCondition,实现Condition中的matches方法

代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
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
image.png
image.png

在ConditionalBeanConfig中要注册进容器的Bean增加条件@Conditional注解,表示根据条件进行选择性注入,stark无论那种系统都会注入,没有任何条件,peter注入前会先判断操作系统,只有在操作系统为Win才会注入,thor只有在操作系统为Mac才会注入

代码语言:javascript
复制
@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
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方法
代码语言:javascript
复制
@Data
public class User {

    private String username;
    private String password;
}
代码语言:javascript
复制
@Data
public class Role {

    private Long id;
    private Long roleId;
    private String roleName;
    private String roleDesc;

}

在config包中新增ImportBeanConfig,@Import的使用方式是在配置类上,value是一个数组,可以添加多个类的字节码

代码语言:javascript
复制
@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

代码语言:javascript
复制
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
image.png

ImportSelector是一个接口,也可以将Bean注册到容器中,importSelectors方法返回的数据就是要注册到IoC容器的组件的全路径名数组,需要自定义实现 这里直接实现ImportSelector类中的selectImports,不做任何修改

代码语言:javascript
复制
public class CustImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return null;
    }
}

在ImportBeanConfig中的@Import注解中加入CustImportSelector.class

代码语言:javascript
复制
@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
image.png

Plus:Debug报错原因

设置断点

image.png
image.png

启动debug,然后step into

image.png
image.png

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

image.png
image.png

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

image.png
image.png

可以看出报错的根本原因就是自定义类中返回null导致的,因此importSelectors方法的返回不能为null ImportSelector接口的正确使用 首先新增两个实体类

修改CustImportSelector中importSelectors方法

代码语言:javascript
复制
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
image.png

使用自定义类实现ImportBeanDefinitionRegistrar接口注入Bean 新增一个Order实体类

代码语言:javascript
复制
@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
image.png
image.png
image.png
代码语言:javascript
复制
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
image.png

beanDefinitionMap是一个Map类型的数据结构

image.png
image.png

使用自定义的CustImportBeanDefinitionRegistrar类, 在ImportBeanConfig类上的@Import注解中的value数组中加入自定义的

代码语言:javascript
复制
@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
image.png

在ImportBeanConfig类上的@Import注解中的value数组中删除自定义的ImportSelect类,也就是说不再往容器中注册Product和Category

代码语言:javascript
复制
@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
image.png

可以看出容器中不存在Product和Category实例化对象,也就不存在Order的实例化对象,说明CustImportBeanDefinitionRegistrar条件限制成功

Section 03 - FactoryBean

FactoryBean接口也可以实现将Bean注册到容器中,但是要实现该接口的方法,Person就是要导入的Bean

代码语言:javascript
复制
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

代码语言:javascript
复制
@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();
    }
}

或者

代码语言:javascript
复制
@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

代码语言:javascript
复制
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
image.png

修改CustFactoryBean的isSingleton方法,即从单例改为多例

代码语言:javascript
复制
@Override
public boolean isSingleton() {
    return false;
}

在FactoryBeanTest中增加一个测试方法,获取两个Person实例化对象,查看是否为同一对象

代码语言:javascript
复制
@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
image.png
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Chapter 02 @Conditional,@Import,@FactoryBean
    • Section 01 - @Conditional
      • Section 02 - @Import
        • Section 03 - FactoryBean
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档