• Spring源码解析之基础应用(三)


    组合Java配置

    在XML中,我们可以使用<import/>标签,在一个XML文件中引入另一个XML文件,在Java类中,我们同样可以在一个配置类中用@Import引入另一个配置类,被引入的配置类中的@Bean也会加载到spring容器。代码如下:

    @Configuration
    public class ConfigA {
    
        @Bean
        public A a() {
            return new A();
        }
    }
    
    @Configuration
    @Import(ConfigA.class)
    public class ConfigB {
    
        @Bean
        public B b() {
            return new B();
        }
    }
    

      

    我们将ConfigB作为配置类传给AnnotationConfigApplicationContext进行spring容器初始化,我们不但可以从spring容器中获得A和B所对应的bean,还可以得到ConfigA和ConfigB所对应的bean:

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
    
        A a = ctx.getBean(A.class);
        B b = ctx.getBean(B.class);
        ConfigA configA = ctx.getBean(ConfigA .class);
        ConfigB configB = ctx.getBean(ConfigB.class);
    }
    

      

    事实上,@Import并不要求我们引入一个配置类,我们也可以引入一个普通的类,spring容器同样会帮我们把这个类初始化成一个bean:

    package org.example.config;
    
    import org.example.beans.D;
    import org.springframework.context.annotation.Import;
    
    @Import(D.class)
    public class MyConfig3 {
    }
    
    package org.example.beans;
    
    public class D {
    }
    

      

    测试用例:

        @Test
        public void test10() {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig3.class);
            System.out.println(ac.getBean(D.class));
        }
    

      

    运行结果:

    org.example.beans.D@134593bf
    

      

    利用@Import,我们可以把一些第三方的插件引入spring容器初始化为bean,当然,我们也可以在一个方法里返回插件的实例,方法用@Bean注解标注。不过,笔者认为,用@Import或者@Bean来引入bean还是分情况的,如果是引入一个类似DataSource的数据源的bean,我们要在方法里指定数据源的地址、用户名、密码,那么我们可以用@Bean,如果我们需要引入的第三方插件不需要设置额外的属性,则可以用@Import。

    我们还可以用@Import来引入一个ImportBeanDefinitionRegistrar接口的实现,spring容器会帮我们回调ImportBeanDefinitionRegistrar接口的实现,在ImportBeanDefinitionRegistrar接口的实现内,我们可以往spring容器注册一个BeanDefinition,spring容器会根据BeanDefinition生成一个bean,BeanDefinition用来描述一个bean的class对象,比如这个bean的class是什么?作用域是单例还是原型?是否懒加载?等等。几乎可以认为,只要是存在于spring容器中的bean,都会有一个对应的BeanDefinition来描述这个bean。我们先来看下BeanDefinition接口的部分方法:

    package org.springframework.beans.factory.config;
    ……
    public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    	……
    	//设置bean所对应的class
    	void setBeanClassName(@Nullable String beanClassName);
    	//获取bean所对应的class
    	String getBeanClassName();
    	//设置bean的作用域
    	void setScope(@Nullable String scope);
    	//获取bean的作用域
    	String getScope();
    	//设置bean是否懒加载
    	void setLazyInit(boolean lazyInit);
    	//判断bean是否懒加载
    	boolean isLazyInit();
    	//设置bean在初始化前需要的依赖项
    	void setDependsOn(@Nullable String... dependsOn);
    	//获取bean在初始化前需要的依赖项
    	String[] getDependsOn();
    	……
    }
    

      

     ImportBeanDefinitionRegistrar可以让我们重写两个方法,从方法名可以看出,注册BeanDefinition,第一个方法仅比第二个多一个参数importBeanNameGenerator,为beanName生成器。

    public interface ImportBeanDefinitionRegistrar {
    	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
    			BeanNameGenerator importBeanNameGenerator) {
    
    		registerBeanDefinitions(importingClassMetadata, registry);
    	}
    	
    	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    	}
    }
    

      

    我们在MyImportBeanDefinitionRegistrar里重写了registerBeanDefinitions,由于BeanDefinition是接口,所以我们用spring编写好的实现类GenericBeanDefinition进行注册,我们声明了一个GenericBeanDefinition后并设置其对应的属性,然后将beanName和BeanDefinition注册进registry:

    package org.example.beans;
    
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.type.AnnotationMetadata;
    
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(D.class);
            registry.registerBeanDefinition("d", beanDefinition);
        }
    }
    
    package org.example.beans;
    
    public class D {
    }
    

      

     我们在MyConfig4中@Import进MyImportBeanDefinitionRegistrar,并在后面的测试用例中把MyConfig4传入应用上下文进行初始化。

    package org.example.config;
    
    import org.example.beans.MyImportBeanDefinitionRegistrar;
    import org.springframework.context.annotation.Import;
    
    @Import(MyImportBeanDefinitionRegistrar.class)
    public class MyConfig4 {
    }
    

      

    测试用例中我们根据在MyImportBeanDefinitionRegistrar注册的beanName获取bean,从运行结果可以看到,spring会根据我们传入的BeanDefinition创建bean。

        @Test
        public void test11() {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig4.class);
            System.out.println(ac.getBean("d"));
        }
    

      

    运行结果:

    org.example.beans.D@4b53f538
    

      

    关于上面说的BeanDefinition如果看不懂的话可以先略过,后面还会再详讲BeanDefinition。

    @DependsOn

    有时候我们存在这样的需求:AService初始化之前,BService必须先初始化,AService不通过属性或构造函数参数显式依赖于BService。比如:AService负责产出消息,BService负责消费消息,最理想的做法是BService必须先AService初始化,让消息能及时消费掉,而spring创建bean的顺序是不固定的,所以我们可以使用@DependsOn来要求在初始化AService之前必须先初始化BService。

    package org.example.beans;
    
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.stereotype.Component;
    
    @Component
    @DependsOn({"c"})
    public class B {
        public B() {
            System.out.println("B construct...");
        }
    }
    
    package org.example.beans;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class C {
    
        public C() {
            System.out.println("C construct...");
        }
    }
    
    package org.example.config;
    
    import org.springframework.context.annotation.ComponentScan;
    
    @ComponentScan("org.example.beans")
    public class MyConfig5 {
    }
    

      

    测试用例:

        @Test
        public void test12() {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
        }
    

      

    运行结果:

    C construct...
    B construct...
    

      

    我们在B类声明对C类的依赖,所以spring在创建bean的先创建C再创建B,大家也可以尝试下,把B类上的@DependsOn去掉,spring会先创建B再创建C。

    FactoryBean

    当创建一个bean的逻辑十分复杂,不适合以@Bean来标注方法返回时,我们可以创建一个类并实现FactoryBean接口,然后在FactoryBean的实现中编写复杂的创建逻辑。我们来看下FactoryBean接口:

    public interface FactoryBean<T> {
    	//返回工厂所创建的实例,实例可被共享,取决于这个实例是单例还是原型
    	T getObject() throws Exception;
    	//返回bean的class对象
    	Class<?> getObjectType();
    	//返回bean是否单例
    	default boolean isSingleton() {
    		return true;
    	}
    }
    

      

    我们编写一个机器人FactoryBean(RobotFactoryBean)用来产生机器人(Robot)对象:

    package org.example.beans;
    
    import org.springframework.beans.factory.FactoryBean;
    import org.springframework.stereotype.Component;
    
    @Component
    public class RobotFactoryBean implements FactoryBean<Robot> {
        @Override
        public Robot getObject() throws Exception {
            return new Robot();
        }
    
        @Override
        public Class<?> getObjectType() {
            return Robot.class;
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    }
    
    
    package org.example.beans;
    
    public class Robot {
    }
    

      

    一般我们通过beanName来获取bean时,都是通过类名来获取,但FactoryBean有点特殊,如果我们要获取Robot这个bean,需要传入它的工厂名称robotFactoryBean,如果我们需要获取工厂对象本身,则在robotFactoryBean前面加一个'&'符号。

        @Test
        public void test13() {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
            Object robot = ac.getBean("robotFactoryBean");
            System.out.println(robot);
            System.out.println(robot.getClass());
            Object factoryBean = ac.getBean("&robotFactoryBean");
            System.out.println(factoryBean);
            System.out.println(factoryBean.getClass());
        }
    

      

    运行结果:

    org.example.beans.Robot@6b1274d2
    class org.example.beans.Robot
    org.example.beans.RobotFactoryBean@7bc1a03d
    class org.example.beans.RobotFactoryBean
    

      

  • 相关阅读:
    c语言变量的交换
    牛客网 多多的电子字典
    算法笔记----背包九讲 ③多重背包问题
    统计学习方法 课后习题第五章
    2020.8.2 19:00-21:00 拼多多算法岗笔试
    python构建模块
    leetcode 剑指 Offer 51. 数组中的逆序对
    pytorch的内部计算
    matplotlib
    矩阵微积分
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/13821506.html
Copyright © 2020-2023  润新知