点击关注公众号,实用技术文章及时了解

来源:blog.csdn.net/l6108003/article/

details/106297618

前言

Spring已经是我们Java Web开发必不可少的一个框架,其大大简化了我们的开发,提高了开发者的效率。同时,其源码对于开发者来说也是宝藏,从中我们可以学习到非常优秀的设计思想以及优雅的命名规范,但因其体系庞大、设计复杂对于刚开始阅读源码的人来说是非常困难的。

所以在此之前首先你得下定决心,不管有多困难都得坚持下去;其次,最好先把设计模式掌握熟练;然后在开始阅读源码时一定要多画UML类图和时序图,多问自己为什么要这么设计?这样设计的好处是什么?还有没有更好的设计?

当然,晕车是难免的,但还是那句话,一定要持之以恒(PS:源码版本5.1.3.RELEASE)。

正文

熟悉IOC体系结构

要学习Spring源码,我们首先得要找准入口,那这个入口怎么找呢?我们不妨先思考一下,在Spring项目启动时,Spring做了哪些事情。这里我以最原始的xml配置方式来分析,那么在项目启动时,首先肯定要先定位——找到xml配置文件,定位之后肯定是加载——将我们的配置加载到内存,最后才是根据我们的配置实例化(本篇文章只讲前两个过程)。

那么Spring是如何定位和加载xml文件的呢?涉及到哪些类呢?我们先来看张类图:

该图是IOC的体系图,整体上你需要有一个大概的印象,可以看到所有的IOC都是有继承关系的,这样设计的好处就是任何一个子类IOC可以直接使用父类IOC加载的Bean,有点像JVM类加载的双亲委派机制;而红色方框圈起来的是本篇涉及到的重要类,需要着重记忆它们的关系。

图中最重要的两个类是BeanFactoryApplicationContext,这是所有IOC的父接口。其中BeanFactory提供了最基本的对bean的操作:

ApplicationContex继承了BeanFactory,同时还继承了MessageSourceResourceLoaderApplicationEventPublisher等接口以提供国际化、资源加载、事件发布等高级功能。

我们应该想到平时Spring加载xml文件应该是ApplicationContext的子类,从图中我们可以看到一个叫ClassPathXmlApplicationContext的类,联想到我们平时都会 将xml放到classPath下,所以我们直接从这个类开始就行,这就是优秀命名的好处。

探究配置加载的过程

ClassPathXmlApplicationContext中有很多构造方法,其中有一个是传入一个字符串的(即配置文件的相对路径),但最终是调用的下面这个构造:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)throws BeansException {super(parent);//创建解析器,解析configLocationssetConfigLocations(configLocations);if (refresh) {refresh();}}

首先调用父类构造器设置环境:

public AbstractApplicationContext(@Nullable ApplicationContext parent) {this();setParent(parent);}public void setParent(@Nullable ApplicationContext parent) {this.parent = parent;if (parent != null) {Environment parentEnvironment = parent.getEnvironment();if (parentEnvironment instanceof ConfigurableEnvironment) {getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);}}}

然后解析传入的相对路径保存到configLocations变量中,最后再调用父类AbstractApplicationContext的refresh方法刷新容器(启动容器都会调用该方法),我们着重来看这个方法:

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {//为容器初始化做准备prepareRefresh();// 解析xmlConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

这个方法是一个典型的模板方法模式的实现,第一步是准备初始化容器环境,这一步不重要,重点是第二步,创建BeanFactory对象、加载解析xml并封装成BeanDefinition对象都是在这一步完成的。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {refreshBeanFactory();return getBeanFactory();}

点进去看是调用了refreshBeanFactory方法,但这里有两个实现,应该进哪一个类里面呢?

如果你还记得前面的继承体系,那你就会毫不犹豫的进入AbstractRefreshableApplicationContext类中,所以在阅读源码的过程中一定要记住类的继承体系。

protected final void refreshBeanFactory() throws BeansException {//如果BeanFactory不为空,则清除BeanFactory和里面的实例if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {//创建DefaultListableBeanFactoryDefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());//设置是否可以循环依赖 allowCircularReferences//是否允许使用相同名称重新注册不同的bean实现.customizeBeanFactory(beanFactory);//解析xml,并把xml中的标签封装成BeanDefinition对象loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}}

在这个方法中首先会清除掉上一次创建的BeanFactory和对象实例,然后创建了一个DefaultListableBeanFactory对象并传入到了loadBeanDefinitions方法中,这也是一个模板方法,因为我们的配置不止有xml,还有注解等,所以这里我们应该进入AbstractXmlApplicationContext类中:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {//创建xml的解析器,这里是一个委托模式XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);// Configure the bean definition reader with this context's// resource loading environment.beanDefinitionReader.setEnvironment(this.getEnvironment());//这里传一个this进去,因为ApplicationContext是实现了ResourceLoader接口的beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// Allow a subclass to provide custom initialization of the reader,// then proceed with actually loading the bean definitions.initBeanDefinitionReader(beanDefinitionReader);//主要看这个方法loadBeanDefinitions(beanDefinitionReader);}

首先创建了一个XmlBeanDefinitionReader对象,见名知意,这个就是解析xml的类,需要注意的是该类的构造方法接收的是BeanDefinitionRegistry对象,而这里将DefaultListableBeanFactory对象传入了进去(别忘记了这个对象是实现了BeanDefinitionRegistry类的),如果你足够敏感,应该可以想到后面会委托给该类去注册。

注册什么呢?自然是注册BeanDefintion。记住这个猜想,我们稍后来验证是不是这么回事。

接着进入loadBeanDefinitions方法获取之前保存的xml配置文件路径,并委托给XmlBeanDefinitionReader对象解析加载:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {Resource[] configResources = getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);}//获取需要加载的xml配置文件String[] configLocations = getConfigLocations();if (configLocations != null) {reader.loadBeanDefinitions(configLocations);}}

最后会进入到抽象父类AbstractBeanDefinitionReader中:

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {// 这里获取到的依然是DefaultListableBeanFactory对象ResourceLoader resourceLoader = getResourceLoader();if (resourceLoader == null) {throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");}if (resourceLoader instanceof ResourcePatternResolver) {// Resource pattern matching available.try {//把字符串类型的xml文件路径,形如:classpath*:user/**/*-context.xml,转换成Resource对象类型,其实就是用流//的方式加载配置文件,然后封装成Resource对象Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);//主要看这个方法int count = loadBeanDefinitions(resources);if (actualResources != null) {Collections.addAll(actualResources, resources);}if (logger.isTraceEnabled()) {logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");}return count;}catch (IOException ex) {throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);}}else {// Can only load single resources by absolute URL.Resource resource = resourceLoader.getResource(location);int count = loadBeanDefinitions(resource);if (actualResources != null) {actualResources.add(resource);}if (logger.isTraceEnabled()) {logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");}return count;}}

这个方法中主要将xml配置加载到存中并封装成为Resource对象,这一步不重要,可以略过,主要的还是loadBeanDefinitions方法,最终还是调用到子类XmlBeanDefinitionReader的方法:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {try {//获取Resource对象中的xml文件流对象InputStream inputStream = encodedResource.getResource().getInputStream();try {//InputSource是jdk中的sax xml文件解析对象InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}//主要看这个方法return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}}protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {//把inputSource 封装成Document文件对象,这是jdk的APIDocument doc = doLoadDocument(inputSource, resource);//主要看这个方法,根据解析出来的document对象,拿到里面的标签元素封装成BeanDefinitionint count = registerBeanDefinitions(doc, resource);if (logger.isDebugEnabled()) {logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;}}public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 创建DefaultBeanDefinitionDocumentReader对象,并委托其做解析注册工作BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();int countBefore = getRegistry().getBeanDefinitionCount();//主要看这个方法,需要注意createReaderContext方法中创建的几个对象documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;}public XmlReaderContext createReaderContext(Resource resource) {// XmlReaderContext对象中保存了XmlBeanDefinitionReader对象和DefaultNamespaceHandlerResolver对象的引用,在后面会用到return new XmlReaderContext(resource, this.problemReporter, this.eventListener,this.sourceExtractor, this, getNamespaceHandlerResolver());}

接着看看DefaultBeanDefinitionDocumentReader中是如何解析的:

protected void doRegisterBeanDefinitions(Element root) {// 创建了BeanDefinitionParserDelegate对象BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);// 如果是Spring原生命名空间,首先解析 profile标签,这里不重要if (this.delegate.isDefaultNamespace(root)) {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;}

在这个方法中重点关注preProcessXmlparseBeanDefinitionspostProcessXml三个方法,其中preProcessXmlpostProcessXml都是空方法,意思是在解析标签前后我们自己可以扩展需要执行的操作,也是一个模板方法模式,体现了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原生标签和自定义标签。怎么区分这两种标签呢?

// 自定义标签
<context:component-scan/>// 默认标签
<bean:/>

如上,带前缀的就是自定义标签,否则就是Spring默认标签,无论哪种标签在使用前都需要在Spring的xml配置文件里声明Namespace URI,这样在解析标签时才能通过Namespace URI找到对应的NamespaceHandler

xmlns:context="http://www.springframework.org/schema/context"http://www.springframework.org/schema/beans

isDefaultNamespace判断是不是默认标签,点进去看看是不是跟我上面说的一致:

public boolean isDefaultNamespace(Node node) {return isDefaultNamespace(getNamespaceURI(node));}public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";public boolean isDefaultNamespace(@Nullable String namespaceUri) {return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));}

可以看到http://www.springframework.org/schema/beans所对应的就是默认标签。接着,我们进入parseDefaultElement方法:

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);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// recursedoRegisterBeanDefinitions(ele);}}

这里面主要是对import、alias、bean标签的解析以及beans的字标签的递归解析,主要看看bean标签的解析:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 解析elment封装为BeanDefinitionHolder对象BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 该方法功能不重要,主要理解设计思想:装饰者设计模式以及SPI设计思想bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 完成document到BeanDefinition对象转换后,对BeanDefinition对象进行缓存注册BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}// Send registration event.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {// 获取id和name属性String id = ele.getAttribute(ID_ATTRIBUTE);String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);// 获取别名属性,多个别名可用,;隔开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");}}//检查beanName是否重复if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}// 具体的解析封装过程还在这个方法里AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {if (!StringUtils.hasText(beanName)) {try {if (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);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;}// bean的解析public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));// 获取class名称和父类名称String className = null;if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}try {// 创建GenericBeanDefinition对象AbstractBeanDefinition bd = createBeanDefinition(className, parent);// 解析bean标签的属性,并把解析出来的属性设置到BeanDefinition对象中parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));//解析bean中的meta标签parseMetaElements(ele, bd);//解析bean中的lookup-method标签parseLookupOverrideSubElements(ele, bd.getMethodOverrides());//解析bean中的replaced-method标签 parseReplacedMethodSubElements(ele, bd.getMethodOverrides());//解析bean中的constructor-arg标签parseConstructorArgElements(ele, bd);//解析bean中的property标签 parsePropertyElements(ele, bd);parseQualifierElements(ele, bd);bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}return null;}

bean标签的解析步骤仔细理解并不复杂,就是将一个个标签属性的值装入到了BeanDefinition对象中,这里需要注意parseConstructorArgElementsparsePropertyElements方法,分别是对constructor-argproperty标签的解析,解析完成后分别装入了BeanDefinition对象的constructorArgumentValuespropertyValues中,而这两个属性在接下来c和p标签的解析中还会用到,而且还涉及一个很重要的设计思想——装饰器模式。

Bean标签解析完成后将生成的BeanDefinition对象、bean的名称以及别名一起封装到了BeanDefinitionHolder对象并返回,然后调用了decorateBeanDefinitionIfRequired进行装饰:

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {BeanDefinitionHolder finalDefinition = definitionHolder;//根据bean标签属性装饰BeanDefinitionHolder,比如<bean class="xx" p:username="dark"/>NamedNodeMap attributes = ele.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {Node node = attributes.item(i);finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);}//根据bean标签子元素装饰BeanDefinitionHolder\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;}

在这个方法中分别对Bean标签的属性和子标签迭代,获取其中的自定义标签进行解析,并装饰之前创建的BeanDefinition对象,如同下面的c和p:

// c:和p:表示通过构造器和属性的setter方法给属性赋值,是constructor-arg和property的简化写法
<bean class="com.dark.bean.Student" id="student" p:username="Dark" p:password="111" c:age="12" c:sex="1"/>

两个步骤是一样的,我们点进decorateIfRequired方法中:

public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {//根据node获取到node的命名空间,形如:http://www.springframework.org/schema/pString namespaceUri = getNamespaceURI(node);if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {// 根据配置文件获取namespaceUri对应的处理类,SPI思想NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler != null) {//调用NamespaceHandler处理类的decorate方法,开始具体装饰过程,并返回装饰完的对象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/")) {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;}

这里也和我们之前说的一样,首先获取到标签对应的namespaceUri,然后通过这个Uri去获取到对应的NamespceHandler,最后再调用NamespceHandler的decorate方法进行装饰。我们先来看看获取NamespceHandler的过程,这涉及到一个非常重要的高扩展性的思想——SPI

public NamespaceHandler resolve(String namespaceUri) {// 获取spring中所有jar包里面的 "META-INF/spring.handlers"文件,并且建立映射关系Map<String, Object> handlerMappings = getHandlerMappings();//根据namespaceUri:http://www.springframework.org/schema/p,获取到这个命名空间的处理类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);//调用处理类的init方法,在init方法中完成标签元素解析类的注册namespaceHandler.init();handlerMappings.put(namespaceUri, namespaceHandler);return namespaceHandler;}}}// AOP标签对应的NamespaceHandler,可以发现NamespaceHandler的作用就是管理和注册与自己相关的标签解析器public void init() {// In 2.0 XSD as well as in 2.1 XSD.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());// Only in 2.0 XSD: moved to context namespace as of 2.1registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());}

看到这里我们应该就清楚了Spring是如何解析xml里的标签了以及我们如果要扩展自己的标签该怎么做。只需要创建一个我们的自定义标签和解析类,并指定它的命名空间以及NamespaceHandler,最后在META-INF/spring.handlers文件中指定命名空间和NamespaceHandler的映射关系即可,就像Spring的c和p标签一样:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

像这样使用SPI的思想设计我们的项目的话,当需要扩展时,不需要改动任何的代码,非常的方便优雅。

接着,我们回到handler的decorate方法,这里有三个默认的实现类:NamespaceHandlerSupportSimpleConstructorNamespaceHandlerSimplePropertyNamespaceHandler

第一个是一个抽象类,与我们这里的流程无关,感兴趣的可自行了解,第二个和第三个则分别是c和p标签对应的NamespaceHandler,两个装饰的处理逻辑基本上是一样的,我这里进入的是SimpleConstructorNamespaceHandler类:

public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {if (node instanceof Attr) {Attr attr = (Attr) node;String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr));String argValue = StringUtils.trimWhitespace(attr.getValue());ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues();boolean ref = false;// handle -ref argumentsif (argName.endsWith(REF_SUFFIX)) {ref = true;argName = argName.substring(0, argName.length() - REF_SUFFIX.length());}ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue);valueHolder.setSource(parserContext.getReaderContext().extractSource(attr));// handle "escaped"/"_" argumentsif (argName.startsWith(DELIMITER_PREFIX)) {String arg = argName.substring(1).trim();// fast default checkif (!StringUtils.hasText(arg)) {cvs.addGenericArgumentValue(valueHolder);}// assume an index otherwiseelse {int index = -1;try {index = Integer.parseInt(arg);}catch (NumberFormatException ex) {parserContext.getReaderContext().error("Constructor argument '" + argName + "' specifies an invalid integer", attr);}if (index < 0) {parserContext.getReaderContext().error("Constructor argument '" + argName + "' specifies a negative index", attr);}if (cvs.hasIndexedArgumentValue(index)) {parserContext.getReaderContext().error("Constructor argument '" + argName + "' with index "+ index+" already defined using <constructor-arg>." +" Only one approach may be used per argument.", attr);}cvs.addIndexedArgumentValue(index, valueHolder);}}// no escaping -> ctr nameelse {String name = Conventions.attributeNameToPropertyName(argName);if (containsArgWithName(name, cvs)) {parserContext.getReaderContext().error("Constructor argument '" + argName + "' already defined using <constructor-arg>." +" Only one approach may be used per argument.", attr);}valueHolder.setName(Conventions.attributeNameToPropertyName(argName));cvs.addGenericArgumentValue(valueHolder);}}return definition;}

很简单,拿到c标签对应的值,封装成ValueHolder,再添加到BeanDefinitionConstructorArgumentValues属性中去,这样就装饰完成了。

讲到这里你可能会觉得,这和平时看到装饰器模式不太一样。其实,设计模式真正想要表达的是各种模式所代表的思想,而不是死搬硬套的实现,只有灵活的运用其思想才算是真正的掌握了设计模式,而装饰器模式的精髓就是动态的将属性、功能、责任附加到对象上,这样你再看这里是否是运用了装饰器的思想呢?

装饰完成后返回BeanDefinitionHolder对象并调用BeanDefinitionReaderUtils.registerBeanDefinition方法将该对象缓存起来,等待容器去实例化。这里就是将其缓存到DefaultListableBeanFactorybeanDefinitionMap属性中,自己看看代码也就明白了,我就不贴代码了。至此,Spring的XML解析原理分析完毕,下面是我画的时序图,可以对照看看:

总结

本篇是Spring源码分析的第一篇,只是分析了refresh中的obtainFreshBeanFactory方法,我们可以看到仅仅是对XML的解析和bean定义的注册缓存,Spring就做了这么多事,并考虑到了各个可能会扩展的地方,那我们平时做的项目呢?看似简单的背后是否有深入思考过呢?

推荐

主流Java进阶技术(学习资料分享)

Java面试题宝典

加入Spring技术开发社区

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

Spring解析 XML 的原理,你搞定了么?相关推荐

  1. Java解析XML的原理

    在用Java解析XML时候,一般都使用现成XML解析器来完成,自己编码解析是一件很棘手的问题,对程序员要求很高,一般也没有专业厂商或者开源组织实现的好. 目前Java XML解析器有十多种之多,解析原 ...

  2. 从原理上搞定编码-- Base64编码

    开发者对Base64编码肯定很熟悉,是否对它有很清晰的认识就不一定了.实际 上Base64已经简单到不能再简单了,如果对它的理解还是模棱两可实在不应该.大概介绍一下Base64的相关内容,花几分钟时间 ...

  3. 推荐 2 个 Spring Boot 工作流项目,轻松搞定工作流!

    点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜,留言必回,有问必答! 每一天进步一点点,是成功的开始... 今天主要推 ...

  4. AXIOM解析XML 详细原理

    转自:http://warlaze.blog.sohu.com/58477971.html AXIOM Axis对象模型(AXIOM)是一个XML对象模型,设计用于提高XML处理期间的内存的使用率和性 ...

  5. Spring Boot 启动,1 秒搞定!

    原文: dev.to 翻译: ImportNew.com - 唐尤华 译文: http://www.importnew.com/30647.html "Spring有多快?" 这是 ...

  6. android jackson 解析json字符串,一文搞定Jackson解析JSON数据

    Json解析-Jackson使用教程 日常求赞,感谢老板. 一.JSON解析 我这里指的解析是:JSON和JavaObject之间的序列化和反序列化. 如果你的项目进行了前后端分离,那你一定使用过JS ...

  7. 切比雪夫不等式例题讲解_「高中数学」柯西不等式,最全解析,高考必备,搞定最后十分...

    知识无价,关注点赞 柯西(Cauchy,Augustin-Louis,1789-1857)是法国数学家.力学家.27岁成为巴黎综合工科学校教授,并当选为法国科学院 院士. 他的一生获得了多项重要的成果 ...

  8. Spring读取xml配置文件的原理与实现

    2019独角兽企业重金招聘Python工程师标准>>> Spring读取xml配置文件的原理与实现 本篇博文的目录: 一:前言 二:spring的配置文件 三:依赖的第三方库.使用技 ...

  9. XML解析(一),SAX解析XML

    转载自  XML解析(一),SAX解析XML 一.概述  SAX,全称Simple API for XML,是一种以事件驱动的XMl API,是XML解析的一种新的替代方法,解析XML常用的还有DOM ...

最新文章

  1. 时频特性分析(Matlab)
  2. Linux C下实现线程池
  3. python结束循环_python中break、continue 、exit() 、pass终止循环的区别
  4. 腾讯iOA零信任安全——IT变革下的新一代企业网
  5. Java生产环境下性能监控与调优详解 第3章 基于JVisualVM的可视化监控
  6. 热情不减!iPhone12国行首批供货已售罄
  7. oracle truncate闪回数据库恢复
  8. python判断点在矩形内_定义一个矩形和点的位置,判断点是否在矩形里面
  9. linux python命令无反应_几个无(有)聊(趣)的Linux命令
  10. poj 3256 Cow Picnic 优化深搜
  11. 黑苹果intel 9560ac网卡成功驱动,无需换卡
  12. 力扣 69. x 的平方根 三种方法
  13. 模2除法(CRC冗余码计算)和二进制/十进制除法
  14. C#毕业设计——基于C#+ASP.NET+SQL Server的酒店入住信息管理系统设计与实现(毕业论文+程序源码)——酒店入住信息管理系统
  15. 网络学习---基础知识
  16. 【剑指offer刷题】排序算法
  17. 1 error and 0 warnings potentially fixable with the `--fix` option.
  18. 照片批量重命名为拍摄日期
  19. postman之什么是接口
  20. xp系统怎么上传到ftp服务器,xp系统怎么上传到ftp服务器

热门文章

  1. 抢先一步 华为将于7月26日发布首款5G智能手机
  2. 沈梦辰回应闲鱼卖假货事件:负责到底 不做一锤子买卖
  3. 拳王虚拟项目公社:月入16000+的虚拟商品教程,虚拟项目全自动化的升级玩法拆解
  4. 中国男足孔已己版(转载,博大家一笑)
  5. mysql分页查询所有数据库,数据库分页查询
  6. websocketpp最简单的服务器
  7. python中魔法函数_02 python中魔法函数
  8. 维护人员工具_软件项目管理:软件工具与开发环境相关知识介绍
  9. 【clickhouse】ClickHouse表引擎 MergeTree 索引与数据存储方式 一级索引 二级索引
  10. 【git】git compare with branch 一样的代码 但是却标识不一样 成块显示 Git 比较 不准确