10. 配置元信息 Configuration Metadata

91 阅读14分钟

10. 配置元信息 Configuration Metadata

10.1 Spring 配置元信息

Spring 的配置元信息,可以大致分为 5 个类型:

  1. Bean 的配置元信息 - BeanDefinition
  2. Bean 的属性元信息 - PropertyValues
  3. 容器配置元信息
  4. 外部化配置元信息 - PropertySource
  5. Profile 元信息 - @Profile 指定活跃的配置文件

什么是外部化配置?

即不是来自程序内部的配置,包括 properties,yaml 文件中的配置,--spring.xx ,-Dspring.xx 之类的配置。

10.2 Bean 配置元信息

Bean 的配置元信息 BeanDefinition 常见的实现有以下 3 种

  1. GenericBeanDefinition:通用型 BeanDefinition,不包含 parent bean 的元信息
  2. RootBeanDefinition:无 Parent 的 BeanDefinition,或是合并后的 BeanDefinition
  3. AnnotatedBeanDefinition:注解标记的 BeanDefinition
  4. ScannedGenericBeanDefinition:@ComponentScan 扫描出来的类

image

结合 4.1 章节,9.2 章节,9.4 章节,10.7.1 章节比较这几种 BeanDefinition 的作用和区别。

配置父子 bean,在 xml 配置 bean 时可以使用parent属性指定父 bean,根据这个配置来分析各个 BeanDefinition 的不同

<bean id="user" class="org.geekbang.ioc.overview.lookup.domain.User">
    <property name="id" value="1"/>
    <property name="name" value="tracccer"/>
</bean>

<bean  class="org.geekbang.ioc.overview.lookup.domain.SuperUser" parent="user" primary="true">
    <property name="address" value="杭州"/>
</bean>

在注解配置 bean 时,java 代码中的继承 extend 也相当于指定了父 bean

  1. GenericBeanDefinition 表示 Bean 的元信息,但是不包括 Bean parent 中的元信息。上面的两个 Bean user, superUser,开始都会被解析为 GenericBeanDefinition,只不过后者设置了 parent
public class GenericBeanDefinition extends AbstractBeanDefinition {

	private String parentName;

    @Override
    public void setParentName(@Nullable String parentName) {
        this.parentName = parentName;
    }
}
  1. RootBeanDefinition 表示一个 Bean 完整的元信息,包括 parent bean 中的信息。

user 在经过 MergedBeanDefinition 操作后,直接将 GenericBeanDefinition 修改为 RootBeanDefinition

superUser 在经过 MergedBeanDefinition 操作后,会将其与 parent 中的元信息 BeanDefinition 合并,然后得到 RootBeanDefinition

public class RootBeanDefinition extends AbstractBeanDefinition {
    // 不能设置父bean的名称, 否则会抛出异常
    public void setParentName(@Nullable String parentName) {
        if (parentName != null) {
            throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
        }
    }
}
  1. AnnotatedBeanDefinition 接口的源码如下所示
public interface AnnotatedBeanDefinition extends BeanDefinition {

	// 注解元信息
	AnnotationMetadata getMetadata();
	
	// 获取factory-method方法元信息
	MethodMetadata getFactoryMethodMetadata();
}

10.3 Bean 属性元信息

  • Bean 属性元信息 - PropertyValues,其经典实现是 MutablePropertyValues
  • Bean 附加属性 - AttributeAccessor
  • Bean 的来源 - BeanMetadataElement

1. 使用示例

下面的代码演示了

  • 创建 BeanDefinition,并为其设置 beanClass,设置 bean 对象的属性
  • 为 BeanDefinition 设置附加属性 attribute
  • 为 BeanDefinition 设置来源 source
  • 创建创建容器并注册bean
  • 自定义BeanPostProcessor,应用前面设置的 attribute 和 source,当 user bean 的来源是前面指定的 source 时,将其name 属性修改为 attribute 的值小毛
  • 进行依赖查找,查看 user bean 的输出结果
public static void main(String[] args) {
    // 1.创建BeanDefinition, 为bean设置属性
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
        .genericBeanDefinition(User.class)
        .addPropertyValue("name", "tracccer")
        .getBeanDefinition();

    // 2.附加属性, 注意不是bean的属性, 不影响bean的实例化初始化
    beanDefinition.setAttribute("aaa", "小毛");
    // 3.设置当前Bean的来源为当前类, 可以是Class,也可以是Resource
    beanDefinition.setSource(ConfigurationMetadataDemo.class);

    // 4.创建容器并注册bean
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    beanFactory.registerBeanDefinition("user", beanDefinition);

    // 5.beanDefinition的Attribute属性的应用, 在bean初始化后, 对bean做一些修改
    // 应用之后输出bean的name为小毛, 之前为tracccer
    beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if ("user".equals(beanName)) {
                BeanDefinition userBd = beanFactory.getBeanDefinition(beanName);
                PropertyValue property = userBd.getPropertyValues().getPropertyValue("name");
                System.out.println("bean 修改之前的属性: name -> " + property.getValue());
                
                // 修改的bean名称必须为user,来源必须是这个类
                if (userBd.getSource().equals(ConfigurationMetadataDemo.class)) {
                    // 获取前面设置的Attribute值
                    String attr = (String) userBd.getAttribute("aaa");
                    User user = (User) bean;
                    user.setName(attr);
                    return user;
                }
                return null;
            }
            return null;
        }
    });

    // 6.依赖查找
    User user = beanFactory.getBean(User.class);
    System.out.println(user);
}

输出结果

bean 修改之前的属性: name -> tracccer
User{id=null, name='小毛'}

2. 源码分析

  1. AttributeAccessor 存储 BeanDefinition 的附加属性

前面演示代码中为 BeanDefinition 设置附加属性 attribute,就是 AttributeAccessor 接口定义的功能

public interface AttributeAccessor {

	void setAttribute(String name, @Nullable Object value);

	@Nullable
	Object getAttribute(String name);

	@Nullable
	Object removeAttribute(String name);

	String[] attributeNames();
}

保存附加属性的具体实现在 AttributeAccessorSupport 中,会将附加属性保存到一个 LinkedHashMap 集合当中

public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable {
	// 用于存储BeanDefinition的附加属性
	private final Map<String, Object> attributes = new LinkedHashMap<>();

	@Override
	public void setAttribute(String name, @Nullable Object value) {
		Assert.notNull(name, "Name must not be null");
		if (value != null) {
			this.attributes.put(name, value);
		}
		else {
			removeAttribute(name);
		}
	}
}
  1. BeanMetadataElement 存储 BeanDefinition 的来源

前面演示代码中,会对 BeanDefinition 设置来源 source,就是 BeanMetadataElement 接口定义的功能

public interface BeanMetadataElement {

	@Nullable
	default Object getSource() {
		return null;
	}
}

具体实现在 BeanMetadataAttributeAccessor 中,会将来源 source 保存到字段中

public class BeanMetadataAttributeAccessor extends AttributeAccessorSupport implements BeanMetadataElement {

    // 用于保存BeanDefinition的来源
	@Nullable
	private Object source;
    
	public void setSource(@Nullable Object source) {
		this.source = source;
	}

	@Override
	@Nullable
	public Object getSource() {
		return this.source;
	}
}
  1. PropertyValues 存储 BeanDefinition 的来源

前面演示代码中,会从 BeanDefinition 中获取属性值 propertyValue,就是 PropertyValues 接口定义的功能

public interface PropertyValues extends Iterable<PropertyValue> {
    
    // 使得PropertyValues可以使用foreach循环遍历
    default Iterator<PropertyValue> iterator() {
		return Arrays.asList(getPropertyValues()).iterator();
	}

    // 根据属性名称获取属性值
    PropertyValue getPropertyValue(String propertyName);
}

具体实现在 MutablePropertyValues 中,会将 bean 的属性值映射保存到 List<PropertyValue> 集合当中

public class MutablePropertyValues implements PropertyValues, Serializable {

    // 保存bean的属性和属性值
	private final List<PropertyValue> propertyValueList;	
	
    // 根据属性名称查找属性值
    public PropertyValue getPropertyValue(String propertyName) {
		for (PropertyValue pv : this.propertyValueList) {
			if (pv.getName().equals(propertyName)) {
				return pv;
			}
		}
		return null;
	}
    // 增加属性
    public MutablePropertyValues addPropertyValue(PropertyValue pv) {
		for (int i = 0; i < this.propertyValueList.size(); i++) {
			PropertyValue currentPv = this.propertyValueList.get(i);
			if (currentPv.getName().equals(pv.getName())) {
				pv = mergeIfRequired(pv, currentPv);
				setPropertyValueAt(pv, i);
				return this;
			}
		}
		this.propertyValueList.add(pv);
		return this;
	}

image-20210514152126841

// 补充 PropertyValues 继承了 Iterable,使用了组合模式/迭代器模式

// AttributeAccessorSupport Support的意思是? 参考 Reslover

10.4 容器配置元信息

  • Spring XML 配置元信息 - <beans > 元素相关,很少使用,熟悉即可

image-20210514160826003

  • Spring XML 配置元信息 - 应用上下文相关

image-20210514160414592

  • 解析 xml 配置的核心类 - BeanDefinitionParserDelegate

1. beans 相关配置

  1. 设置全局懒加载default-lazy-init="true"

下面的 xml 配置信息中,<import >标签引入的 bean 称为 inner beans,<beans >标签中配置的 bean 称为 outter beans。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"

       default-lazy-init="true">

    <import resource="dependcy-lookup-context.xml"/>

    <bean id="user2" class="org.geekbang.ioc.overview.lookup.domain.User">
        <property name="id" value="1"/>
        <property name="name" value="outter"/>
    </bean>
</beans>

上面配置中为将所有 bean 设置懒加载 default-lazy-init="true",然后查看 bean 的元信息,判断配置是否生效

public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean-configuration-metadata.xml");
    ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();

    // 获取inner beans, 查看是否继承了outter配置的default-lazy-init属性
    BeanDefinition bd = beanFactory.getBeanDefinition("user");
    System.out.println("inner bean 是否为懒加载: " + bd.isLazyInit());
    User user = applicationContext.getBean("user", User.class);
    System.out.println(user);

    // 获取outter beans, 查看utter配置的default-lazy-init属性是否生效
    BeanDefinition bd2 = beanFactory.getBeanDefinition("user2");
    System.out.println("outter bean 是否为懒加载: " + bd2.isLazyInit());
    User user2 = applicationContext.getBean("user2", User.class);
    System.out.println(user2);
}

输出结果如下,说明 outter 配置的全局懒加载只对 outter bean 生效

inner bean 是否为懒加载: false
User{id=1, name='tracccer'}

outter bean 是否为懒加载: true
User{id=1, name='outter'}

为什么 inner bean 没有继承,与视频中所说的不符?

  1. 设置全局 bean 合并 default-merge="default"

若 bean 的配置中设置了parent bean,会将两个 bean 的信息进行合并。

经过测试,将其设置为 false,并不会影响 bean 的合并

  1. 设置全局 bean 初始化方法 default-init-method="xxx"

bean 初始化时会调用自身创建的 xx 方法,也就是说这个方法需要在每个 bean 中都定义

2. context 相关配置

  1. 想要使用 <context: >命名空间,必须在 xml 开头引入,后面 context 相关的标签开头都必须加上 <context:xxx>,aop 也是一个命名空间,beans 是默认的命名空间,所以前面没加命名空间的xml标签,默认为 beans
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
       
    <context:annotation-config></context:annotation-config>
</beans>
BeanDefinitionParserDelegate.java

public static final String DEFAULT_LAZY_INIT_ATTRIBUTE = "default-lazy-init";

public static final String DEFAULT_MERGE_ATTRIBUTE = "default-merge";	

protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) {
    // 获取xml中配置的default-lazy-init
    String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE);
    // 如果是默认值,则继承父配置中的default-lazy-init
    if (isDefaultValue(lazyInit)) {
        lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE);
    }
    defaults.setLazyInit(lazyInit);

    String merge = root.getAttribute(DEFAULT_MERGE_ATTRIBUTE);
    if (isDefaultValue(merge)) {
        merge = (parentDefaults != null ? parentDefaults.getMerge() : FALSE_VALUE);
    }
    defaults.setMerge(merge);

    // ....
}

10.5 XML 装载 Bean 配置元信息

XML元素使用场景
<beans:beans />单个 XML 资源下的多个 Spring Beans 配置
<beans:bean />单个 Spring Bean 的配置 BeanDefinition
<beans:alias />为 Spring Bean 的配置 BeanDefinition 映射别名
<beans:import />加载外部 Spring xml 配置资源

Spring XML 资源 BeanDefinition 解析与注册

  • 核心 api - XMLBeanDefinitionReader
    • 资源 - Resource
    • 底层 - BeanDefinitionDocumentReader
      • XML 解析 - Java dom api
      • BeanDefinition 解析 - BeanDefinitionParserDelegate
      • BeanDefinition 注册 - BeanDefinitionRegistry

Spring 解析 xml 配置文件,解析并注册bean,源码如下所示

XmlBeanDefinitionReader.java
// 加载xml资源中的BeanDefinition
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    throws BeanDefinitionStoreException {

    try {
        // 1.加载xml
        Document doc = doLoadDocument(inputSource, resource);
        // 2.解析xml, 注册bean
        int count = registerBeanDefinitions(doc, resource);
        
        if (logger.isDebugEnabled()) {
            // 打印注册bean的数量
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }
    catch (SAXParseException ex) {
        // xml格式不合法
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
             "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    }
    // .....省略其他异常
}

在 xml 中可以使用 <beans profile="dev"> 设置活跃的 bean 配置文件

  <!-- 开发配置 -->
  <beans profile="dev">
    <context:property-placeholder location="classpath:config/application.properties, classpath:config/application-dev.properties"/>
    <import resource="spring-hadoop-dev.xml"/>
  </beans>
 
  <!-- 测试配置 -->
  <beans profile="test">
    <context:property-placeholder location="classpath:config/application.properties, classpath:config/application-prd.properties, classpath:config/application-test.properties"/>
    <import resource="spring-hadoop-test.xml"/>
  </beans>

解析 xml 之前首先判断一下,当前的 profile 是否生效,若不生效则退出。若 profile 生效或 profile 不存在,则正常解析 xml,注册 bean

protected void doRegisterBeanDefinitions(Element root) {

    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    // 判断xml根节点是否为默认的namespace
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        // 判断profile是否存在
        if (StringUtils.hasText(profileSpec)) {
			//....若当前profile不生效,则直接return
        }
    }
	// 解析xml前的钩子方法,空实现
    preProcessXml(root);
    // (重点)解析xml,注册bean
    parseBeanDefinitions(root, this.delegate);
    // 解析xml后的钩子方法,空实现
    postProcessXml(root);

    this.delegate = parent;
}

10.6 Properties 装载 Bean 配置元信息

10.7 Java 注解装载 Bean 配置元信息

Spring 注解场景起始版本
@Repository数据仓储模式注解2.0
@Component通用组件模式注解2.5
@Named类似于 @Component2.5
@Service服务模式注解2.5
@ControllerWeb 控制器模式注解2.5
@Configuration配置类模式注解3.0
@AutowiredBean 依赖注入2.5
@Qualifier细粒度的 @Autowired 依赖查找2.5
@Resource类似于 @Autowired2.5
@Inject类似于 @Autowired2.5
@Profile配置化条件装配3.1
@Conditional编程条件装配4.0
  • Bean 扫描处理核心类 - ClassPathScanningCandidateComponentProvider

  • Bean 依赖注入处理核心类 - AutowiredAnnotationBeanPostProcessor

  • Bean 依赖注入处理核心类 - CommonAnnotationBeanPostProcessor

1. @Component 注解扫描过程源码分析

Spring 应用上下文初始化时(注意是初始化,不是启动refresh),会扫描指定的多个包下的所有 @Component 类,源码如下所示

ClassPathBeanDefinitionScanner.java
    
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    
    // 保存扫描到的BeanDefinition
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();

    // 遍历所有需要扫描的包
    for (String basePackage : basePackages) {
        // 扫描包下所有的@Component类, 见7-2代码块
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

        // 遍历所有@Component类
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            // 为BeanDefinition设置scope
            candidate.setScope(scopeMetadata.getScopeName());
            // 为BeanDefinition生成名称,为bean首字母小写的类名
            // 见4.2.2源码分析
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

            if (candidate instanceof AbstractBeanDefinition) {
                // 为bean的属性设置默认值,包括lazy,autowireMode,initMethodName,destroyMethodName
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                // 为bean的属性设置指定的值,值根据注解@Lazy,@Primary,@DependsOn来获取
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                // BeanDefinitionHolder包含两个属性beanName和BeanDefinition
                definitionHolder =
                    AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                // 将BeanDefinitionHolder保存到Set集合中
                beanDefinitions.add(definitionHolder);

                // 注册BeanDefinition到应用上下文
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

扫描指定包下所有 Class 文件,并过滤出被 @Componet 标记的 Class,然后生成 BeanDefinition 并返回。

ClassPathScanningCandidateComponentProvider.java

// 代码块7-2  扫描指定包下的所有@Component类
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        // 获取要扫描的包路径
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        // 获取扫描路径下的所有 class 文件
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        
        // 遍历扫描到的所有class文件
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            // class文件可读
            if (resource.isReadable()) {
                try {
                    // 获取class文件的注解元信息
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

                    // 判断该class是否被@Component注解标记
                    if (isCandidateComponent(metadataReader)) {
                        // 生成BeanDefinition
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						// 设置BeanDefinition所属的资源
                        sbd.setResource(resource);
                        // 设置BeanDefinition的来源
                        sbd.setSource(resource);
                        
                        // 判断BeanDefinition是否合规, 如不能是接口抽象类等
                        if (isCandidateComponent(sbd)) {
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            // 添加到候选BeanDefinition集合中
                            candidates.add(sbd);
                        }
                        else {
                            if (debugEnabled) {
                                logger.debug("Ignored because not a concrete top-level class: " + resource);
                            }
                        }
                    }
                    else {
                        if (traceEnabled) {
                            logger.trace("Ignored because not matching any filter: " + resource);
                        }
                    }
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                        "Failed to read candidate component class: " + resource, ex);
                }
            }
            else {
                if (traceEnabled) {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

判断一个 Class 是否是我们想要扫描的 @Component bean,源码如下所示:

// 判断该class是否被@Component注解标记
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    // 遍历所有要避免扫描的注解,这里为空
    for (TypeFilter tf : this.excludeFilters) {
        // 若该类中包含指定注解, 则返回false, 表示不是候选bean
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return false;
        }
    }
    // 遍历所有要扫描的注解,这里是@Componet,@Named
    for (TypeFilter tf : this.includeFilters) {
        // 判断该类的注解是否为@Componet
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}

// 注册过滤器, 包含@Component,@ManagedBean,@Named
protected void registerDefaultFilters() {
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    }
    try {
        // JSR-330提出的由JDK实现的一套IOC注解,为了和Spring解耦,和@Inject作用一样
        this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    }
}

2. Java 注解配置的 Bean 解析与注册

  • 核心 - AnnotatedBeanDefinitionReader
    • 资源 - 被注解标记的 Class
    • 条件评估 - ConditionEvaluator
    • Bean 范围解析 - ScopeMetadataResolver
    • BeanDefinition 解析
    • BeanDefinition 处理 - AnnotationConfigUtils.processCommonDefinitionAnnotations
    • BeanDefinition 注册 - BeanDefinitionRegistry

AnnotatedBeanDefinitionReader 负责解析 Java 注解标记的 bean,有 4 个关键属性,源码如下所示:

public class AnnotatedBeanDefinitionReader {

    // bean注册中心, 也就是容器
	private final BeanDefinitionRegistry registry;

    // bean名称生成器,注解类型的bean通常不会设置名称
	private BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;

    // 类的解析方式,接口则使用动态代理,Class则使用Cglib
	private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();

    // @Conditional条件通过则注册该bean,否则跳过该bean
	private ConditionEvaluator conditionEvaluator;

加载注解配置的 bean 包含两个阶段:

  1. 解析 bean 得到 BeanDefinition
  2. 注册 BeanDefinition 到容器

源码分析,注解类型配置的 bean,会解析 bean 配置元信息得到 BeanDefinition,封装为 BeanDefinitionHolder,最后注册到容器

// 代码块1
AnnotatedBeanDefinitionReader.java

private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
                                @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
                                @Nullable BeanDefinitionCustomizer[] customizers) {
	// 1.解析bean配置得到BeanDefinition
    // 生成BeanDefinition, 设置beanClass
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    // 处理@Conditional,若条件不满足,则跳过该bean
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }
	// 设置回调, 这里为null
    abd.setInstanceSupplier(supplier);
    // 解析并设置scope
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    
    // 若未设置bean名称, 自动生成
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

    // 解析@Lazy, @Primary, @Value, @DependsOn, @Description, 保存到BeanDefinition
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    
    // 使用spring api注册bean时,可以在方法参数中手动设置 Lazy,Primary等, 这里跳过
    if (qualifiers != null) {
        for (Class<? extends Annotation> qualifier : qualifiers) {
            if (Primary.class == qualifier) {
                abd.setPrimary(true);
            }
            else if (Lazy.class == qualifier) {
                abd.setLazyInit(true);
            }
            else {
                abd.addQualifier(new AutowireCandidateQualifier(qualifier));
            }
        }
    }
    // 跳过
    if (customizers != null) {
        for (BeanDefinitionCustomizer customizer : customizers) {
            customizer.customize(abd);
        }
    }
	// 封装BeanDefinition到BeanDefinitionHolder
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    // 设置代理模式,若是类,则使用Cglib,若是接口,则使用动态代理,或者不使用代理
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    
    // 2.注册BeanDefinition到容器
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

10.8 XML 装载 IoC 容器配置元信息

image

10.9 Java 注解装载 IoC 容器配置元信息

image

image

@ImpotResource:与 xml 中 <import >作用相同,引入其他配置文件

@Import:引入普通类,将其作为 bean 注册到容器;引入配置类,将其下的 bean 注册到容器

@PropertySource:与<context:property-placeholder >作用相同,与 @Value 配合使用,使用 properties 文件中的属性

1. @Import 的使用

@Import 注解有以下几种使用方式:

  1. 引入普通类,将其作为 bean 注册到容器;需要注意的是,使用 @Import 标记的类,必须是配置类

  2. 引入配置类,从而让配置类下所有的 bean 都注册到容器。在自定义 starter 时经常使用

  3. @Import引入ImportSelector的实现类

  4. 引入 ImportBeanDefinitionRegistrar 的实现类

  5. 使用 @Import 注解引入普通类,使用 @Import 标记的类,必须是配置类

@Import(User.class)
public class ImportDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 将当前类作为配置类注册到容器
        applicationContext.register(ImportDemo.class);

        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }
}

输出结果

User{id=null, name='null'}

**使用场景:**当我们引入第三方jar包时,没法修改第三方源码,并在其类上加上@Component注解,就可以在我们自己的代码中使用@Import,将其注册到Spring容器。这个在Spring整合Mybatis时会用到。

上述例子中 User.class 就是第三方包中的类,我们可以使用 @Import 将其注册到Spring容器。

  1. 使用 @Import 注解引入配置类,使用 @Import 标记的类,必须是配置类。下面的 UserConfiguration 就是配置类
@Configuration
public class UserConfiguration {

    @Bean("user")
    public User createUser() {
        User user = new User();
        user.setId(9L);
        user.setName("Sombra");
        return user;
    }
}

使用 @Import 引入配置类,可以将配置类中定义的 bean 注册到容器

@Import(UserConfiguration.class)
public class ImportDemo2 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(ImportDemo2.class);

        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }
}

输出结果

User{id=9, name='Sombra'}
  1. 更多的 @Import 使用方式,见文章SpringBoot之@Import注解正确使用方式

2. @ImportResource 的使用

@ImportResource 引入 spring 配置文件,会将其中的bean 注册到容器

@ImportResource("classpath:/dependcy-lookup-context.xml")
public class ImportResourceDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(ImportResourceDemo.class);

        applicationContext.refresh();
        Map<String, User> userMap = applicationContext.getBeansOfType(User.class);
        for (Map.Entry<String, User> userEntry : userMap.entrySet()) {
            System.out.println(userEntry.getKey() + ": "+ userEntry.getValue());
        }
    }
}

输出结果,表示配置文件中的所有 bean 都被注册到了容器

user: User{id=1, name='tracccer'}
org.geekbang.ioc.overview.lookup.domain.SuperUser#0: User{id=1, name='tracccer, address='杭州'}

3. @PropertyResource 的使用

  1. 创建 properties 属性文件
#student.properties
stu.name=Hammond
  1. 使用 @PropertySource 引入属性文件,使用 @Value 和填充符来使用 properties 中的属性
@Configuration		// 必须的
@PropertySource("classpath:/student.properties")
public class PropertySourceDemo {

    @Value("${stu.name}")
    private String stuName;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(PropertySourceDemo.class);

        applicationContext.refresh();
        PropertySourceDemo demo = applicationContext.getBean(PropertySourceDemo.class);
        System.out.println(demo.stuName);
    }
}

输出结果

Hammond
  1. 下面是使用 xml 的方式引入外部化配置,与 @PropertySource + @Value 的作用一样
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

4. 源码分析

这种方式引入 spring 配置文件,并加载其中的 bean,原理与 10.7 章节 xml 方式装载 bean 配置元信息的方式相同,底层都是调用XmlBeanDefinitionReader#doLoadBeanDefinitions来解析 xml 并注册 bean的。具体流程如下所示:

  1. 在应用上下文启动时,invokeBeanFactoryPostProcessors会回调所有 BeanFactoryPostProcessor
  2. 这里只关注 ConfigurationClassPostProcessor,他会加载 @ImportResource 注解引入的 spring xml 配置文件
  3. 调用XmlBeanDefinitionReader#doLoadBeanDefinitions来解析 xml 并注册 bean,具体与
ConfigurationClassBeanDefinitionReader.java

private void loadBeanDefinitionsForConfigurationClass(
    ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    // 通过@Conditional注解判断该配置类是否需要加载
    if (trackedConditionEvaluator.shouldSkip(configClass)) {
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
            this.registry.removeBeanDefinition(beanName);
        }
        this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
        return;
    }

    // 将@Import引入的bean,注册到容器
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
	// 加载@ImportResource引入的xml配置文件,解析并注册bean
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 加载@Import引入的ImportBeanDefinitionRegistrar实现类
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

通过以上源码分析,我们也知道了在框架启动时,回调 BeanFactoryPostProcessor 的其中一个作用就是加载和注册配置类。这一点可以结合 Bean 实例化章节深入理解。

10.10 Extensible XML authoring 扩展 Spring XML 元素

MyBatis,Dubbo 等整合 Spring 的框架时,就需要使用 Extensible XML authoring 扩展 Spring XML 元素

// 看着挺麻烦的,用到了再学吧

10.11 Properties 装载外部化配置

  • 注解驱动 - @PropertySource
  • Api 编程 - org.springframework.core.env.PropertySource
  • xml 配置

Spring 应用上下文有多个属性源,如果属性源中存在同名的属性,则最先加载的属性源优先级最高,最常见的就是user.name属性,因为 Spring 会加载系统属性,user.name就是当前操作系统用户的名称。

下面的代码,演示了存在多个属性源时,属性优先级的问题,分别使用 @PropertySource 和 spring api 编程的方式引入了外部配置

@Configuration
@PropertySource("classpath:/student.properties")
public class PropertySourceDemo2 {

    @Bean
    public User user(@Value("${user.name}") String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(PropertySourceDemo2.class);

        // 使用spring api创建属性源
        Map<String, Object> map = new HashMap<>();
        map.put("user.name", "firstName");
        MapPropertySource mapPropertySource = new MapPropertySource("first-api-property-source", map);
        // 将该属性源添加到Spring应用上下文环境
        applicationContext.getEnvironment().getPropertySources().addFirst(mapPropertySource);

        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);

        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        System.out.println("============打印 Spring PropertySource 所有属性源==============");
        for (org.springframework.core.env.PropertySource<?> propertySource : propertySources) {
            System.out.println(propertySource);
        }
    }
}

输出结果如下,可以看到注入的user.name是我们使用 api 手动自定义的属性源 first-api-property-source。如果删除自定义的属性源,则user.name会使用 systemProperties 即系统属性中的user.name,即操作系统的当前用户名称,并不会使用 @PropertySource 引入的配置文件中的user.name,因为其优先级最低。

User{id=null, name='firstName'}
============打印 Spring PropertySource 所有属性源==============
MapPropertySource {name='first-api-property-source'}
PropertiesPropertySource {name='systemProperties'}
SystemEnvironmentPropertySource {name='systemEnvironment'}
ResourcePropertySource {name='class path resource [student.properties]'}

@Value 注解的处理过程,在前面章节已经分析过,与 @Autowired 的处理过程一样。

更多属性源的细节见 Environment 章节。

10.12 YAML 装载外部化配置

  • YamlProcessor
  • YamlMapFactoryBean
  • YamlPropertieFactoryBean

1. XML

  1. 创建 yaml 属性文件 student.yml
student:
  id: 3
  name: mei
doctor:
  id: 9
  name: anna
  1. 在 Spring 配置文件中引入 yaml 属性文件,注入的类型本身就可以是 Resource 类型,Spring 会将classpath:student.yml自动转换为 Resource 对象,这里是通过 FactoryBean 接口实例化 bean
<bean id="yamlMap" class="org.springframework.beans.factory.config.YamlMapFactoryBean">
    <property name="resources" value="classpath:student.yml"/>
</bean>
  1. 创建容器,加载 spring xml 配置文件,会将 yaml 属性文件以 bean 的方式注册到容器
public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    reader.loadBeanDefinitions("yaml-property-source-context.xml");

    Map yamlMap = beanFactory.getBean("yamlMap", Map.class);
    System.out.println(yamlMap);
}

输出结果,Spring 是将 yaml 文件转换为 map,然后以 bean 的方式注册到容器,因此我们可以通过依赖查找,获取所有 yaml 中设置的属性

{student={id=3, name=mei}, doctor={id=9, name=anna}}
  1. 源码分析,实现了 FactoryBean 和 InitializingBean 接口
public class YamlMapFactoryBean extends YamlProcessor implements FactoryBean<Map<String, Object>>, InitializingBean {

	private Resource[] resources = new Resource[0];

	private Map<String, Object> map;

    // 在bean初始化完成后回调
	@Override
	public void afterPropertiesSet() {
		if (isSingleton()) {
			this.map = createMap();
		}
	}
	// 获取bean,会将yaml中的属性解析为Map集合
	@Override
	public Map<String, Object> getObject() {
		return (this.map != null ? this.map : createMap());
	}

    // 该FactoryBean生产的bean是Map类型
	@Override
	public Class<?> getObjectType() {
		return Map.class;
	}
    
    public void setResources(Resource... resources) {
		this.resources = resources;
	}
}

2. 注解方式引入 YAML 配置

  1. 实现 PropertySourceFactory 接口,重写方法。这个方法的作用是将 yaml Resource 对象转换为外部化配置 PropertiesPropertySource
public class YamlPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
        factoryBean.setResources(resource.getResource());
        Properties yamlProperties = factoryBean.getObject();

        return new PropertiesPropertySource(name, yamlProperties);
    }
}
  1. 使用 @PropertySource 引入 yaml 配置文件,设置 factory 为 YamlPropertySourceFactory.class,表示由该类来将配置的 classpath:/student.yml解析为外部化配置
@PropertySource(name = "yamlPropertySource",
        value = "classpath:/student.yml",
        factory = YamlPropertySourceFactory.class)
public class AnnotatedYamlPropertySourceDemo {

    @Bean
    public User user(@Value("${student.name}") String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AnnotatedYamlPropertySourceDemo.class);

        applicationContext.refresh();
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
    }
}

输出结果

User{id=null, name='mei'}

// 原理并没有搞清楚, factorybean 的作用又是什么呢?

10.13 面试题

  1. Spring 内建 XML Schema 常见有哪些?

image

  1. Spring 配置元信息具体有哪些?

    答:

  2. Yaml 属性文件如何生效?

  3. Java 注解装载 IoC 容器配置元信息有几种方式?

  4. @ComponentScan 扫描过程