Spring|Spring 源码分析之IOC容器的基本实现
说到Spring 大家可能都知道,也都用过 ,那么今天就给大家来解析一下,Spring 从解析Xml 到最后的创建Bean 都用到了 什么类,用到了那些模式>
BeanFactor beanfactor = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
功能分析:
1.读取配置文件beanFactory.xml
2.根据beanFacotry.xml 找到配置所对应的类,并且初始化。
3.调用后实例化。
Spring 解析Xml读取Bean 核心类介绍 -DefaultListableBeanFacotry 和XmlBeanDefintionReader
1.XmlBeanFactory
XmlBeanFacotry继承自DefaultListableBeanFactory ,而DefaultListableBeanFacotry是整个加载bean的核心部分,是Spring注册及加载bean的默认实现,而XmlBeanfactory与DefaultListableBeanFactory不同的地方在于 XmlBeanFactory 实现了自定义的Xml读取器XmlBeanDefinationReader实现了BeanDefinitionReader读取,
2.XmlBeanDefinitionReader
XmlBeanDefintionReader 读中有资源文件读取,解析以及注册bean 的类 ,如:ResourceLoader,BeanDefinitionReader DocumentLoader 等等我就不列举了
容器的基础XmlBeanFactory
1.对xml文件的封装
Spring 通过对配置文件的读取封装到对应的类中,如,New ClassPathResource("beanfacotry.xml"); 那么ClassPathResouce完成了什么功能呢?
1.将当前URL封装到Resouce 我们知道URL 没有默认定义ClassPath或者SerevletContext 等资环的handler,此时Spring 自己底层封装了Resouce 即URl封装底层资源。代码如下
public interface Resource extends InputStreamSource{
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
.......
}
这里我们可以借鉴一下,Spring读取xml的方式
Resource resource = new ClassPathResource("bean.xml");
InputStream inputStream = resouce.getInputStream();
Spring 是如何通过 .xml 得到ClassPathResource对象呢 ?其实很简单,我们用IDEA编辑器打开ClassPathResource 对象看看
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
} else {
is = this.classLoader.getResourceAsStream(this.path);
}if (is == null) {
throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
} else {
return is;
}
}
当通过Resource 相关类对xml文件进行封装后具体对配置文件的读取工作就交个XmlBeanDefinitionReader来处理了。
我们可以用IDEA 打开XmlBeanFacotry debug下
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader;
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, (BeanFactory)null);
//调用XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) 的构造方法
}public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
//调用父类的构造器初始化忽略给定接口的装配功能
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
//loadBeanDefinitions 才是加载bean的真正位置
}
}
调用loadBeanDefinitions 加载Bean 跟踪一下
XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//通过属性来记录 已近加载的资源
Set currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}if (!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var6;
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
//从encodedResource中获取已近封装的Resource对象 并在此从Resource中获取InpuStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
//设置编码
}
//这里才是真正的加载bean的部分
var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if (((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}}
return var6;
}
}
调用doLoadBeanDefinitions 加载bean 再次进入到doLoadBeanDefinitions 具体的方法中
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
int validationMode = this.getValidationModeForResource(resource);
//获取xml的验证模式
Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, validationMode, this.isNamespaceAware());
//加载xml文件 获取Document 对象
return this.registerBeanDefinitions(doc, resource);
//获取Document 注册bean信息
} catch (BeanDefinitionStoreException var5) {
throw var5;
} catch (SAXParseException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
} catch (SAXException var7) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
} catch (ParserConfigurationException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
} catch (IOException var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
} catch (Throwable var10) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
}
}
1.获取xml验证模式
我们知道xml验证 有种验证方式XSD(Xml Schemas Definition)和DTD (Document type Definition)两种验证方式
这种为Dtd的声明方式
这种为Xsd 的声明方式
其中,如果没有指明 声明方式 就是用默认的声明方式
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = this.getValidationMode();
if (validationModeToUse != 1) { //如果手动了制定了验证模式则使用指定的验证模式
return validationModeToUse;
} else { //否则就是 默认的验证方式使用自动检测
int detectedMode = this.detectValidationMode(resource);
return detectedMode != 1 ? detectedMode : 3;
}
}
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.");
} else {
InputStream inputStream;
try {
inputStream = resource.getInputStream();
} catch (IOException var5) {
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?", var5);
}try {
return this.validationModeDetector.detectValidationMode(inputStream);
//这里才是真正的获取验证模式
} catch (IOException var4) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", var4);
}
}
}
那我们可以看detectValidationMode 里面是如何处理的
public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
while(true) {
String content;
if ((content = reader.readLine()) != null) {
content = this.consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {//如果是注释或者行或者是空的则跳过
continue;
}
if (this.hasDoctype(content)) {// hasDoctype如果 content.indexOf("DOCTYPE") > -1;
则为DTD 验证
isDtdValidated = true;
} else if (!this.hasOpeningTag(content)) {//否则就是XSD
continue;
}
}int var6 = isDtdValidated ? 2 : 3;
return var6;
}
} catch (CharConversionException var9) {
;
} finally {
reader.close();
}return 1;
}
获取Document 经过了获取验证模式 就可以进行 Document的加载了 同样在XmlBeanFacotry类中,对于文档的读取并没有真正的而在这里解析而是委托给 了DocumentLoader去执行
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//创建DocumentBuilderFactory 对象 其中调用newsInstance 方法//FactoryFinder.find(
DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
返回DocumentBuilderFactory 的实现类 DocumentBuilderFactoryImplDocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
//根据DocumentBuilderFactoryImpl 创建DocumentBuilder对象
return builder.parse(inputSource);
//真正的创建Document 对象
}
1.加载Document 这里也简单 就是获取DocumentBuilderFactory 的实现类DocumentBuilderFactoryImpl
2.根据DocumentBuilderFactoryImpl 获取DocumentBuilder
3.通过DocumentBuilder 来获取Document
这里有必要说明一下 在调用loadDocument 之前调用的getEntityResolver()方法
调用getEntityResolver 方法 返回 EntityResolver 对象,EntityResolver的作用
1.获取SystemId 和publicId
如果 为XSD 的验证模式
SystemId : http://www.springframework.org/schema/beans/spring-beans.xsd
publicId :null
DTD的验证模式
SystemId: http://www.springframework.org/schema/beans/spring-beans.xsd
publicId: //SPRING//DTD BEAN 2.0//EN
解析及注册BeanDefinitions 当把xml文件转换成Document 后,接下来 就是提取和注册bean 了
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
//实例化BeanDefinitionDocumentReader
documentReader.setEnvironment(this.getEnvironment());
//将环境变量设置其中
int countBefore = this.getRegistry().getBeanDefinitionCount();
//记录统计前BeanDefinitions的加载个数
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
//加载和注册bean
return this.getRegistry().getBeanDefinitionCount() - countBefore;
//记录统计后BeanDefinitions的加载个数
}
其中document 是上一节 loadDocument加载转换出来的,在这个方法中很好的体现了 单一制则原则,将逻辑委托给单一的类处理, 而这个逻辑处理类就是BeanDefinitionReaderDocumentReader 。而 BeanDefinitionReaderDocumentReader 的类型已经是DefaultBeanDefinitionReaderDocumentReader 了,进入registerBeanDefinitions 的方法 其实目的只有一个就是提取Root
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);
//这里才是真正的注册bean
}
山路18弯 ,如果说我们以前一直是XMl解析的准备阶段,那么现在, 我们真正的开始解析了,
protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute("profile");
//处理profile
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "environment property must not be null");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",;
");
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createHelper(this.readerContext, root, parent);
this.preProcessXml(root);
//解析前 对bean进行处理 留给子类实现
this.parseBeanDefinitions(root, this.delegate);
//这里专门解析
this.postProcessXml(root);
//解析后,对bean进行处理 留给子类实现
this.delegate = parent;
}
通过上面的代码我们看到了处理流程,首先是对profile的处理,然后看是解析,可是当我们更加preProcessXml和postProcessXml的时候 发现里面是空的,,如果你有了解过设计模式 那么这就是模板方法模式,如果子类需要对加载bean 之前和加载 bean 之后进行处理 则子类需要继承DefaultBeanDefinitionDocumentReader
处理profile 标签
继承到web环境是,在web.xml 加入以下代码,
Spring.profile.activedev
从这里我们就可以看出 在配置文件中部署两套配置 来试用于测试环境和生产环境,这样就可以方便的进行切换
解析并且注册BeanDefinition
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)) {
this.parseDefaultElement(ele, delegate);
//对bean进行处理
} else {
delegate.parseCustomElement(ele);
//对bean进行处理
}
}
}
} else {
delegate.parseCustomElement(root);
}}
在Springde Xml 的配置中有两大类Bean声明一种是默认的 如:
另一种就是自定义的
如果是自定以的Spring知道该怎么做,则采用this.parseDefaultElement方法进行解析,否则则采用 delegate.parseCustomElement(ele)方法 对自定义 命名空间进行解析,而判断是否默认命名空间还是自定义空间的办法是用 delegate.isDefaultNamespace(ele) 中的 node.getNamespaceURI() 来获取命名空间,并且于Spring中固定的命名空间进行对比http://www.springframework.org/schema/beans,如果一样则是默认的 否则 则不是
【Spring|Spring 源码分析之IOC容器的基本实现】对于默认标签解析于自定义标签解析 我们下次再说
推荐阅读
- 如何寻找情感问答App的分析切入点
- Activiti(一)SpringBoot2集成Activiti6
- D13|D13 张贇 Banner分析
- SpringBoot调用公共模块的自定义注解失效的解决
- 解决SpringBoot引用别的模块无法注入的问题
- 自媒体形势分析
- 2020-12(完成事项)
- Android事件传递源码分析
- 2018-07-09|2018-07-09 Spring 的DBCP,c3p0
- Python数据分析(一)(Matplotlib使用)