初学者必看的SpringBoo自动装配原理2

电子说

1.3w人已加入

描述

4. BeanDefinition结构

既然讲到了BeanDefinition,我们来看一下BeanDefinition里面究竟定义了些什么

让我们点进AbstractBeanDefinition这个类,一探究竟

哇!好多成员变量,整个人都要看晕了@_@

我们来重点关注以下三个成员:

private volatile Object beanClass;
private int autowireMode = AUTOWIRE_NO;
private ConstructorArgumentValues constructorArgumentValues;

4.1 beanClass

这个属性决定了该Bean定义的真正class到底是谁,接下来我们来做点实验

我们定义两个Bean类,A和B

@Component
public class A {
    @Value("我是AAA")
    private String name;
}
@Component
public class B {
    @Value("我是BBB")
    private String name;
}

接下来我们实现上面的BeanFactoryPostProcessor接口,来创建一个自定义的bean后置处理器

/**
 * 自定义的bean后置处理器
 * 通过这个MyBeanPostProcessor来修改bean定义的属性
 * @author dzzhyk
 */
public class MyBeanPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        GenericBeanDefinition defA = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");
        System.out.println("这里是MyBeanPostProcessor,我拿到了:" + defA.getBeanClassName());
    }
}

最后在XML配置文件中开启包扫描

<context:component-scan base-package="pojo"/>
<context:annotation-config />

注意: 这里不要使用JavaConfig类来配置bean,不然会报如下错误

ConfigurationClassBeanDefinition cannot be cast to org.springframework.beans.factory.support.GenericBeanDefinition

这个错误出自这一句:

GenericBeanDefinition defA = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");

最后,我们创建一个测试类:

public class Test {
    @org.junit.Test
    public void test(){
        ClassPathXmlApplicationContext ca = new ClassPathXmlApplicationContext("applicationContext.xml");
        A aaa = ca.getBean("a", A.class);
        System.out.println("最终拿到了==> " + aaa);
    }
}

测试运行!

这里是MyBeanPostProcessor,我拿到了:pojo.A
最终拿到了==> A(name=我是AAA, b=B(name=我是BBB))

可以看到MyBeanPostProcessor成功拿到了A的Bean定义,并且输出了提示信息

接下来让我们做点坏事

我们在MyBeanPostProcessor中修改A的Bean对象,将A的beanClass修改为B.class

System.out.println("这里是MyBeanPostProcessor,我修改了:"+ defA.getBeanClassName() + " 的class为 B.class");
// 把A的class改成B
defA.setBeanClass(B.class);

重新运行Test类,输出了一些信息后:报错了!

这里是MyBeanPostProcessor,我拿到了:pojo.A
这里是MyBeanPostProcessor,我修改了:pojo.A 的class为 B.class

BeanNotOfRequiredTypeException: 
 Bean named 'a' is expected to be of type 'pojo.A' but was actually of type 'pojo.B'

我要拿到一个A类对象,你怎么给我一个B类对象呢?这明显不对

综上所述,我们可以得出beanClass属性控制bean定义的类

4.2 autowireMode

我们继续看第二个属性:autowireMode,自动装配模式

我们在AbstractBeanDefinition源码中可以看到:

private int autowireMode = AUTOWIRE_NO;

自动装配模式默认是AUTOWIRE_NO,就是不开启自动装配

可选的常量值有以下四种:不自动装配,通过名称装配,通过类型装配,通过构造器装配

  • AUTOWIRE_NO
  • AUTOWIRE_BY_NAME
  • AUTOWIRE_BY_TYPE
  • AUTOWIRE_CONSTRUCTOR

接下来我们来模拟一个自动装配场景,仍然是A和B两个类,现在在A类中添加B类对象

@Component
public class A {
    @Value("我是AAA")
    private String name;
    @Autowired
    private B b;
}

我们希望b对象能够自动装配,于是我们给他加上了@Autowired注解,其他的完全不变,我们自定义的MyBeanPostProcessor中也不做任何操作,让我们运行测试类:

这里是MyBeanPostProcessor,我拿到了:pojo.A
最终拿到了==> A(name=我是AAA, b=B(name=我是BBB))

自动装配成功了!我们拿到的A类对象里面成功注入了B类对象b

现在问题来了,如果我把@Autowired注解去掉,自动装配会成功吗?

这里是MyBeanPostProcessor,我拿到了:pojo.A
最终拿到了==> A(name=我是AAA, b=null)

必然是不成功的

但是我就是想要不加@Autowired注解,仍然可以实现自动装配,需要怎么做?

这时就要在我们的MyBeanPostProcessor中做文章了,加入如下内容:

defA.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);

再输出结果:

这里是MyBeanPostProcessor,我拿到了:pojo.A
最终拿到了==> A(name=我是AAA, b=B(name=我是BBB))

自动装配成功了!这次我们可没加@Autowired,在我们的自定义的bean后置处理器中设置了autowireMode属性,也实现了自动装配

综上,autowireMode属性是用来控制自动装配模式的,默认值是AUTOWIRE_NO,即不自动装配

4.3 constructorArgumentValues

constructorArgumentValues的字面含义是构造器参数值

改变这个参数值,我们可以做到在实例化对象时指定特定的构造器

话不多说,show me your code:

因为要研究构造器,只能先”忍痛“关掉lombok插件,手写一个pojo.Student类

/**
 * Student类
 * @author dzzhyk
 */
@Component
public class Student {
    private String name;
    private Integer age;

    public Student() {
        System.out.println("==>使用空参构造器 Student()");
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
        System.out.println("==>使用双参数构造器 Student(String name, Integer age)");
    }
}

我们都知道,spring在实例化对象时使用的是对象的默认空参构造器:

我们新建一个测试方法test

@Test
public void test(){
    ApplicationContext ca = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = ca.getBean("stu", Student.class);
    System.out.println("==>" + student);
}

运行可以得到下面结果:

这里是MyBeanPostProcessor,我拿到了:pojo.Student
==>使用空参构造器 Student()
==>pojo.Student@402e37bc

可以看到,确实使用了空参构造器

但是如何指定(自定义)使用哪个构造器呢?我根本看不见摸不着,Spring全帮我做了,实在是太贴心了。

接下来就聊聊constructorArgumentValues的使用:

我们在MyBeanPostProcessor中加入如下内容,对获取到的pojo.Student的bean定义进行操作:

ConstructorArgumentValues args = new ConstructorArgumentValues();
args.addIndexedArgumentValue(0, "我指定的姓名");
args.addIndexedArgumentValue(1, 20);
defStu.setConstructorArgumentValues(args);

再次运行test:

这里是MyBeanPostProcessor,我拿到了:pojo.Student
==>使用双参数构造器 Student(String name, Integer age)
==>pojo.Student@2f177a4b

可以看到这次使用了双参数构造器

有人会好奇ConstructorArgumentValues到底是个什么东西,我点进源码研究一番,结果发现这个类就是一个普通的包装类,包装的对象是ValueHolder,里面一个List一个Map

而ValueHolder这个对象继承于BeanMetadataElement,就是构造器参数的一个包装类型

通过这个例子我们可以看到ConstructorArgumentValues就是用来管控构造器参数的,指定这个值会在进行bean注入的时候选择合适的构造器。

5. 装配对象

现在我们把目光放回到SpringBoot的自动装配上来,原来在真正进行bean实例化对象前,我们前面还有这些过程,尤其是存在使用后置处理器BeanFactoryPostProcessor来对bean定义进行各种自定义修改的操作。

经过上面我们漫长的研究过程,我们终于可以回答第一个问题了:

自动装配的对象:Bean定义 (BeanDefinition)

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分