ImportBeanDefinitionRegistrar

HeJin大约 2 分钟源码解析bean的注入

应用场景

  • 如果要实现动态bean的装载可以使用ImportBeanDefinitionRegistrar。尤其是如果想装载动态代理对象的时候,例如Mybatis的启动器就是使用了它实现了Mapper接口的代理对象装载的。
  • 为什么不能使用@Import注解引入配置类装载动态代理对象呢?因为@Import注解需要指定具体的配置类,然后再类中使用@Bean返回注册对象。这个配置类我们也无法知道,new的bean对象也无法知道。
  • 为什么不能使用ImportSelector装载动态代理对象呢?因为动态代理对象是运行时动态生成的,我们无法在编写代码的时候知道全类名。不同的类会生成不同的动态代理对象,无法把全类名提前写入到配置文件中。所以就不能使用ImportSelector装载bean了。最根本的原因就是无法在编码阶段获得动态代理对象的类信息。

简单使用

Dog类:

public class Dog {
    private String name;

    private int age;

    public Dog() {
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

实现ImportBeanDefinitionRegistrar接口:

public class SimpleRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 创建BeanDefinition
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName("com.registrar.Dog");
        beanDefinition.setBeanClass(Dog.class);
        ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
        constructorArgumentValues.addIndexedArgumentValue(0, "大黄");
        constructorArgumentValues.addIndexedArgumentValue(1, 2);
        beanDefinition.setConstructorArgumentValues(constructorArgumentValues);

        // 注册BeanDefinition
        registry.registerBeanDefinition("dog", beanDefinition);
    }
}

在Spring创建bean的源码探究中,我们发现bean是根据BeanDefinition来创建的。在这里我们可以创建简单的BeanDefinition对象,给Dog对象传入了构造参数。

主启动类导入SimpleRegistrar

@SpringBootApplication
@Import(SimpleRegistrar.class)
public class SpringBootAnalysisApp {
    private static final Logger logger = LoggerFactory.getLogger(SpringBootAnalysisApp.class);

    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(SpringBootAnalysisApp.class, args);
        Dog dog = applicationContext.getBean(Dog.class);
        logger.info("dog: {}", dog);
    }
}

结果:

com.sanfen.SpringBootAnalysisApp         : dog: Dog{name='大黄', age=2}

发现我们在Spring容器中获取到了Dog对象,并且就是我们传入的构造属性。

分析一下:

  • 在这里我们无须知道Dog的全类名,是通过构造BeanDefinition对象来创建bean的。
  • 如果是动态代理对象,我们只需要构建BeanDefinition对象,就可以把对象注册到容器中了。使用的时候直接利用Java的多态,使用接口类型的变量指向代理对象,就可以获取到容器中的动态生成的代理对象了。