(一)源码导读-Spring容器初始化流程-XmlBeanFactory
✨这里是第七人格的博客✨小七,欢迎您的到来~✨
🍅系列专栏:Spring源码解析🍅
✈️本篇内容: 源码导读-Spring容器初始化流程-XmlBeanFactory✈️
🍱 本篇收录完整代码地址:gitee.com/diqirenge/s… 🍱
楔子
小七早在2021年的时候,就想学习输出一些关于Spring源码的文章了,然后开篇了如何下载编译Spring源码以后,就太监掉了。不要问小七为什么?问就是懒 ̄□ ̄||但是这一次,小七也要当一次真男人!
Spring 简单使用示例
随意编写一个类
public class MyBean {
private String name = "myName";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在resources下添加资源文件spring.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 id="myBean" class="com.run2code.MyBean"/>
</beans>
编写启动类
/**
* Spring 简单使用示例
* 关注公众号【奔跑的码畜】,一起进步不迷路
*
* @author 第七人格
* @date 2024/01/19
*/
public class BeanFactoryDemo {
private static final String FILE_NAME = "spring.xml";
public static void main(String[] args) {
XmlBeanFactory demo = createXmlBeanFactory(FILE_NAME);
MyBean myBean = (MyBean) demo.getBean("myBean");
System.out.println(myBean.getName());
}
public static XmlBeanFactory createXmlBeanFactory(String fileName) throws BeansException {
return new XmlBeanFactory(new ClassPathResource(fileName));
}
}
启动成功后控制台会打印出myName
myName
完整demo地址: gitee.com/diqirenge/s…
源码分析
从使用例子中我们可以知道,我们是先构建了一个XmlBeanFactory
的对象,然后调用它的getBean
方法,来获取bean的。那我们又是怎么构建出XmlBeanFactory的呢?是通过调用构造方法new ClassPathResource(fileName)
,传入文件名来构建ClassPathResource
的。
new XmlBeanFactory(new ClassPathResource(fileName));
那ClassPathResource
又是一个什么东西呢?
观察XmlBeanFactory的构造方法,我们知道ClassPathResource本质上是个Resource
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
Resource是个接口,他继承于InputStreamSource接口
public interface Resource extends InputStreamSource
而InputStreamSource很简单,他里面只有一个getInputStream()方法,返回了一个InputStream对象
InputStream getInputStream() throws IOException;
所以ClassPathResource本质上是要获取一个InputStream输入流
。
那我们还有必要在仔细研究ClassPathResource的源码吗?没有必要,因为我们的目的是探寻Spring容器的初始化流程,而不是研究资源是怎么加载的,所以抓大放小,我们只要了解他是要获取InputStream输入流就行了,接着我们返回XmlBeanFactory的构造方法。
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
进入他重载的构造方法
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
// 1、调用父类的构造方法做一些事情
super(parentBeanFactory);
// 2、根据方法名是要去载入bean定义-BeanDefinitions
this.reader.loadBeanDefinitions(resource);
}
很明显,构造方法分为两部分:
①调用父类的构造方法做一些事情
②根据方法名是要去载入bean定义-BeanDefinitions
我们先从第一步看起,看看调用父类的构造方法到底搞了什么飞机。
XmlBeanFactory→→→→→DefaultListableBeanFactory
很可惜,XmlBeanFactory的爸爸DefaultListableBeanFactory,也是直接调的他自己的爸爸
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
super(parentBeanFactory);
}
DefaultListableBeanFactory→→→→→AbstractAutowireCapableBeanFactory
public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
// 1、调用重载的构造方法
this();
// 2、从方法名推测是给父类的bean赋值
setParentBeanFactory(parentBeanFactory);
}
AbstractAutowireCapableBeanFactory的构造方法也是做了2个事情:
①调用重载的构造方法
②从方法名推测是给父类的bean赋值
我们分别瞄一眼这2个方法
1、构造方法
public AbstractAutowireCapableBeanFactory() {
// 1、调用父类构造方法
super();
// 2、调用ignoreDependencyInterface方法
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
好吧,又是两步走
①找爸爸
public AbstractBeanFactory() {
// 空的构造方法什么也没干
}
还好这一次AbstractBeanFactory是一个空的构造方法什么也没干,可以不用再找爸爸了
②调用ignoreDependencyInterface方法
这个方法的实现很简单,其实就是将类放到了Set集合中
public void ignoreDependencyInterface(Class<?> ifc) {
this.ignoredDependencyInterfaces.add(ifc);
}
结合前面的构造方法,我们可以知道这里将BeanNameAware、BeanFactoryAware、BeanClassLoaderAware都放到了这个Set中。那这个方法是干什么的呢?方法的注释已经告诉你了啊,小可爱~
忽略给定的依赖注入接口进行自动装配。 这将通常由应用程序上下文用于注册通过其他方式解析的依赖项,例如通过BeanFactoryAware的BeanFactory或通过ApplicationContextAware的ApplicationContext。 默认情况下,只忽略BeanFactoryAware接口。要忽略更多类型,请为每种类型调用此方法。
码蛋,每个中文都认识,怎么连起来就看不懂了呢o(╥﹏╥)o
这个时候我们换个思路,既然ignoredDependencyInterfaces是个Set,那么我们看看有什么地方用到了他。
protected boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) {
return (AutowireUtils.isExcludedFromDependencyCheck(pd) ||
this.ignoredDependencyTypes.contains(pd.getPropertyType()) ||
AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces));
}
还好,我们只在isExcludedFromDependencyCheck看到用到了ignoredDependencyInterfaces,并且我们只需要判断AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces)为true的情况就可以了。以下是该方法的详细注释:
// 判断给定的属性描述符(PropertyDescriptor)对应的setter方法是否在指定的接口集合中定义
public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set<Class<?>> interfaces) {
// 获取属性描述符对应的setter方法
Method setter = pd.getWriteMethod();
// 如果setter方法不为空
if (setter != null) {
// 获取setter方法所在的类
Class<?> targetClass = setter.getDeclaringClass();
// 遍历接口集合
for (Class<?> ifc : interfaces) {
// 如果接口是目标类的父接口,并且接口中包含setter方法
if (ifc.isAssignableFrom(targetClass) && ClassUtils.hasMethod(ifc, setter)) {
// 返回true,表示setter方法在接口中定义
return true;
}
}
}
// 如果没有找到匹配的接口,返回false
return false;
}
根据以上代码我们可以知道,在Spring中有一些接口的属性,比如:BeanNameAware、BeanFactoryAware、BeanClassLoaderAware等,是不可以由外部随意注入的。因为他们姓赵,天也奈何不了他们。
以下是示例代码:
①新建IgnoredDependencyInterfacesTest类,实现BeanNameAware接口
// 当实现了BeanNameAware接口时,输出的结果为:ignoredDependencyInterfacesTest
// 否则,输出的结果为:myName
public class IgnoredDependencyInterfacesTest implements BeanNameAware {
//public class IgnoredDependencyInterfacesTest {
private String beanName = "myName";
public String getBeanName() {
return beanName;
}
public void setBeanName(String name) {
this.beanName = name;
}
}
②在Spring.xml添加以下定义
<bean id="ignoredDependencyInterfacesTest" class="com.run2code.IgnoredDependencyInterfacesTest">
<property name="beanName" value="myName"/>
</bean>
③在BeanFactoryDemo中编写测试代码
XmlBeanFactory demo = createXmlBeanFactory(FILE_NAME);
IgnoredDependencyInterfacesTest myBean = (IgnoredDependencyInterfacesTest) demo.getBean("ignoredDependencyInterfacesTest");
System.out.println(myBean.getBeanName());
④测试结果
ignoredDependencyInterfacesTest
我们定义的beanName:myName没有被注入
2、setParentBeanFactory方法
@Override
public void setParentBeanFactory(@Nullable BeanFactory parentBeanFactory) {
if (this.parentBeanFactory != null && this.parentBeanFactory != parentBeanFactory) {
throw new IllegalStateException("Already associated with parent BeanFactory: " + this.parentBeanFactory);
}
if (this == parentBeanFactory) {
throw new IllegalStateException("Cannot set parent bean factory to self");
}
this.parentBeanFactory = parentBeanFactory;
}
因为我们从前面传进来的parentBeanFactory是空的,所以这块代码其实就是这样的
this.parentBeanFactory = null;
再瞟一眼parentBeanFactory,他其实就是个BeanFactory,再看看注释:父级bean工厂,用于支持bean继承。
/** Parent bean factory, for bean inheritance support. */
@Nullable
private BeanFactory parentBeanFactory;
好了,目前parentBeanFactory这玩意没有用到,查个眼,可能后面会用到。
第一条线我们分析完了,小结一下,其实调用父类的构造方法,主要就做了2件事情:
①忽略给定的依赖注入接口进行自动装配
②给父类的bean赋值
我们画一个小结的流程图如下:
再回忆一下这个构造方法
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
// 1、调用父类的构造方法做一些事情
super(parentBeanFactory);
// 2、根据方法名是要去载入bean定义-BeanDefinitions
this.reader.loadBeanDefinitions(resource);
}
接下来我们分析第二条线
this.reader.loadBeanDefinitions(resource);
首先我们可以知道reader,是一个XmlBeanDefinitionReader的对象
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
并且这个this,指代的就是XmlBeanFactory自己,因为XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory实现了BeanDefinitionRegistry接口。
我们看一下这个构造方法,他其实是直接调用的父类的构造方法
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
再观察一下AbstractBeanDefinitionReader的构造方法,其实就是初始化了一些属性,比如:registry、resourceLoader还有environment,但是我们现在还不知道这些属性有什么作用,所以先插个眼,混个眼熟。
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
然后我们继续分析调用的loadBeanDefinitions方法,可以看到loadBeanDefinitions中调用了另一个重载的方法loadBeanDefinitions,这个方法只是把入参从Resource包装成了EncodedResource
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
继续跟进代码,其实encoding、charset都是空的,也就是说默认的,我们没有为这个resource指定字符集和编码
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
继续分析loadBeanDefinitions方法,重点关注doLoadBeanDefinitions
观察doLoadBeanDefinitions后,我们可以发现,其实有2个关键点:
①doLoadDocument方法
根据 inputSource和resource创建Document
②registerBeanDefinitions方法
注册bean定义
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 根据 inputSource和resource创建Document
Document doc = doLoadDocument(inputSource, resource);
// 注册bean定义
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
// 此处省略非重点代码
...
}
首先我们来分析一下doLoadDocument方法,他最终是创建了一个Document对象,如果你有做过xml文件的解析,你一定对这个对象不陌生,你可以把它理解为一个包含了xml所有元素、属性和文本内容的接口,从概念上讲,它是文档树的根,并提供对文档数据的主要访问。说人话就是,你可以用它将student.xml和Student类相互转换。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
初略看一下DefaultDocumentLoader#loadDocument
this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware())
具体实现在DefaultDocumentLoader中,其实就是一些Document对象的组装,所以我们把重点转到参数的获取上,看这些参数都是怎么获取的,并且有什么作用。
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
接下来,我们来看看这些参数:
①inputSource是我们传进来的输入流,没什么好说的了。
②getEntityResolver(),从名字看,是去拿了解析器
,并且点进去后,一眼看不出来,他是怎么获取解析器的,所以我们先插个眼,等一下应该是要重点关注
的对象。
③this.errorHandler,点进去是一个日志处理器,不是很重要,先忽略。
private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
④getValidationModeForResource(resource),获取资源验证模式,我们稍微看一下这个方法。
protected int getValidationModeForResource(Resource resource) {
// 获取检验模式,默认就是VALIDATION_AUTO
int validationModeToUse = getValidationMode();
// 默认肯定相等,所以我们不会走这个分支
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 接下来需要看看这个方法是怎么处理的
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
很明显detectValidationMode(resource)是我们下一个需要关注的方法
点进方法后,我们简单分析一下,返回结果是VALIDATION_DTD
或者VALIDATION_XSD
,且他们受hasDoctype
方法影响
hasDoctype方法比较简单,就是判断了是否包含了DOCTYPE这个常量
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}
那么DOCTYPE是什么呢?就是一个DOCTYPE的字符
private static final String DOCTYPE = "DOCTYPE";
所以Spring是怎么验证资源模式的呢?就是通过xml文件中是否包含DOCTYPE字符串来判断的,如果包含,那么就是DTD,否则就是XSD。
⑤isNamespaceAware(),这个方法我们点进去,瞄一眼,其实很简单,默认情况下,就是fales。
大致分析了一遍后,返回我们刚刚插眼的地方,看看getEntityResolver()到底是个什么东西。
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
entityResolver是在这里赋值的,那他是ResourceEntityResolver还是DelegatingEntityResolver呢?这个取决于getResourceLoader的返回值。
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
看一看getResourceLoader,代码很简单,就是直接取的AbstractBeanDefinitionReader的resourceLoader属性
private ResourceLoader resourceLoader;
至于resourceLoader,这个玩意好像在我们前面分析reader的时候,看到过耶
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
我们去到AbstractBeanDefinitionReader的构造方法,果然是我们想要的东西
所以 getResourceLoader()得到的resourceLoader就是PathMatchingResourcePatternResolver,那么entityResolver就是ResourceEntityResolver了。
然后我们看看new ResourceEntityResolver(resourceLoader)做了些什么
public ResourceEntityResolver(ResourceLoader resourceLoader) {
super(resourceLoader.getClassLoader());
// 这里就是传递了一个对象,所以我们应该看看上面的方法,他的父类做了什么
this.resourceLoader = resourceLoader;
}
他们的父类DelegatingEntityResolver,就初始化了2个解析器对象,结合我们前面分析过的Spring是如何获取资源验证模式的,我们这里可以大胆猜测,应该是初始化的DTD和XSD的解析器。
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
this.dtdResolver = new BeansDtdResolver();
this.schemaResolver = new PluggableSchemaResolver(classLoader);
}
点进对象验证一下
①BeansDtdResolver是DTD的解析器
②PluggableSchemaResolver是XSD的解析器
至此,Document对象是怎么生成的,我们已经梳理清楚了,结下来我们来看看registerBeanDefinitions这个方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// ①创建一个BeanDefinitionDocumentReader对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// ②获取当前注册表中的Bean定义数量
int countBefore = getRegistry().getBeanDefinitionCount();
// ③使用documentReader注册Bean定义,传入doc和创建的ReaderContext对象(基于resource)
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// ④返回注册后的Bean定义数量与之前的数量之差
return getRegistry().getBeanDefinitionCount() - countBefore;
}
通过方法名我们大概能够知道这个方法做了些什么事
①创建一个BeanDefinitionDocumentReader对象 ②获取当前注册表中的Bean定义数量 ③使用documentReader注册Bean定义,传入doc和创建的ReaderContext对象(基于resource) ④返回注册后的Bean定义数量与之前的数量之差
先看第一点,传入documentReaderClass,返回BeanDefinitionDocumentReader
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
看看属性,可以确认传入的对象是DefaultBeanDefinitionDocumentReader
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
DefaultBeanDefinitionDocumentReader.class;
然后大致浏览一下BeanUtils.instantiateClass方法,我们可以知道,其实他的作用就是把传入的class,通过反射给生成对象来,所以我们这里返回的BeanDefinitionDocumentReader就是DefaultBeanDefinitionDocumentReader
接着再看第二点,获取当前注册表中的Bean定义数量,这一点已经很明确了,简单点进去看看就好。
然后在看第三点registerBeanDefinitions方法之前,我们先看看createReaderContext做了什么
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
也很简单,new了一个XmlReaderContext对象,初始化了一些我们从来没见过的参数,也不知道有啥用,所以老规矩,插个眼,混个眼熟。
然后看第三点registerBeanDefinitions,往下找我们能在DefaultBeanDefinitionDocumentReader看到registerBeanDefinitions方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
又是一个do开头的方法,经典名言:Spring中do开头的方法一般就是这个方法真正的实现
下面这3行代码异常瞩目
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
其中preProcessXml和postProcessXml,都是protected级别的空方法,也就是说都是Spring留给我们的拓展点。
那我们其实要分析的方法就是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);
}
}
首先我们来看一下,Spring是如何来判断是否是默认命名空间的,跟进delegate.isDefaultNamespace方法
public boolean isDefaultNamespace(Node node) {
return isDefaultNamespace(getNamespaceURI(node));
}
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
return !StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri);
}
最后我们知道namespaceUri为空或者和BEANS_NAMESPACE_URI相等,那么就是默认的命名空间。而BEANS_NAMESPACE_URI是个常量,值为:www.springframework.org/schema/bean… 。所以namespaceUri为空或者等于http://www.springframework.org/schema/beans ,那么就是默认的命名空间
。那么这个namespaceUri在哪里呢?这里贴出我们例子中的xml配置,读者可以自己思考一下(ps:xml的链接在idea下是可以点的哟)
默认的命名空间搞清楚了,接下来,我们继续看看他是怎么解析标签的。
①解析默认标签
parseDefaultElement(ele, delegate);
具体方法逻辑
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析 import 标签
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 解析 alias 标签
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 解析 bean 标签
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 解析 beans 标签
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
我们这里选择bean这个分支来看看,Spring是如何解析的
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析bean标签,并封装到BeanDefinitionHolder中
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
首先看看delegate.parseBeanDefinitionElement(ele)这个方法,他很简单,直接调用了parseBeanDefinitionElement(ele, null)这个方法
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
但继续跟进parseBeanDefinitionElement(ele, null),这个方法就显得稍稍有点复杂了,我为这块代码加了一些简单的注释,大家可以先看看
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 获取元素的id属性值
String id = ele.getAttribute(ID_ATTRIBUTE);
// 获取元素的name属性值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// 创建一个别名列表
List<String> aliases = new ArrayList<>();
// 如果name属性有值,将其分割成数组并添加到别名列表中
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// 将id作为beanName,如果没有指定id且别名列表不为空,则使用别名列表中的第一个作为beanName
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
// 如果没有指定containingBean,检查beanName的唯一性
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 解析Bean定义元素,返回一个AbstractBeanDefinition对象
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
// 如果beanDefinition不为空
if (beanDefinition != null) {
// 如果没有指定beanName,尝试生成一个新的beanName
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
} else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// 注册一个别名,如果可能的话,为普通的bean类名,如果生成器返回了类名加上后缀。
// 这是为了兼容Spring 1.2/2.0的向后兼容性。
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
} catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
// 将别名列表转换为数组
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 返回一个包含beanDefinition、beanName和别名数组的BeanDefinitionHolder对象
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
// 如果beanDefinition为空,返回null
return null;
}
分析下来,我们可以猜测解析bean标签的关键方法应该是这个
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
继续跟进parseBeanDefinitionElement方法,代码行数也不少,我们先通览一遍,写上注释
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
// 将beanName压入解析状态栈中
this.parseState.push(new BeanEntry(beanName));
String className = null;
// 如果元素有CLASS_ATTRIBUTE属性,则获取该属性值并去除空格
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
// 如果元素有PARENT_ATTRIBUTE属性,则获取该属性值
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// 根据className和parent创建Bean定义对象
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 解析Bean定义的属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 设置Bean定义的描述信息
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析meta元素
parseMetaElements(ele, bd);
// 解析lookup-method子元素
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析replaced-method子元素
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析constructor-args元素
parseConstructorArgElements(ele, bd);
// 解析property元素
parsePropertyElements(ele, bd);
// 解析qualifier元素
parseQualifierElements(ele, bd);
// 设置Bean定义的资源和源信息
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
// 返回Bean定义对象
return bd;
}
catch (ClassNotFoundException ex) {
// 处理类未找到异常
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
// 处理依赖的类未找到异常
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
// 处理其他异常
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
// 弹出解析状态栈
this.parseState.pop();
}
// 返回null表示解析失败
return null;
}
parseState状态,目前我们没有用到,暂时可以不管。
继续往下面看,我们发现首先要获取一个bd对象,这里是返回的是一个抽象的AbstractBeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
点进createBeanDefinition方法
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}
继续点进BeanDefinitionReaderUtils.createBeanDefinition方法
public static AbstractBeanDefinition createBeanDefinition(
@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setParentName(parentName);
if (className != null) {
if (classLoader != null) {
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}
else {
bd.setBeanClassName(className);
}
}
return bd;
}
我们可以看到最后返回的AbstractBeanDefinition,其实真正的类型是GenericBeanDefinition
我们在idea中打开GenericBeanDefinition的类图,再加深一把对他的类继承关系的印象
然后返回上层,继续分析那个有点长的parseBeanDefinitionElement方法
再继续返回上层,简单看看decorateBeanDefinitionIfRequired。我们可以知道他其实是对标签进一步的解析和校验,如果发现bean标签中还存在自定义标签,就对自定义标签进行解析。
接下来再次返回上层,看看我们的bean定义是怎么用起来的
跟进BeanDefinitionReaderUtils.registerBeanDefinition方法
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
我们发现,代码首先从BeanDefinitionHolder中拿到beanName,然后调用 registry.registerBeanDefinition,最后在对从BeanDefinitionHolder中拿到的别名列表进行处理(调用registry.registerAlias方法)。
所以我们先看看 registry.registerBeanDefinition方法是怎么实现的
// 注册Bean定义的方法
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 检查beanName是否为空,如果为空则抛出异常
Assert.hasText(beanName, "Bean name must not be empty");
// 检查beanDefinition是否为null,如果为null则抛出异常
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
// 如果beanDefinition是AbstractBeanDefinition的实例,则进行验证
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 获取已存在的Bean定义
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已存在Bean定义
if (existingDefinition != null) {
// 如果不允许覆盖Bean定义
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
} else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
// 如果现有Bean定义的角色比新定义的角色低(例如,从ROLE_APPLICATION变为ROLE_SUPPORT或ROLE_INFRASTRUCTURE)
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(existingDefinition)) {
// 如果现有Bean定义与新定义不同
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
} else {
// 如果现有Bean定义与新定义相同
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 将新的Bean定义添加到映射中
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
// 如果尚未开始创建Bean定义
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
// 无法再修改启动时集合元素(以获得稳定迭代)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
} else {
// 仍在启动注册阶段
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
// 如果已存在Bean定义或包含单例Bean,则重置Bean定义
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
} else if (isConfigurationFrozen()) {
// 清除类型缓存
clearByTypeCache();
}
}
根据以上方法我们可以知道,注册BeanDefinition,实际上就是在为一系列缓存属性赋值,而其中最值得我们关注的就是beanDefinitionMap,也就是我们常说的Spring容器,也叫Spring IOC。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
他本质上是个map,为了避免线程安全问题,Spring使用的是ConcurrentHashMap。
到这里注册BeanDefinition这条路就走完了。
至于别名的注册,因为篇幅的问题,小七这里就不带着读者去看了,如果读者感兴趣可以自己去看看。
然后看看我们刚刚分析的方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
他最后一步就是发布一个BeanDefinition已经被注册的事件通知
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
跟进fireComponentRegistered这个方法,我们其实可以发现这是一个空的实现。
最后我们完善一下我们的流程图,方便理解记忆
转载自:https://juejin.cn/post/7328012551756283915