日积月累,水滴石穿 😄
我们已经知道Spring
会将 bean标签
,@Bean
,@Component
等方式所定义的 Bean,最终都会被解析为 BeanDefinition
对象。那 Spring 是怎么读取 xml 配置文件或者说是怎么解析被 @Bean
等注解标注的方法或类并最终成了BeanDefinition
呢?(注解方式之后再说)
BeanDefinitionReader
BeanDefinitionReader
的作用是读取 Spring
配置文件中的内容,将之解析为BeanDefinition
并注册到 BeanDefinitionRegistry
工厂中。
接下来就一起来看看 BeanDefinitionReader
的定义
public interface BeanDefinitionReader {
//返回用于注册bean定义的bean工厂
BeanDefinitionRegistry getRegistry();
//资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
@Nullable
ResourceLoader getResourceLoader();
//返回类加载器。
@Nullable
ClassLoader getBeanClassLoader();
//BeanName生成器
//为没有明确指定bean名称的Bean生成一个名称
BeanNameGenerator getBeanNameGenerator();
//从指定的资源加载bean定义,返回找到的bean定义的数量
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
//指定多个资源加载bean定义,返回找到的bean定义的数量
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
//从指定的资源位置加载bean定义
//该位置也可以是位置模式,前提是此bean定义读取器的ResourceLoader是ResourcePatternResolver。
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
//加载多个配置文件路径
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
BeanDefinitionReader实现类
BeanDefinitionReader
是一个接口,它有多个实现类,那我们看看它一共有多少个子类吧!
可以看到 BeanDefinitionReader
下有一个抽象子类 AbstractBeanDefinitionReader
,AbstractBeanDefinitionReader
下有三个子类。
再来看一张继承关系图:
AbstractBeanDefinitionReader
:为BeanDefinitionReader
接口的抽象实现类,实现了 EnvironmentCapable,提供了获取/设置环境的方法XmlBeanDefinitionReader
:读取 XML 文件定义的BeanDefinition
PropertiesBeanDefinitionReader
:可以从属性文件,Resource,Property 对象等读取 BeanDefinitionGroovyBeanDefinitionReader
:可以读取 Groovy 语言定义的 Bean
AbstractBeanDefinitionReader
该类实现了 BeanDefinitionReader
和 EnvironmentCapable
接口的抽象类,提供属性:注册 bean 定义的bean 工厂、资源加载器、加载 bean 类的类加载器、环境、BeanName 生成器。具体定义如下:
//注册bean定义的bean工厂
private final BeanDefinitionRegistry registry;
//资源加载器
@Nullable
private ResourceLoader resourceLoader;
//加载 bean 类的类加载器
@Nullable
private ClassLoader beanClassLoader;
//环境
private Environment environment;
//BeanName生成器
private BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;
该类最核心的方法是 loadBeanDefinitions()
方法,所以接下来我们主要就是分析该方法:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
String[] var3 = locations;
int var4 = locations.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
count += this.loadBeanDefinitions(location);
}
return count;
}
当传入的参数为资源位置数组时,进入上述方法,遍历调用 loadBeanDefinitions(location)
方法。其定义如下:
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(location, (Set)null);
}
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取资源加载器
ResourceLoader resourceLoader = this.getResourceLoader();
// 判断资源加载器是否为空,为空抛出异常
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int count;
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//根据资源路径调用ResourcePatternResolver的getResources方法,此方法可以加载多个资源
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
//根据资源来加载bean定义,调用本类loadBeanDefinitions(Resource... resources)方法
count = loadBeanDefinitions(resource);
//方法参数,由上层传入
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
} catch (IOException var6) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
}
} else {
//此方法只能加载一个资源
Resource resource = resourceLoader.getResource(location);
//调用父类的loadBeanDefinitions(resources)方法,然后走不同的子类实现
count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
}
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
//循环调用父类loadBeanDefinitions(resources)方法,然后走不同的子类实现
count += loadBeanDefinitions(resource);
}
return count;
}
根据资源加载器的不同,来处理资源路径,从而返回多个或一个资源,然后再将资源作为参数传递给 loadBeanDefinitions(Resource... resources)
方法,该方法用于处理多个资源,归根结底,最后还是调用 BeanDefinitionReader#loadBeanDefinitions(resource)
方法,该方法的具体实现就交给了子类进行处理。
XmlBeanDefinitionReader
该类作为 AbstractBeanDefinitionReader
的扩展类,继承了 AbstractBeanDefinitionReader
所有的方法,同时也扩展了很多新的方法,主要用于读取 XML 文件中定义的 bean。具体使用如下:
public static void main(String[] args) {
//设置加载环境
//System.setProperty("spring.profiles.active", "dev");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 指定具体子类
AbstractBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
int i = reader.loadBeanDefinitions("spring-config.xml");
System.out.println("本次加载的Bean数量:" + i);
// 根据BeanName获取Bean
User user = (User)factory.getBean("user");
System.out.println("user" + user);
User.Vip vip = user.getVip();
System.out.println("vip:" + vip.getVipLevel());
//根据别名获取Bean
User user2 = (User)factory.getBean("user2");
System.out.println("user2:" + user2);
User user4 = (User)factory.getBean("user4");
System.out.println("user4:" + user4);
//根据bean名称获取别名数组
String[] users = factory.getAliases("user");
for (String s : users) {
System.out.println(s);
}
}
结果:====
本次加载的Bean数量:1
userUser{name='null', address='null', vip=com.gongj.bean.User$Vip@2ef1e4fa}
vip:9
user2:User{name='null', address='null', vip=com.gongj.bean.User$Vip@2ef1e4fa}
user4:User{name='null', address='null', vip=com.gongj.bean.User$Vip@2ef1e4fa}
user2
user3
user4
user7
user5
user6
- User对象
public class User {
public User() {
}
public User(String name, String address) {
this.name = name;
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", vip=" + vip +
'}';
}
private String name;
private String address;
private Vip vip;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Vip getVip() {
return vip;
}
public void setVip(Vip vip) {
this.vip = vip;
}
// 内部Bean测试
public class Vip {
public String vipLevel;
public String getVipLevel() {
return vipLevel;
}
public void setVipLevel(String vipLevel) {
this.vipLevel = vipLevel;
}
}
}
- spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--可以根据 , 、,; 、;进行分割-->
<bean class="com.gongj.bean.User" name="user2,user5;user6,;user7" id="user">
<property name="vip">
<bean id="inner" class="com.gongj.bean.User$Vip">
<constructor-arg ref="user"/>
<property name="vipLevel" value="9"/>
</bean>
</property>
</bean>
<alias name="user2" alias="user3"></alias>
<alias name="user2" alias="user4"></alias>
<!--<beans profile="dev,;prd">
<bean id="user" class="com.gongj.bean.User" name="user2,;user5"></bean>
<alias name="user2" alias="user3"></alias>
<alias name="user2" alias="user4"></alias>
</beans>-->
</beans>
- pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
接下来就开始我们的重头戏,XmlBeanDefinitionReader
读取 xml 文件配置。
从我们写的这段代码进入 reader.loadBeanDefinitions("spring-config.xml")
,上面已经提到最终会调用BeanDefinitionReader#loadBeanDefinitions(resource)
方法,该方法的具体实现就交给了子类进行处理,而我们指定的子类实现为XmlBeanDefinitionReader
,调试进入子类实现,可以看到它加载Resource
并封装为EncodedResource
,并指定编码,当然它的 encoding、 charset 都是null。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource)); >1
}
public EncodedResource(Resource resource) {
this(resource, (String)null, (Charset)null);
}
然后进入到loadBeanDefinitions(EncodedResource encodedResource)
方法,该方法主要根据encodedResource
获得InputSource
对象,并将InputSource
对象参数和Resource
对象参数传递给下层方法。
//当前正在加载的XML bean定义资源,使用ThreadLocal ,这样可以避免资源重复加载
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
new NamedThreadLocal<>("XML bean definition resources currently being loaded");
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
//获得当前正在加载的XML bean定义资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
//分配空间,并将其添加进去
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
//判断currentResources中是否包含encodedResource,如果有则抛出异常,没有则加入
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//获取Resource对应的字节流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//inputStream 流转为一个 InputSource 对象
InputSource inputSource = new InputSource(inputStream);
//如果资源有编 石马 格式,那就给 inputSource 对象也设置上编石马格式
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//该方法做进一步的解析操作,也是创建BeanDefinition的关键
return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); >1
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
//移除当前正在加载的XML bean定义资源
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
然后进入doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法
,该方法主要是将xml 资源文件转换为Document
对象并根据Dcoument
对象注册BeanDefinition
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//将资源文件解析成 Document 对象
Document doc = doLoadDocument(inputSource, resource); >1
//根据返回的Dcoument注册Bean信息
int count = registerBeanDefinitions(doc, resource); >2
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
我们先来看一下 doLoadDocument(InputSource inputSource, Resource resource)
方法:
//定义从资源文件加载 到 转换为 Document 的功能
private DocumentLoader documentLoader = new DefaultDocumentLoader();
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware()); >1
}
doLoadDocument
方法里最终调用的是 documentLoader.loadDocument ()
方法。
该方法的调用,一共需要五个参数:
- InputSource:要调用的资源文件。
- EntityResolver: 处理文件的验证方式。
- ErrorHandler: 错误处理器。
- validationMode: XML 文件的验证模式。
- namespaceAware: 是否开启自动感知名称空间。
从 documentLoader.loadDocument ()
点击会进入到 DocumentLoader
接口,该接口下只有一个实现:DefaultDocumentLoader
。
具体的调用如下:
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//创建DocumentBuilderFactory对象
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); >1
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//创建DocumentBuilder对象
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); >2
//将inputSource解析为Document对象
return builder.parse(inputSource);
}
这里就会将 XML 里的配置解析为 Document
对象。使用这个Document
对象可以获取 XML 文件中的节点并且创建节点。
createDocumentBuilderFactory(validationMode, namespaceAware)
:创建DocumentBuilderFactory
对象
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
// 1、获取DocumentBuilderFactory实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
// 2、如果开启xml验证的话,则验证xml
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
// 如果xml验证模式为XSD则需要强制指定由此代码生成的解析器将提供对XML名称空间的支持
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
}
catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
createDocumentBuilder(factory, entityResolver, errorHandler)
:创建DocumentBuilder
对象
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
// 创建DocumentBuilder对象
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// 如果entityResolver不为空,则给docBuilder设置entityResolver
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
// 如果errorHandler不为空,则给docBuilder设置errorHandler
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
接下来我们看registerBeanDefinitions(doc, resource)
方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 创建BeanDefinitionDocumentReader对象,完成 BeanDefinition 的解析和注册
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//之前的bd数量
int countBefore = getRegistry().getBeanDefinitionCount();
// 注册bd
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); >1
// 本次注册的bd数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
这个方法中重点关注documentReader.registerBeanDefinitions(doc, createReaderContext(resource))
方法。点击进入就会到 BeanDefinitionDocumentReader
接口,该接口的作用就是定义读取 Docuemnt
并注册 BeanDefinition
。
该接口就一个实现类 DefaultBeanDefinitionDocumentReader
,接下来就看 DefaultBeanDefinitionDocumentReader
类中的 registerBeanDefinitions
方法。
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement()); >1
}
该方法有两个入参:
- Document:代指 Spring 的配置文件信息,通过
BeanDefinitionReader
解析Resrouce
实例得到。 - XmlReaderContext :主要包含了
BeanDefinitionReader
和Resrouce
然后进入doRegisterBeanDefinitions(doc.getDocumentElement())
方法,进行解析
protected void doRegisterBeanDefinitions(Element root) {
// 创建 delegate 对象,解析 Element 的各种方法
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// 验证 XML 文件的命名空间,
// 即判断是否含有 xmlns="http://www.springframework.org/schema/beans"
if (this.delegate.isDefaultNamespace(root)) {
// 获取profile属性的值 <beans profile="dev,;prd"></beans>
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
//将字符串按照指定的字符转换成String[]数组,如字符串中不包含指定字符,则将整个字符串放进数组。
//如指定字符有多个,是分别按单个字符来切割的。
//字符串: "gong-jie/yuan"
// *
// * 指定字符: "-/"
// * 返回数组:[gong, jie, yuan]
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 判断你指定的环境是否与加载的配置文件的环境一致,如果不一致直接返回
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 前置空方法
preProcessXml(root);
// 解析方法
parseBeanDefinitions(root, this.delegate); >1
// 后置空方法
postProcessXml(root);
this.delegate = parent;
}
我们来看其中最重要的方法 parseBeanDefinitions(root, this.delegate);
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//是否是DefaultNamespace
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;
// 如果符合Spring的命名规则,对该标签进行解析。
// 实例 <bean id="user" class="com.gongj.bean.User"></bean>
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate); >1
}
else {
// 解析用户自定义的规则
// <tx:annotation-driven/>
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 解析用户自定义的规则
delegate.parseCustomElement(root);
}
}
而两种方式的读取及解析差别是非常大的,如果采用 Spring 默认的配置。Spring 当然知道该怎么做,但是如果是自定义的,那么就需要用户实现一些接口及配置,对于根节点或者子节点如果是默认命名空间的话,采用 parseDefaultElement
方法进行解析,否则使用delegate.parseCustomElement
方法对自定义命名空间进行解析。 而判断是否是默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceURI
获取命名空间,并与 Spring 中固定的命名空间http://www.springframework.org/scherna/beans
进行比对,如果一致则认为是默认,否则就认为是自定义。
默认标签的解析是在 parseDefaultElement(ele, delegate)
函数进行的, 函数中的功能一目了然 ,分别对四种标签(import,alias,bean,beans)做了不同的处理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析import标签 <import resource="spring-config.xml"></import>
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 解析alias标签 <alias name="user" alias="user2"></alias>
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 解析bean标签 <bean id="user" class="com.gongj.bean.User"></bean>
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate); >1
}
// 解析beans标签
/**
* <beans profile="dev">
* <bean id="user" class="com.gongj.bean.User"></bean>
* <import resource="spring-config.xml"></import>
* <alias name="user" alias="user2"></alias>
* </beans>
*/
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
//调用doRegisterBeanDefinitions,再次重复解析xml的过程
doRegisterBeanDefinitions(ele);
}
}
BeanDefinitionReader
这个源码阅读就到这啦!下面将拆分几篇博文介绍各种标签的解析。
参考文献
Spring IoC之BeanDefinitionReader