最近买了本书,来大概学习写spring源码
一:先来段代码来测试一下。
照书上的先来试试
<?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="myTestBean" class="com.nico.TestClient.SpringTest.BeanTest.MyTestBean"/> </beans>
package com.nico.TestClient.SpringTest.BeanTest; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; /** * Created by Administrator on 2016/5/9. */ public class SpringBean { @Test public void testSimpleLoad(){ //已经过时的方法 BeanFactory bf =new XmlBeanFactory(new ClassPathResource("BeanFactoryTest.xml")); ApplicationContext context=new ClassPathXmlApplicationContext("BeanFactoryTest.xml"); MyTestBean myTestBean = (MyTestBean) context.getBean("myTestBean"); System.out.println("testStr:"+myTestBean.getTestStr()); } }
当照着书上面的代码样例写出来的时候,擦IDEA告诉我已经过时了,不禁让我心底一凉,这书是不是买的太久了。。。
好吧我们来学习新版本的源码,大概印证下吧,只有。
首先这个类来读取资源文件
ApplicationContext context=new ClassPathXmlApplicationContext("BeanFactoryTest.xml");
然后我们可以通过context.getbean就可以获取各种类型的bean。
大致学习源码还是按照书中的步骤来,节省时间!!只有以后在有机会一个版本一个版本的看看区别。
二:载入xml文件,以及读取。
上面测试代码的大致流程:
1.读取配置文件
2.根据配置文件中的配置找到对应类的配置,并实例化
3.调用实例化后的实例
三:分析代码以及实现
首先我们先从第一段代码分析:
(1)BeanFactory bf =new XmlBeanFactory(new ClassPathResource("BeanFactoryTest.xml"));
大致流程:
1.new ClassPathResource(xml文件)返回ClassPathResource
2.new XmlBeanFactory() 返回beanfactiory对象
(2)配置文件封装
spring的配置文件是通过ClassPathResource来进行封装的,将不用来源的资源抽象成RL,然后我们可以查看Resource来看看都有哪几种方法:
看下类图
相比书中所写,已经少了很多,将就看吧。。大致脉络看看。
我们进去看看Resource,其中的方法有:
contentLength
createRelative
exists
getDescription
getFile
getFilename
getURI
getURL
isOpen
isReadable
lastModified
其中抽象了Spring内部使用到的底层资源,file,url,classpath等。并且存在三个判断当前资源状态的三个方法:存在性,刻度性,是否可以打开状态
Resource接口方便对资源进行统一管理,实现方式是通过class或者classLoader提供的底层方法进行调用。
当resource相关类完成了对配置文件的封装后配置文件的读取工作就就交给xmlBeanDefinitionReader来处理了。
XmlBeanFactory的初始化有若干方法,我看下使用Resource实例作为构造参数的方法:
/** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
下面的reader.loadBeanDefinitions就是资源加载的真正实现。
在这之前还有一个父类,我点进去瞅一瞅。
/** * Create a new AbstractAutowireCapableBeanFactory. */ public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
其中ignoreDependencyInterface的主要功能就是忽略给定接口的自动装配功能。
(3)加载Bean
上面我们走到这个this.reader.loadBeanDefinitions(resource),这里是整个资源加载的切入点。
大致顺序:
1.封装资源文件。当进入
loadBeanDefinitions方法时候使用EncodeResource类进行封装
2.获取输入流。从Resource中获取对应的InputStream并且构造InputSource
3.通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
源码流程:
public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(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.getResource()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { 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(); } } } protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); 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); } }
大致的源码调用顺序。
首先看EncodedResource的作用,对于资源文件的编码进行处理。主要的方法在于其中的getResder方法,用来设置编码。
当构造好了encodeResource对象后,再次转入
loadBeanDefinitions(EncodedResource encodedResource),这个方法内部就是真正的数据准备阶段。
然后
doLoadBeanDefinitions方法就是核心算法。
然后它大概做了三件事:
1.获取对xml文件的验证模式
2.加载xml文件,并得到对应的document
3.根据返回的document注册bean信息
三:获取XML的验证模式
xml文件的验证模式保证了xml文件的正确性,比较常用的是两种:DTD和XSD。
DTD和XSD的区别
都在头部有声明
(1)验证模式的读取
通过这个方法来读取getValidationModeForResource
如果手动指定了验证模式则使用指定的验证模式,如果未指定则使用自动检测
int validationModeToUse = getValidationMode(); if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; }
自动检测方法在
detectValidationMode中实现,最后来/** * Does the content contain the the DTD DOCTYPE declaration? */ private boolean hasDoctype(String content) { return (content.indexOf(DOCTYPE) > -1); }
判断是否含有DOCTYPE来检测验证模式。
四:获取document
经过验证模式准备的步骤就可以进行document加载了,然后通过loadDocument方法来进行,主要是通过SAX解析XML文档,
@Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
然后其中要有个EntityResolver.
(1)EntityResolver用法
SAX应用需要实现自定义处理外部实体,则必须实现此接口向SAX注册一个实例,对于解析XML,SAX首先读取该XML的声明,根据声明去寻找相应的DTD定义。默认寻址通过网络,下载过程是个漫长的过程,而且网络中断或者不可用,这里会报错。这个的作用是项目本身就可以提供一个DTD声明的方法,避免了通过网络来寻找相应的声明。
五:解析及注册BeanDefinitions
上面我们已经把文件转化成Document,然后就是提取以及注册bean。
当拥有Document实例对象时候,引入下面的方法。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
很符合面向对象的单一职责的原则。BeanDefinitionDocumentReader是一个接口,实例化是在
createBeanDefinitionDocumentReader完成,通过这个方法,然后进入DefaultBeanDefinition Document Reader,这个方法的重要目的之一就是提取ROOT以便于再次将root作为参数继续BeanDefinition的注册
@Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
protected void doRegisterBeanDefinitions(Element root) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { Assert.state(this.environment != null, "Environment must be set for evaluating profiles"); String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!this.environment.acceptsProfiles(specifiedProfiles)) { return; } } // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
之前的所有都是XML加载解析的准备阶段doRegisterBeanDefinitions算是真正地开始进行解析。
那么处理流程,首先是对rpofile的处理,然后开始解析。
(1)profile属性的使用
注册bean的最开始是对PROFILE_ATTRIBUTE属性的解析,
profile 用的最多是更换不同的数据库,用来生产环境和开发环境。
(2)解析并注册BeanDeifnition
处理了profile后就可以进行XML的读取了。进入
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); } }
这个方法就是XML的读取,