BeanDefinition是Spring中很重要的一个部分,通过加载bean的配置的,把bean的配置信息转换为BeanDefinition对象,这个对象中就是一个对bean的描述相关信息。beanFactory就可以按照beanDefinition的描述对bean进行创建,beanDefinition 存在于beanFactory工厂中。
XmlBeanFactory是读取Spring配置文件的入口类,我们通过这个类作为入口,一层一层的扒开如何加载bean的配置信息
public class XmlBeanFactory extends DefaultListableBeanFactory {// XmlBeanDefinitionReaer 作为bean的private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);public XmlBeanFactory(Resource resource) throws BeansException {// 调用内部的构造方法this(resource, null);}public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {// 忽略给定接口的自动装配super(parentBeanFactory);// 加载配置核心代码// 传入xml配置文件路径this.reader.loadBeanDefinitions(resource);}}
1. 加载Xml文件
加载指定的xml文件,通过构造函数指定配置文件
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
1.1. 配置文件封装
Spring 的配置文件读取是通过ClassPathResource 进行封装的,
2. 资源加载
资源文件的加载是通过XmlBeanDefinitionReader类来实现的,在这个类中有一个loadBeanDefinition()方法。
首先对资源文件使用EncodeResource进行封装
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {// 读取配置文件// 在读取配置文件前,将配置文件对象封装成EncodedResource对象,目的是当设置了编码时,Spring会使用相应的编码作为配置文件输入流的编码return loadBeanDefinitions(new EncodedResource(resource));}
使用EncodeResource封装资源文件的目的是对资源文件进行编码处理,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。当获取输入流时,会根据编码返回输入流。
public Reader getReader() throws IOException {if (this.charset != null) {return new InputStreamReader(this.resource.getInputStream(), this.charset);}else if (this.encoding != null) {return new InputStreamReader(this.resource.getInputStream(), this.encoding);}else {return new InputStreamReader(this.resource.getInputStream());}}
对资源文件Resource封装后,进入文件加载的核心方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(encodedResource, "EncodedResource must not be null");if (logger.isInfoEnabled()) {logger.info("Loading XML bean definitions from " + encodedResource);}// 记录已经加载过的资源文件(配置文件)Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {currentResources = new HashSet<>(4);this.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");}try {// 从EncodedResource对象中获取已经封装的Resource对象并再次从Resource中获取InputStream资源文件输入流InputStream inputStream = encodedResource.getResource().getInputStream();try {// 将资源文件输入流封装到InputSource对象InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}// 读取读取资源文件的核心方法return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {// 关闭输入流inputStream.close();}}catch (IOException ex) {throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}}
加载xml文件
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 加载XML文件,得到对应的DocumentDocument doc = doLoadDocument(inputSource, resource);// 根据Document注册Bean信息return registerBeanDefinitions(doc, resource);}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);}}
3. 获取XML的验证模式
在了解XML文件的验证模式之前,先介绍下什么是XML的验证模式。XML的验证模式保证了XML的正确性,比较常见的验证模式有两种:DTD和XSD
3.1. DTD验证模式
DTD(Document Type Definition)文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种能保证XML文档格式正确的有效方法,可以对比XML文档和DTD文件来看文档是否规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间的关系定义规则,元素可使用的属性,可使用的实体或符号规则。
3.2. XSD验证模式
XML Schema 语言就是XSD(XML Schemas Definition)。XSD 描述了XML文档的结构,可以用一个XSD来验证某个XML文档,以检查XML文档是否符合要求。文档设计者可以根据XSD来指定XML文档所允许的结构和内容,并可检查XML文档是否有效。XSD 本身就是XML文档,它符合XML语法结构,可以用通用的XML解析器来解析。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());}
3.3. 验证模式获取
Spring 在加载xml文档之前会先获取xml文档的验证模式,这个验证模式就是通过getValidationModeForResource()方法来获取对应资源的验证模式
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());}
分析getValidationModeForResource()方法是如何来获取xml的验证模式的。
protected int getValidationModeForResource(Resource resource) {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;}
getValidationModeForResource()方法很简单,里面的逻辑如果指定了验证模式,就使用指定的验证模式。如果没有指定验证模式,就自动是被验证模式。手动指定验证模式可以通过XmlBeanDefinitionReader类中的setValidationMode 来设置。
3.3.1. 手动设置验证模式
// 伪代码XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();reader.setValidationMode(XmlValidationModeDetector.VALIDATION_NONE);
3.3.2. 自动识别验证模式
自动识别验证模式的逻辑由detectValidationMode()方法完成,然后交由XmlValidationModeDetector类的detectValidationMode()方法识别验证模式
protected int detectValidationMode(Resource resource) {if (resource.isOpen()) {throw new BeanDefinitionStoreException("Passed-in Resource [" + resource + "] contains an open stream: " +"cannot determine validation mode automatically. Either pass in a Resource " +"that is able to create fresh streams, or explicitly specify the validationMode " +"on your XmlBeanDefinitionReader instance.");}InputStream inputStream;try {inputStream = resource.getInputStream();}catch (IOException ex) {throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +"Did you attempt to load directly from a SAX InputSource without specifying the " +"validationMode on your XmlBeanDefinitionReader instance?", ex);}try {// 由XmlValidationModeDetector 识别验证模式return this.validationModeDetector.detectValidationMode(inputStream);}catch (IOException ex) {throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +resource + "]: an error occurred whilst reading from the InputStream.", ex);}}
XmlValidationModeDetector类的detectValidationMode()方法
public int detectValidationMode(InputStream inputStream) throws IOException {// Peek into the file to look for DOCTYPE.BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));try {boolean isDtdValidated = false;String content;while ((content = reader.readLine()) != null) {// 读取行content = consumeCommentTokens(content);// 如果读取的是空行或者注释行略过if (this.inComment || !StringUtils.hasText(content)) {continue;}if (hasDoctype(content)) {isDtdValidated = true;break;}// 读取到<开始符号,验证模式一定会在开始符号之前if (hasOpeningTag(content)) {// End of meaningful data...break;}}// 返回验证模式return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);}catch (CharConversionException ex) {// 由调用方决定用哪种验证模式// 如果获取验证模式异常return VALIDATION_AUTO;}finally {reader.close();}}
4. 获取Document
经过xml文档验证模式的获取工作后,接下来就是加载Document了。读取文档XmlBeanFactoryReader类会委托给DocumentLoader接口去执行,默认实现类是DefaultDocumentLoader
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {// 创建DocumentBuilderFactoryDocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);if (logger.isTraceEnabled()) {logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");}// 创建DocumentBuilderDocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);// 获取Documentreturn builder.parse(inputSource);}
4.1. 创建DocumentBuilderFactory
创建DocumentBuilderFactory 在Spring中没有什么特殊的地方,仅创建一个工厂对象
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)throws ParserConfigurationException {// 实例化DocumentBulderFactory 对象DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();factory.setNamespaceAware(namespaceAware);if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {factory.setValidating(true);// xml文档校验类型是XSDif (validationMode == XmlValidationModeDetector.VALIDATION_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;}
4.2. 创建DocumentBuilder
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)throws ParserConfigurationException {// 实例化DocumentBuilder 对象DocumentBuilder docBuilder = factory.newDocumentBuilder();// 设置EntityResolverif (entityResolver != null) {docBuilder.setEntityResolver(entityResolver);}// 异常处理Handerif (errorHandler != null) {docBuilder.setErrorHandler(errorHandler);}return docBuilder;}
4.3. 获取EntityResolver
在4.2中创建DocumentBuilder时,会给它赋值EntityResolver。它的获取方式很简单,是在XmlBeanDefinitionReader类中调用DefaultDocumentLoader类时传入的。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现如何寻找DTD声明的过程,比如我们将DTD放在项目的某个位置,在实现时直接将此文档读取并返回给SAX即可,这样避免了通过网络来寻找相应的声明。
// XmlBeanDefinitionReader类中获取EntityResolver的方法protected EntityResolver getEntityResolver() {if (this.entityResolver == null) {ResourceLoader resourceLoader = getResourceLoader();// 指定EntityResolverif (resourceLoader != null) {this.entityResolver = new ResourceEntityResolver(resourceLoader);}else {// 默认EntityResolver实现类this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());}}return this.entityResolver;}
4.3.1. 默认实现DelegatingEntityResolver
DelegatingEntityResolver 作为EntityResolver接口的默认实现,在它的构造方法中创建了两个EntityResolver对象
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {// DTD 解析对象this.dtdResolver = new BeansDtdResolver();// 从/META-INF/spring.schemas解析this.schemaResolver = new PluggableSchemaResolver(classLoader);}
DelegatingEntityResolver 类中解析方法,逻辑很简单就是决定采用哪中解析方式来解析
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)throws SAXException, IOException {if (systemId != null) {// DTD解析if (systemId.endsWith(DTD_SUFFIX)) {return this.dtdResolver.resolveEntity(publicId, systemId);}// META-INF/spring.schemas解析else if (systemId.endsWith(XSD_SUFFIX)) {return this.schemaResolver.resolveEntity(publicId, systemId);}}// Fall back to the parser's default behavior.return null;}
5. 解析及注册BeanDefinition
在获取了Document后,接下来就是解析Document提取及注册bean。回到第2节中,在doLoadBeanDefinitions()方法中调用完获取Document方法后,仅接着就是调用registerBeanDefinitions()方法来注册bean
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 实例化DefaultBeanDefinitionDocumentReader 对象// DefaultBeanDefinitionDocumentReader 是BeanDefinitionDocumentReader 接口的实现BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 记录统计前的BeanDefinition 加载个数int countBefore = getRegistry().getBeanDefinitionCount();// 加载及注册beandocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 本次加载的BeanDefinition 加载个数return getRegistry().getBeanDefinitionCount() - countBefore;}
registerBeanDefinitions()方法本身没有加载及注册bean方法中,只提取Document的root,具体的注册交给doRegisterBeanDefinitions()方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;// 提取root// 将注册工作交给doRegisterBeanDefinitions()方法doRegisterBeanDefinitions(doc.getDocumentElement());}
接着,我们看doRegisterBeanDefinitions()方法是如何真正的解析XML文件的
protected void doRegisterBeanDefinitions(Element root) {// xml文件解析器BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);if (this.delegate.isDefaultNamespace(root)) {// 处理profile属性String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);// We cannot use Profiles.of(...) since profile expressions are not supported// in XML config. See SPR-12458 for details.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);// 解析后处理,留给子类实现postProcessXml(root);this.delegate = parent;}
5.1. profile 属性的使用
在注册bean时,最开始解析的是对PROFILE_ATTRIBUTE解析,关于profile属性的作用是用来区分环境的,比如:dev、sit、sandbox等环境
有了profile这个属性,我们就可以同时在配置文件中部署多套配置,来适应不同的环境,如开发环境和生产环境。方便我们在开发、部署时的切换
5.2. 解析并注册BeanDefinition
处理完profile属性后,就可以进行XML文件的解析,parseBeanDefinitions()是我们解析XML文件的方法
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 判断是否有默认命名空间// 判断是否默认命名空间还是自定义命名空间的办法,如果和http://www.springframework.org/schema/beans相等就表示默认命名空间,否则是自定义命名空间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);}}
6. 默认标签的解析
在Spring中有默认标签和自定义标签两种,两种标签的解析存在这很大的不同。接下来我们先看默认标签是如何解析的。在5.2中默认标签的解析会交给parseDefaultElement()方法来处理,可以发现会解析4种标签,分别有不同的处理方式
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)) {// recursedoRegisterBeanDefinitions(ele);}}
6.1. import标签的解析
import标签的作用是在xml文件中引入其他的xml文件,这在我们分模块的开发当中是很常见的,将多个模块的bean按模块配置在不同的xml文件中,然后通过import引入这些xml文件。import 标签的解析由importBeanDefinitionResource()方法来完成
protected void importBeanDefinitionResource(Element ele) {// 获取resource属性String location = ele.getAttribute(RESOURCE_ATTRIBUTE);// 如果resource属性不存在,不做任何处理if (!StringUtils.hasText(location)) {getReaderContext().error("Resource location must not be empty", ele);return;}// 解析系统属性,如:"${user.dir}"location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);Set<Resource> actualResources = new LinkedHashSet<>(4);// 校验location是绝对URI还是相对URIboolean absoluteLocation = false;try {absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();}catch (URISyntaxException ex) {// cannot convert to an URI, considering the location relative// unless it is the well-known Spring prefix "classpath*:"}// 如果是绝对URI就直接根据地址加载对应的配置文件if (absoluteLocation) {try {int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);if (logger.isTraceEnabled()) {logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");}}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, ex);}}else {// 相对路径根据相对地址计算出相对地址try {int importCount;//Resource存在多个子实现时,如VfsResource、FileSystemResource等// 而每个resource的createRelative 的实现方式是不一样的,这里先使用子类的方法尝试解析Resource relativeResource = getReaderContext().getResource().createRelative(location);if (relativeResource.exists()) {importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);actualResources.add(relativeResource);}else {// 如果解析不成功,使用默认解析器DefaultNamespaceHandlerResolver进行解析String baseLocation = getReaderContext().getResource().getURL().toString();importCount = getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);}if (logger.isTraceEnabled()) {logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");}}catch (IOException ex) {getReaderContext().error("Failed to resolve current resource location", ele, ex);}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex);}}// 解析后进行监听器激活处理Resource[] actResArray = actualResources.toArray(new Resource[0]);getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));}
6.2. alias标签解析
我们在xml文档中对bean的定义除了用id外,还可以使用alias。alias的作用是为bean提供别名,这样就相当于我们为一个bean定义了多个名称,在Spring容器创建完这个bean后,我们使用其中的任何一个名称就可以获取到该bean
protected void processAliasRegistration(Element ele) {// 获取bean标签中的beanNameString name = ele.getAttribute(NAME_ATTRIBUTE);// 获取bean标签中的aliasString alias = ele.getAttribute(ALIAS_ATTRIBUTE);boolean valid = true;if (!StringUtils.hasText(name)) {getReaderContext().error("Name must not be empty", ele);valid = false;}if (!StringUtils.hasText(alias)) {getReaderContext().error("Alias must not be empty", ele);valid = false;}if (valid) {try {// 为bean注册aliasgetReaderContext().getRegistry().registerAlias(name, alias);}catch (Exception ex) {getReaderContext().error("Failed to register alias '" + alias +"' for bean with name '" + name + "'", ele, ex);}// 别名注册后,通知监听器做相应处理getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));}}
6.3. bean标签的解析及注册
processBeanDefinition()方法只干了4件事情
- 解析默认标签,交给BeanDefinitionParserDelegate类的parseBeanDefinitionElement()方法去解析
- 解析默认标签子节点下的自定义标签,交给BeanDefinitionParserDelegate类的decorateBeanDefinitionIfRequired()方法去解析
- 注册BeanDefinition,交给BeanDefinitionReaderUtils类的registerBeanDefinition()方法完成
- 发送注册事件,通知相关监听器
这几件事情都不是由它自己本身完成,而是都交给其他类来完成
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 委托BeanDefinitionParserDelegate类进行元素解析,返回BeanDefinitionHolder// BeanDefinitionHolder中已经包含了我们在bean标签中定义的id、class、name、alias之类的属性BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 解析默认标签的子节点下的自定义属性bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// Register the final decorated instance.// 注册BeanDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// 发送注册事件,通知相关监听器getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}
6.3.1. 解析BeanDefinition
我们接着看BeanDefinitionParserDelegate类的parseBeanDefinitionElement()方法,是如何解析bean标签
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return 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);// 分割name属性List<String> aliases = new ArrayList<>();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}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");}}if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}// 对其他标签解析,返回GenerBeanDefinition对象AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {if (!StringUtils.hasText(beanName)) {try {// 如果beanName不存在,那么根据Spring中提供的命名规则为当前bean生成beanNameif (containingBean != null) {beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {beanName = this.readerContext.generateBeanName(beanDefinition);// Register an alias for the plain bean class name, if still possible,// if the generator returned the class name plus a suffix.// This is expected for Spring 1.2/2.0 backwards compatibility.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);// 将解析后的bean信息封装到BeanDefinitionHolder中return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;}
对其他标签解析,统一封装成GenericBeanDefinition对象
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));String className = null;// 解析class属性if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;// 解析parent属性if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}try {// 创建用于用于承载属性AbstractBeanDefinition类型的GenericBeanDefinition对象AbstractBeanDefinition bd = createBeanDefinition(className, parent);// 硬编码解析默认bean的各种属性parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);// 提取descriptionbd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));// 解析元数据parseMetaElements(ele, bd);// 解析lookup-method属性parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析replaced-method属性parseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析构造函数参数parseConstructorArgElements(ele, bd);// 解析property子元素parsePropertyElements(ele, bd);// 解析qualifier 子元素parseQualifierElements(ele, bd);bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));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();}return null;}
6.3.2. AbstractBeanDefinition属性
对其他标签解析完后,我们就已经完成了XML文档到GenericBeanDefinition的转换,也就是说到这里xml中的所有配置都可以在GenericBeanDefinition 实例中找到对应的属性
GenericBeanDefinition 只是子类实现,大部分的通用属性都保存在了AbstractBeanDefinition中。我们可以通过查看xml中bean的配置属性和AbstractBeanDefinition类中的属性做对比知道属性的含义。
6.3.3. 解析默认标签中的自定义标签元素
回到6.3中,在调用解析默认标签方法后,仅接着就是解析默认标签中的自定义标签。默认标签中的自定义标签是由BeanDefinitionParserDelegate类的decorateBeanDefinitionIfRequired()方法来完成
// 解析标签方法protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 解析默认标签,也就是6.3.1中分析的BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 解析默认标签中的自定义标签// 是我们这里要介绍的bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 注册BeanDefinitionBeanDefinitionReaderUtils.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));}}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) {return decorateBeanDefinitionIfRequired(ele, originalDef, null);}public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {BeanDefinitionHolder finalDefinition = originalDef;// 遍历所有属性,看看是否由适用于修饰的属性NamedNodeMap attributes = ele.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {Node node = attributes.item(i);finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);}// Decorate based on custom nested elements.// 遍历所有子节点,看看是否有适用于修饰的子元素NodeList children = ele.getChildNodes();for (int i = 0; i < children.getLength(); i++) {Node node = children.item(i);if (node.getNodeType() == Node.ELEMENT_NODE) {finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);}}return finalDefinition;}
上面的代码中对元素的所有属性以及子节点进行分别调用,都通过decorateIfRequired()方法解析。首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所对应的NamespaceHandler进行进一步解析
public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {// 自定义标签的命名空间String namespaceUri = getNamespaceURI(node);// isDefaultNamespace(namespaceUri) 对非默认标签进行修饰if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {// 根据命名空间找到对应的处理器NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler != null) {// 修饰BeanDefinitionHolder decorated =handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));if (decorated != null) {return decorated;}}else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);}else {// A custom namespace, not to be handled by Spring - maybe "xml:...".if (logger.isDebugEnabled()) {logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");}}}return originalDef;}
6.3.4. 注册解析的BeanDefinition
对bean解析后得到BeanDefinition后,我们需要将它注册到BeanFactory中,通过processBeanDefinition()方法中调用BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())方法进行注册。我们看registerBeanDefinition()方法的代码,对beanDefinition的注册分成根据beanName和alias注册
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// 使用beanName作为唯一key,把BeanDefinition注册到beanFactory中String beanName = definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// 如果存在别名,注册别名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}}
6.3.4.1. 通过beanName注册BeanDefinition
注册bean的本质其实就是将bean的BeanDefinition信息加入到map缓存中,beanName作为map的key,BeanDefinition作为map的value
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");// 注册前的最后一次校验,这里的校验不同于之前的XML文件校验// 主要是对于AbstractBeanDefinition属性中的methodOverrides校验// 校验methodOverrides是否与工厂方法并存或者methodOverrides对应的方法根本不存在if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}// 获取bean的定义信息BeanDefinitionBeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);// beanName已经被注册if (existingDefinition != null) {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_INFRASTRUCTUREif (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)) {if (logger.isDebugEnabled()) {logger.debug("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}else {if (logger.isTraceEnabled()) {logger.trace("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}this.beanDefinitionMap.put(beanName, beanDefinition);}else { // beanName 没被注册if (hasBeanCreationStarted()) {// 加锁,防止beanDefinitionMap同一时间被多个线程修改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 {// bean的定义信息加入map缓存this.beanDefinitionMap.put(beanName, beanDefinition);// 记录已经被注册的beanthis.beanDefinitionNames.add(beanName);// 从手动注册的单实例map缓存中移除bean定义信息// 如果是直接通过beanFactory工厂对象将对象注册进单实例缓存中,是属于手动注册// bean的配置中如果有beanName和手动注册的重名,那么删除手动注册removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}// 重置所有beanName对应的缓存// 清楚解析之前留下的对应的beanName 的缓存if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}else if (isConfigurationFrozen()) {clearByTypeCache();}}
6.3.4.2. 通过别名注册BeanDefinition
别名注册方法的代码逻辑很清晰
- alias 和 beanName 相同时,删除alias
- alias覆盖处理,若aliasName已经使用并已经指向了另一beanName则需要用户设置进行处理
- alias循环检查。当A->B存在时,若再次出现A->C->B时抛出异常
注册alias
public void registerAlias(String name, String alias) {Assert.hasText(name, "'name' must not be empty");Assert.hasText(alias, "'alias' must not be empty");// 锁定存放bean别名的Map// 防止统一时间只允许一个线程操作别名Mapsynchronized (this.aliasMap) {// 如果beanName和别名相同的话,不记录别名,并从map缓存中删除if (alias.equals(name)) {this.aliasMap.remove(alias);if (logger.isDebugEnabled()) {logger.debug("Alias definition '" + alias + "' ignored since it points to same name");}}else {// 从别名map缓存中获取beanName信息String registeredName = this.aliasMap.get(alias);if (registeredName != null) {if (registeredName.equals(name)) {// 别名已经存在,不需要进行处理return;}if (!allowAliasOverriding()) {throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +name + "': It is already registered for name '" + registeredName + "'.");}if (logger.isInfoEnabled()) {logger.info("Overriding alias '" + alias + "' definition for registered name '" +registeredName + "' with new target name '" + name + "'");}}// 别名循环检查// 当A->B存在时,若再次出现A->B->C时候者会抛出异常checkForAliasCircle(name, alias);// 将别名加入aliasMap缓存this.aliasMap.put(alias, name);if (logger.isDebugEnabled()) {logger.debug("Alias definition '" + alias + "' registered for name '" + name + "'");}}}}
6.4. beans标签的解析及注册
beans标签是一个定义一组bean的标签,可以可以理解为一个beans标签中可以包含多个bean标签。beans标签解析很简单,它是解析代码是复用doRegisterBeanDefinitions()方法。将Element 元素交给该方法执行,递归解析标签。
6.5. ReaderEventListener 监听器
在前面我们对import、alias、bean、beans标签解析完后,会发送一个事件。Spring没有对这个事件有具体的事件,只留了一个空的实现。
// 解析完标签后,发送事件getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
如果我们在标签解析完后,通过这个事件来做一些业务处理。我们只需要实现ReaderEventListener接口,然后把这个实现类注册进XmlBeanDefinitionReader中。
public class ReaderEventListenerImpl implements ReaderEventListener {/*** 解析标签前事件* @param defaultsDefinition*/@Overridepublic void defaultsRegistered(DefaultsDefinition defaultsDefinition) {}/*** bean 解析完成后事件* @param componentDefinition*/@Overridepublic void componentRegistered(ComponentDefinition componentDefinition) {}/*** alias 标签解析完成后事件* @param aliasDefinition*/@Overridepublic void aliasRegistered(AliasDefinition aliasDefinition) {}/*** import 标签解析完成后事件* @param importDefinition*/@Overridepublic void importProcessed(ImportDefinition importDefinition) {}}
把实现类注册进XmlBeanDefinitionRead中,就可以实现在import、alias、bean标签解析完成之后监听消息,做出符合业务逻辑的开发
public static void main(String[] args) {XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(new XmlBeanFactory(new ClassPathResource("beans.xml")));reader.setEventListener(new ReaderEventListenerImpl());}}
7. 自定义标签的解析
前面已经介绍了Spring的默认标签是如何解析的,现在我们分下下对于自定义标签Spring是如何解析的,我们先回顾一下自定义标签的入口是从哪里开始的,在DefaultBeanDefinitionDocumentReader类中的parseBeanDefinition()方法
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);}}
接着看parseCustomElement()方法是如何解析自定义标签的
public BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);}// containingBd 为父类的bean,对顶层元素的解析应设置为nullpublic BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取对应的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间找到对应的NamespaceHandlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 调用自定义的NamespaceHaner 进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}
7.1. 获取标签的命名空间
无论是Spring中默认标签还是自定义标签的解析,都是从命名空间开始的。至于如何提取对应元素的命名空间其实并不需要我们亲自去实现,在org.w3c.dom.Node中已经提供了方法供我们直接调用
public String getNamespaceURI(Node node) {return node.getNamespaceURI();}
7.2. 提取自定义标签处理器
有了命名空间,我们就可以进行NamespaceHandler的提取了,继续之前parseCustomElement()方法的跟踪,分析NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri),在readerContext初始化的时候其属性namespaceHandlerResolver已经被初始化为DefaultNamespaceHandlerResolver的实例,所以,这里调用resolve方法其实调用的是DefaultNamespaceHandlerResolver类中的方法,我们进入DefaultNamespaceHandlrResolver的resolve()方法进行查看
public NamespaceHandler resolve(String namespaceUri) {// 获取所有已经配置的handler映射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的初始化方法namespaceHandler.init();// 记录在缓存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);}}}
7.3. 标签解析
通过handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))解析标签,进入到NamespaceHandlerSupport类的parse()方法
public BeanDefinition parse(Element element, ParserContext parserContext) {// 寻找解析器BeanDefinitionParser parser = findParserForElement(element, parserContext);// 标签解析return (parser != null ? parser.parse(element, parserContext) : null);}
先寻找元素对应的解析器,然后在调用解析器中的parse()方法
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {// 获取元素名称// 如:自定义标签:<myname:user> 那么元素名称就是userString 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;}
解析方法parse()
public final BeanDefinition parse(Element element, ParserContext parserContext) {AbstractBeanDefinition definition = parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) {try {String id = resolveId(element, definition, parserContext);if (!StringUtils.hasText(id)) {parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element)+ "' when used as a top-level tag", element);}String[] aliases = null;if (shouldParseNameAsAliases()) {String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);registerBeanDefinition(holder, parserContext.getRegistry());if (shouldFireEvents()) {BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}}catch (BeanDefinitionStoreException ex) {String msg = ex.getMessage();parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);return null;}}return definition;}
具体的解析方法交给parseInternal()
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();String parentName = getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}// 获取自定义标签中的class// 此时会调用自定义解析器中的getBeanClass方法Class<?> beanClass = getBeanClass(element);if (beanClass != null) {builder.getRawBeanDefinition().setBeanClass(beanClass);}else {// 若解析器中没有重写getBeanClass()方法,则尝试检查子类是否重写getBeanClassName()方法String beanClassName = getBeanClassName(element);if (beanClassName != null) {builder.getRawBeanDefinition().setBeanClassName(beanClassName);}}builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));BeanDefinition containingBd = parserContext.getContainingBeanDefinition();if (containingBd != null) {// 若存在父类,使用父类的scope属性builder.setScope(containingBd.getScope());}if (parserContext.isDefaultLazyInit()) {// 配置延迟加载builder.setLazyInit(true);}// 调用子类重写的doParse方法进行解析doParse(element, parserContext, builder);return builder.getBeanDefinition();}
对于自定义标签的解析,需要注意的几个点。
- 定义XSD文件来规范xml文档的格式
- 继承AbstractSingleBeanDefinitionParser 抽象类实现doParse()方法解析自定义标签
- 创建自定义Handler继承至NamespaceHandlerSupport将解析器注册到Spring容器
