Spring源码解析之BeanDefinition(一)

BeanDefinition

在spring中,BeanDefinition是十分重要的概念,可以说绝大部分的bean,都是从BeanDefinition产生的。那么BeanDefinition到底是什么呢?在回答这个问题前,我们先来思考Java是如何产生一个对象的?要产生一个Java对象,最基础是是要有一个class对象,好让Java知道如何描述这个对象,这个对象有多少个字段,这个对象有什么行为,需要为这个对象开辟多大的空间。同理,spring中的bean,我们可以选择是单例还是原型?是否懒加载?实现不同的回调方法让bean在不同的生命周期里进行回调……等等,描述一个bean的性质,同样需要一个对象,也就是BeanDefinition。

我们先来看下BeanDefinition接口的概览,从下面的图可以知道,BeanDefinition继承了AttributeAccessor和BeanMetadataElement两个接口,并且BeanDefinition有众多实现,比如:AnnotatedGenericBeanDefinition、ChildBeanDefinition、ConfigurationClassBeanDefinition、GenericBeanDefinition、RootBeanDefinition、ScannedGenericBeanDefinition……,这些不同的BeanDefinition分别在不同的场景下使用。

虽然上面存在众多接口和实现,但笔者会带大家逐一认识这些接口、以及接口的作用。首先还是从我们最重要的BeanDefinition接口讲起:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
	……
	void setParentName(String parentName);

	String getParentName();

	void setBeanClassName(String beanClassName);

	String getBeanClassName();

	void setScope(String scope);

	String getScope();

	void setLazyInit(boolean lazyInit);

	boolean isLazyInit();

	void setDependsOn(String... dependsOn);

	String[] getDependsOn();

	void setAutowireCandidate(boolean autowireCandidate);

	boolean isAutowireCandidate();

	void setPrimary(boolean primary);

	boolean isPrimary();

	void setFactoryBeanName(String factoryBeanName);

	String getFactoryBeanName();

	void setFactoryMethodName(String factoryMethodName);

	String getFactoryMethodName();

	ConstructorArgumentValues getConstructorArgumentValues();

	default boolean hasConstructorArgumentValues() {
		return !getConstructorArgumentValues().isEmpty();
	}

	MutablePropertyValues getPropertyValues();

	default boolean hasPropertyValues() {
		return !getPropertyValues().isEmpty();
	}

	void setInitMethodName(String initMethodName);

	String getInitMethodName();

	void setDestroyMethodName(String destroyMethodName);

	String getDestroyMethodName();
	
	void setDescription(String description);

	String getDescription();
	
	boolean isSingleton();

	boolean isPrototype();

	boolean isAbstract();
	……
}

    

上面是BeanDefinition的主要方法,我们先挑几个易于理解的说明:

  1. setBeanClassName(String beanClassName)和getBeanClassName()用于设置和获取一个class对象,spring可以根据BeanDefinition所指向的class对象,生成一个bean。那么,是不是说所有的BeanDefinition必须要设置一个class呢?毕竟spring是根据BeanDefinition所指向的class来生成bean的,答案是:否,BeanDefinition不一定要指定一个class。至于是为什么后面我们再详细解释。
  2. setScope(String scope)和getScope()用于获取一个bean的作用域,是单例(singleton)还是原型(prototype)。
  3. setLazyInit(boolean lazyInit)和isLazyInit()用于设置和判断一个bean是否是懒加载。
  4. setDependsOn(String... dependsOn)和getDependsOn()用于设置和获取bean在初始化前的依赖项。
  5. setDescription(String description)和getDescription()用来设置和描述这个bean,在程序运行时并不会有太大的影响。
  6. isSingleton()判断这个bean是否是单例。
  7. isPrototype()判断这个bean是否是原型。

AutowireCandidate和Primary

setAutowireCandidate(boolean autowireCandidate)和isAutowireCandidate()用于设置和判断bean是否自动参与候选,在AbstractBeanDefinition抽象类中默认为true。来看下面这段代码,A1Service和A2Service分别实现了AService,然后我们在TestAService注入AService。

package org.example.service;

public interface AService {
}

package org.example.service;

import org.springframework.stereotype.Component;

@Component
public class A1Service implements AService {
}

package org.example.service;

import org.springframework.stereotype.Component;

@Component
public class A2Service implements AService {
}

package org.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TestAService {
    @Autowired
    private AService service;

    public AService getService() {
        return service;
    }
}

  

正常来说spring容器是无法启动的,因为在注入TestAService的AService时spring会检查到存在两个候选bean,从而报错。但我们可以通过修改A1Service或A2Service对应BeanDefinition的autowireCandidate属性,让容器只存在一个实现了AService候选的bean,从而完成注入。在修改BeanDefinition的属性之前,这里要介绍一个接口BeanFactoryPostProcessor:

public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

  

我们一般是通过@ComponentScan注解或XML的<context:component-scan base-package="..."/>标签来配置bean的扫描路径,在spring生成bean的时候,spring会为扫描路径下的每个有需要生成bean类生成对应的BeanDefinition,然后根据这些BeanDefinition生成对应的bean。在根据BeanDefinition生成bean的中间,会执行我们所实现的BeanFactoryPostProcessor 接口,所以我们可以在这个接口修改BeanDefinition的autowireCandidate属性。我们调用beanFactory的getBeanDefinition(String beanName)方法获取A1Service对应的BeanDefinition,然后修改autowireCandidate为false,不参与bean的候选。

package org.example.service;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class AServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a1Service");
        beanDefinition.setAutowireCandidate(false);
    }
}

  

测试用例:

    @Test
    public void test01() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
        TestAService test1Service = ac.getBean(TestAService.class);
        System.out.println(test1Service.getService().getClass());
    }

  

运行结果:

class org.example.service.A2Service

  

如果不设置两个BeanDefinition的autowireCandidate属性,我们也可以设置primary属性,setPrimary(boolean primary)和isPrimary()是用来设置和判断主要候选bean,在AbstractBeanDefinition类的实现中primary属性默认为false,如果存在多个候选bean的时候,spring会选择primary为true的bean进行注入。大家可以修改AServiceBeanFactoryPostProcessor的代码将A1Service对应的BeanDefinition的primary属性设置为true,这时候TestAService的service属性会以A1Service的bean进行注入,就不再是之前的A2Service了。

autowireCandidate和primary在XML中如下配置:

    <bean id="..." class="..." autowire-candidate="true"/>
    <bean id="..." class="..." primary="true"/>

 

注:这里不能将A1Service和A2Service对应BeanDefinition的autowireCandidate属性同时改为false,也不能将primary同时改为true,否则spring容器找不到可参与候选的bean或者找到多个主要参与候选的bean,都会报错。

BeanDefinition的继承

我们之前说过,spring容器的大部分bean都是根据BeanDefinition来生成,但BeanDefinition又不一定要指定class,这是为什么呢?因为BeanDefinition可以继承,每个BeanDefinition都有属于自己的beanName,而子BeanDefinition通过setParentName(String parentName)和getParentName()来获取父BeanDefinition的beanName。来看下面这段配置,我们配置了两个bean,abPerson的abstract属性为true,代表这个bean不需要实例化,作用域scope是原型,同时bean的内部还指定了一个属性的值,age是18。第二个bean我们指定了Person类,指定了它的父BeanDefinition为abPerson,设置了一个属性name为sam。于是abPerson的age属性,作用域都得以继承。

    <bean id="abPerson" abstract="true" scope="prototype">
        <property name="age" value="18"></property>
    </bean>
    <bean id="sam" class="org.example.beans.Person" parent="abPerson">
        <property name="name" value="Sam"></property>
    </bean>

 

注:abPerson不指定abstract属性也不会报错,但最好还是指定下,告诉spring这个不用根据这个BeanDefinition实例化bean。

Person.java

package org.example.beans;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

        

测试用例:

    @Test
    public void test02() {
        ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml");
        Person sam = cc.getBean("sam", Person.class);
        System.out.println(sam);
        System.out.println(sam.getName());
        System.out.println(sam.getAge());
        System.out.println("_____________");
        sam = cc.getBean("sam", Person.class);
        System.out.println(sam);
        System.out.println(sam.getName());
        System.out.println(sam.getAge());
    }

  

运行结果:

org.example.beans.Person@77f99a05
Sam
18
_____________
org.example.beans.Person@63440df3
Sam
18

   

可以看到,两次获取sam这个bean内存地址都不一样,age打印都是18,表明abPerson的作用域和age属性也得以继承。

我们还可以从spring容器中获取sam和abPerson对应的BeanDefinition,在获取abPerson的BeanDefinition时,我们通过samBeanDefinition的parentName来获取,并且我们打印这两个BeanDefinition的class:

    @Test
    public void test03() {
        ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml");
        BeanDefinition samBd = cc.getBeanFactory().getBeanDefinition("sam");
        BeanDefinition parentBd = cc.getBeanFactory().getBeanDefinition(samBd.getParentName());
        System.out.println("samBd parentName:" + samBd.getParentName());
        System.out.println("samBd class:" + samBd.getClass());
        System.out.println("parentBd class:" + parentBd.getClass());
    }

  

运行结果:

samBd parentName:abPerson
samBd class:class org.springframework.beans.factory.support.GenericBeanDefinition
parentBd class:class org.springframework.beans.factory.support.GenericBeanDefinition

  

可以看到samBd正确打印了parentName,且通过XML配置的bean,其BeanDefinition的实现为GenericBeanDefinition。事实上,标记了@Component、@Configuration的类都会生成不同实现的BeanDefinition,后续还会介绍。

我们注意到,BeanDefinition有个 getPropertyValues()方法,这个方法是返回我们在<bean/>标签里配置的<property/>属性,在samBd的propertyValues属性中,只有一个元素,即为<name:Sam>,而我们在abPerson配置的<age:18>并不存在,但我们获取sam这个bean的age又是有值的,之所以能完成这样的功能是因为spring有一个合并BeanDefinition的概念,合并BeanDefinition就是逐级查找父BeanDefinition的属性(property、scope、lazy)进行合并,然后spring根据这个合并完的BeanDefinition生成我们所设定的bean。

 

posted @ 2020-10-27 08:26  北洛  阅读(976)  评论(0编辑  收藏  举报