什么是 BeanDefinition?
BeanDefinition 直译为 bean 定义,描述了一个 bean 实例具有的构造方法参数和属性值等信息。与 Java 中的 Class 类似,Class 是类文件在内存中的表现形式,BeanDefinition 是 Spring Bean 配置元信息在内存中的表现形式,各种配置元信息最后都会被转换为 BeanDefinition ,Spring 根据 BeanDefinition 实例化、初始化 bean,BeanDefinition 涉及到 Spring Bean 的整个生命周期。
BeanDefinition 的分类及使用场景
在 Spring 3 之前,我们最常用 xml 配置 Spring 中的 bean,Spring 3 及之后,注解逐渐成为使用 Spring 的主要方式。此外较少使用的 bean 配置元数据还包括 properties、groovy,我们甚至可以定义自己的 BeanDefinition。Spring 中的 BeanDefinition 主要如下图所示。
BeanDefinition 是所有 BeanDefinition 实现类的接口,其定义了基本的 BeanDefinition 信息,各实现可以包含其他不同的信息。BeanDefinition 包含的信息及含义如下所示。
属性 | 含义 | 作用 |
---|---|---|
parentName | 父 BeanDefinition 名称 | BeanDefinition 具有父子关系,子 BeanDefinition 可以继承父 BeanDefinition,并可以覆盖父 BeanDefinition 中的信息 |
beanClassName | bean 的类名 | 可能包含占位符,不一定是运行时 bean 的类型 |
scope | 作用域 | bean 的作用范围,如 singleton、prototype 及自定义的 scope 等 |
lazyInit | 延迟初始化 | 默认非延迟初始化,在容器启动后会获取非延迟初始化的单例 bean |
dependsOn | 依赖的其他 bean | 当前 bean 如果需要 实例化,则必须保证依赖的 bean 先进行实例化 |
autowireCandidate | 是否为候选对象 | 如果不为候选对象,则 autowire 时当前 bean 不会被注入 |
primary | 是否为主要的 bean | 如果为 primary,则存在多个相同类型的 bean 时,primary bean 会被优先 autowire |
factoryBeanName | 工厂 bean 名称 | 用于指定创建当前 bean 的其他 bean 实例 |
factoryMethodName | 工厂方法名称 | 用于指定创建当前 bean 的 bean 实例的方法。factoryBeanName 此时如果为空则工厂 bean 为当前 bean ,工厂方法必须为静态方法。 |
constructorArgumentValues | 构造方法参数值 | 表示用于创建 bean 的构造方法或工厂方法的参数值,Spring 会根据参数选择合适的构造方法 |
propertyValues | bean 的属性值 | bean 实例化后会根据属性值进行初始化 |
initMethodName | 初始化方法名称 | bean 实例化后会进行初始化,调用初始化方法,该方法不能包含参数 |
destroyMethodName | 销毁方法名称 | bean 实例在销毁之前调用的方法,该方法最多携带一个 boolean 类型的参数 |
role | 角色 | 表示 bean 在应用中扮演的角色 |
description | 描述 | 表示 bean 的描述信息 |
resolvableType | 可解析类型 | 表示 bean 在运行时的实际类型,包含泛型信息 |
singleton | 单例 | 表示当前 bean 是否为单例,即在某一容器内只能有一个实例 |
prototype | 原型 | 每次获取 bean 的实例,都会创建一个新的实例 |
abstract | 抽象 | 当前 BeanDefinition 是否为抽象的,如果为抽象的则不能用来实例化 bean |
resourceDescription | 资源描述 | 加载当前 BeanDefinition 的资源的描述信息 |
originatingBeanDefinition | 原始 BeanDefinition | 一个 BeanDefinition 可以作为其他 BeanDefinition 的代理,其表示代理的对象 |
AbstractBeanDefinition 是 BeanDefinition 的抽象实现,并在 BeanDefinition 接口的基础上添加了一些获取 BeanDefinition 其他信息的方法。
RootBeanDefinition 是合并后的 BeanDefinition,由于 BeanDefinition 可以具有父子关系,因此在运行时需要获取最终表示 bean 的 RootBeanDefinition ,其不能再获取父 BeanDefinition。
ChildBeanDefinition ,表示具有 父 BeanDefinition 的 BeanDefinition,使用较少。
GenericBeanDefinition,通用的 BeanDefinition,Spring 推荐编程式注册 BeanDefinition 时使用 GenericBeanDefinition,而不是使用 RootBeanDefinition。
AnnotatedBeanDefinition,注解 BeanDefinition,Spring 注解编程中使用的 BeanDefinition,包含类或方法的注解元信息。
如何创建 BeanDefinition
通常情况下,Spring 在解析不同的配置元信息时会使用不同的 BeanDefinition,我们不必进行关心。而如果我们想通过编程式向 Spring 注册 BeanDefinition ,我们则需要掌握创建 BeanDefinition 的一些技巧。具有两种创建 BeanDefinition 的方式。
- 直接通过 new 指令创建 BeanDefinition。
- 通过 BeanDefinitionBuilder 的静态方法创建 BeanDefinition ,这是建造者模式的一种实现,推荐使用这种方式创建 BeanDefinition。示例代码如下所示:
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(String.class) .setLazyInit(true).setAbstract(false).getBeanDefinition();
BeanDefinition 解析
BeanDefinition 通过 BeanDefinitionReader 解析配置元数据获取,BeanDefinitionReader 根据不同的配置元数据具有不同的实现,具体如下。
根据如上类图,可以发现,除了 BeanDefinitionReader 接口,还具有一个用于解析注解元数据的 AnnotatedBeanDefinitionReader ,这是因为 BeanDefinitionReader 解析的都是资源文件,而 AnnotatedBeanDefinitionReader 是 Spring 3 才添加的用来解析注解元数据为 BeanDefinition。
AbstractBeanDefinitionReader 是 BeanDefinitionReader 的抽象实现,主要将资源位置解析为资源 Resource。 PropertiesBeanDefinitionReader 用来解析 properties 文件,XMLBeanDefinitionReader 用来解析 xml 文件,GrovvyBeanDefinitionReader 用来解析 grovvy 文件。
目前最常用的为注解,在老旧的项目中可能存在 xml 配置文件,而 properties 和 groovy 则几乎没有用户使用。
注解元数据解析
AnnotatedBeanDefinitionReader 解析注解元数据为 BeanDefinition 的核心源码如下。
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 先进行 @Conditional 条件评估
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
// 解析 scope 信息
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
// 处理通用的注解信息,如 @Primary、@Lazy 等
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
// 处理 qualifier 信息
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));
}
}
}
// 自定义 BeanDefinition 信息
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
// 创建 BeanDefinition
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 注册 BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
可以看到,AnnotatedBeanDefinitionReader 实现就是读取类的配置信息,将其转换为对应的 BeanDefinition 属性,然后再创建 BeanDefinition 。需要注意的是,在扫描包时会使用 MetadataReader 读取类,底层使用 asm 读取类,因此不会进行类加载,会将类的元信息抽象为 AnnotationMetadata,此时直接创建 ScannedGenericBeanDefinition 对象。
xml 配置解析
xml 配置文件的解析由 XmlBeanDefinitionReader 来完成。XmlBeanDefinitionReader 加载 BeanDefinition 的入口方法如下。
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
这里正是将前面提到的 AbstractBeanDefinitionReader 解析出的资源文件作为参数,然后转换为 EncodedResource。继续跟踪源码如下。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
... 省略部分代码
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
... 省略部分代码
}
这里将 EncodedResource 转换为 InputSource ,继续跟踪源码如下。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
... 省略部分代码
}
先把 InputSource 转换为 Document,然后解析 Document 注册 bean。解析 xml 是使用 jdk org.xml.sax 进行完成,查看 doLoadDocument 方法如下。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
这里有一个 EntityResolver 需要留意,其用来获取本地的 dtd 或 xsd ,避免网络请求的开销或网络请求失败。主要的实现为 DelegatingEntityResolver,其会将 dtd 文件的获取委托为 BeansDtdResolver,将 xsd 文件的获取委托给 PluggableSchemaResolver。PluggableSchemaResolver 会从类路径下 META-INF/spring.schemas
文件读取 xsd 文件的位置。spring-beans 模块下 spring.schemas 的部分内容如下。
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool.xsd
http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util.xsd
跟踪注册 BeanDefinition 的方法如下。
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
DefaultBeanDefinitionDocumentReader.class;
// 注册 BeanDefinition
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
这里 XmlBeanDefinitionReader 创建了一个 BeanDefinitionDocumentReader,其实现为 DefaultBeanDefinitionDocumentReader ,并将 BeanDefinition 的解析注册委托为这个对象。跟踪 DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
源码,其调用方法如下。
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 如果 <beans/> 标签使用 Spring 默认的命名空间,并且指定的 profile 未被激活,则不处理
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
这里已经进入了较为核心的处理逻辑,先获取 <beans/>
标签的 profile 属性,如果未被激活则不会再处理,否则开始解析 <beans/>
标签。后面的逻辑已经比较重要,在看的朋友留意。跟踪 parseBeanDefinitions 方法。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 默认命名空间直接进行解析
parseDefaultElement(ele, delegate);
} else {
// 自定义命名空间
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
这里对<beans/>
的子标签进行解析,如果发现使用的是默认的命名空间则直接解析,否则解析自定义的命名空间。<beans/>
标签下默认命名空间的标签包括<import/>
、<alias/>
、<bean/>
、<beans/>
,按照 xml dtd 或 xsd 的定义中规中矩的解析即可,而非默认命名空间的解析则是 Spring 留给我们扩展使用。下面把重点放到自定义标签的解析,跟踪BeanDefinitionParserDelegate#parseCustomElement(Element)
方法,其又会调用如下的方法。
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 根据命名空间查询对应的处理器,然后进行处理
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
该方法根据命名空间找到相应的命名空间处理器 NamespaceHandler ,然后进行解析。先看 NamespaceHandler 的定义。
public interface NamespaceHandler {
// 初始化
void init();
// 将标签解析为 BeanDefinition
@Nullable
BeanDefinition parse(Element element, ParserContext parserContext);
// 对 BeanDefinition 再次处理,Spring 使用其解析默认命名空间下标签的非默认命名空间属性或子标签
@Nullable
BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
那么命名空间处理器是如何获取的呢?其实现源码如下。
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
// 缓存 NamespaceHandler
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
}
NamespaceHandler 是从 handlerMappings 获取的,获取到后会先调用其 init 方法进行初始化。再来看 handlerMappings 的获取方法。
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
public DefaultNamespaceHandlerResolver() {
this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}
}
这里看到,NamespaceHandler 最终是定义在 META-INF/spring.handlers
位置中,spring-beans 模块中也定义了这样的文件,用于解析命名空间c
、p
、util
。查看如下。
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
至此,Spring 默认命名空间和非默认命名空间的 xml 标签,Spring 都完美进行了解析。
自定义 xml 配置解析
根据上面的描述,如果需要解析 Spring xml 配置文件自定义的标签,我们只需要在类路径下 META-INF/spring.handlers
文件中定义自己的 NamespaceHandler 即可,事实上 Spring 提供了一个便于我们实现自己业务逻辑的 NamespaceHandlerSupport。代码较少,这里直接拿来。
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<>();
private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap<>();
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
@Override
@Nullable
public BeanDefinitionHolder decorate(
Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext);
return (decorator != null ? decorator.decorate(node, definition, parserContext) : null);
}
@Nullable
private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) {
BeanDefinitionDecorator decorator = null;
String localName = parserContext.getDelegate().getLocalName(node);
if (node instanceof Element) {
decorator = this.decorators.get(localName);
}
else if (node instanceof Attr) {
decorator = this.attributeDecorators.get(localName);
}
else {
parserContext.getReaderContext().fatal(
"Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node);
}
if (decorator == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionDecorator for " +
(node instanceof Element ? "element" : "attribute") + " [" + localName + "]", node);
}
return decorator;
}
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
this.decorators.put(elementName, dec);
}
protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) {
this.attributeDecorators.put(attrName, dec);
}
}
NamespaceHandlerSupport 将标签、属性、子标签的解析分别委托给 BeanDefinitionParser、BeanDefinitionDecorator。我们在 init 方法进行注册即可。
至此,总结 Spring 自定义 xml 配置文件标签解析的流程如下。
- 定义自己的命名空间对应的 xsd 文件,并在
META-INF/spring.schemas
文件注册,便于直接查找本地文件。 - 在 Spring xml 配置文件中指定自己的命名空间及对应的 xsd 文件路径。
- 实现 NamespaceHandlerSupport 类,在 init 方法注册自己的 BeanDefinitionParser、BeanDefinitionDecorator。
- 在类路径文件
META-INF/spring.handlers
中定义自己的 NamespaceHandler 。
BeanDefinition 注册
BeanDefinition 由 BeanDefinitionRegistry 进行注册。BeanDefinitionRegistry 定义如下。
public interface BeanDefinitionRegistry extends AliasRegistry {
// 注册 BeanDefinition
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
// 移除注册的 BeanDefinition
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 获取注册的 BeanDefinition
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 是否注册了给定名称的 BeanDefinition
boolean containsBeanDefinition(String beanName);
// 获取已经注册的 BeanDefinition
String[] getBeanDefinitionNames();
// 获取注册的 BeanDefinition 的数量
int getBeanDefinitionCount();
// 给定名称的 BeanDefinition 是否已经注册,包括别名
boolean isBeanNameInUse(String beanName);
}
BeanDefinitionRegistry 由 DefaultListableBeanFactory 进行实现,内部使用 map 保存 BeanDefinition,ApplicationContext 应用上下文的实现类中也实现了 BeanDefinitionRegistry ,其底层会委托给 DefaultListableBeanFactory 。如果我们需要注册自定义的 BeanDefinition,只需要通过 BeanFactoryAware 、@Autowire 注入、ConfigurableApplicationContext 等方法获取 DefaultListableBeanFactory 实例即可。
总结
BeanDefinition 作为 bean 定义的元数据,是理解 Spring 的基石,本篇对 BeanDefinition 的分类、解析、注册详细进行了描述,希望对大家有帮忙,欢迎留言讨论。