Spring源码解析(一)

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

核心容器:包括Core、Beans、Context、EL模块。

  • Core模块:封装了框架依赖的最底层部分,包括资源访问、类型转换及一些常用工具类。
  • Beans模块:提供了框架的基础部分,包括反转控制和依赖注入。其中Bean Factory是容器核心,本质是“工厂设计模式”的实现,而且无需编程实现“单例设计模式”,单例完全由容器控制,而且提倡面向接口编程,而非面向实现编程;所有应用程序对象及对象间关系由框架管理,从而真正把你从程序逻辑中把维护对象之间的依赖关系提取出来,所有这些依赖关系都由BeanFactory来维护。
  • Context模块:以Core和Beans为基础,集成Beans模块功能并添加资源绑定、数据验证、国际化、Java EE支持、容器生命周期、事件传播等;核心接口是ApplicationContext。
  • EL模块:提供强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从Spring 容器获取Bean,它也支持列表投影、选择和一般的列表聚合等。
  • AOP模块:Spring AOP模块提供了符合 AOP Alliance规范的面向方面的编程(aspect-oriented programming)实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中;这样各专其职,降低业务逻辑和通用功能的耦合。
  • Aspects模块:提供了对AspectJ的集成,AspectJ提供了比Spring ASP更强大的功能。
  • 事务模块:该模块用于Spring管理事务,只要是Spring管理对象都能得到Spring管理事务的好处,无需在代码中进行事务控制了,而且支持编程和声明性的事务管理。
  • JDBC模块:提供了一个JBDC的样例模板,使用这些模板能消除传统冗长的JDBC编码还有必须的事务控制,而且能享受到Spring管理事务的好处。
  • ORM模块:提供与流行的“对象-关系”映射框架的无缝集成,包括Hibernate、JPA、MyBatis等。而且可以使用Spring事务管理,无需额外控制事务。
  • OXM模块:提供了一个对Object/XML映射实现,将java对象映射成XML数据,或者将XML数据映射成java对象,Object/XML映射实现包括JAXB、Castor、XMLBeans和XStream。
  • JMS模块:用于JMS(Java Messaging Service),提供一套 “消息生产者、消息消费者”模板用于更加简单的使用JMS,JMS用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
  • Web/Remoting模块:Web/Remoting模块包含了Web、Web-Servlet、Web-Struts、Web-Porlet模块。
  • Web模块:提供了基础的web功能。例如多文件上传、集成IoC容器、远程过程访问(RMI、Hessian、Burlap)以及Web Service支持,并提供一个RestTemplate类来提供方便的Restful services访问。
  • Web-Servlet模块:提供了一个Spring MVC Web框架实现。Spring MVC框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的JSP标签,完全无缝与Spring其他技术协作。
  • Web-Struts模块:提供了与Struts无缝集成,Struts1.x 和Struts2.x都支持
  • Test模块: Spring支持Junit和TestNG测试框架,而且还额外提供了一些基于Spring的测试功能,比如在测试Web框架时,模拟Http请求的功能。
    数据访问/集成模块:该模块包括了JDBC、ORM、OXM、JMS和事务管理。

Spring的结构组成:
核心类介绍:
1、DefaultListableBeanFactory
注意:XmlBeanFactory继承自DefaultListableBeanFactory,而这个类时整个bean加载的核心部分。
* AliasRegistry: 定义对alias的简单增删改操作
* SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现;
* SingletonBeanRegistry:定义对单例的注册和获取
* BeanFactory:定义获取bean及bean的各种属性
* DefaultSingletonBeanRegistry:对接口SingleBeanRegistry各函数的实现
* HierarchicalBeanFactory:继承BeanFactory,
* BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作。
* FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。
* ConfigurableBeanFactory:提供配置Factory的各种方法
* ListableBeanFactory:根据各种条件获取bean的配置清单
* AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后台处理器
* ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型和接口等
* DefaultListableBeanFactory:综合上面的所有功能,主要是对bean注册后的处理。

注意: XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要是用于从xml文档中读取BeanDefinition,对于注册以及获取bean都是从父类DefaultListableBeanFactory继承的方法去实现。

2、XmlBeanDefinitionReader:xml配置文件的读取时Spring中重要的功能。
* ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource.
* BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能;
* EnvironmentLoader:定义获取Environment方法

容器的基础XmlBeanFactory:

BeanFactory bf = new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”));

在这里,首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,当有了Resource后就可以进行XmlBeanFactory的初始化了

Resource如何封装的呢?
在ClassPathResource中完成了什么功能呢?
在Java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同的前缀来识别。比如file:、http:、jar.
Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、ClassPath等,对于不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。

当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了

XmlBeanFactory中的this.reader.loadBeanDefinitions(resource);才是资源加载的真正实现。

这里的流程:1、封装资源文件;对参数Resource使用EncodedResource类进行封装;
2、获取输入流,从Resource中获取对应的InputStream并构造InputSource
3、通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions;

    首先对传入的resource参数进行封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取xml文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions;
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        Document doc = this.doLoadDocument(inputSource, resource);
        return this.registerBeanDefinitions(doc, resource);
    } catch (BeanDefinitionStoreException var4) {
        throw var4;
    } catch (SAXParseException var5) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
    } catch (SAXException var6) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
    } catch (ParserConfigurationException var7) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
    } catch (IOException var8) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
    } catch (Throwable var9) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
    }

}
以上代码所做的只有两件事:a、加载xml文件,并且得到对应的Document(其中会有对xml文件的验证模式)
b、根据返回的Document注册bean信息;

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}

[ ] xml文件的验证模式保证了xml文件的正确性;常用的验证模式有两种:DTD和XSD
DTD: 文档类型定义; 是一种xml约束模式语言,DTD保证了xml文档格式正确性,通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确,一个DTD文档包括:元素的定义规则、元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
XSD:XML Schemas Definition,描述了xml文档的结构。可以用一个指定的xml schema来验证某个xml文档,来检查该xml文档是否符合其要求;

这里通过getValidationModeForResource方法来获取对应资源的验证模式;
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);
        }
    }

}
接着
public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

    byte var4;
    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)) {
                    isDtdValidated = true;
                //读取到<开始符号,验证模式一定会在开始符号之前
                } else if(!this.hasOpeningTag(content)) {
                    continue;
                }
            }

            int var5 = isDtdValidated?2:3;
            return var5;
        }
    } catch (CharConversionException var9) {
        var4 = 1;
    } finally {
        reader.close();
    }

    return var4;

}
//上述代码描述的主要功能就是:如果设定了验证模式则使用验证模式;否则使用自动检测方式;Spring用来检测验证模式的方式:判断是否包含DOCTYPE,如果是就是DTD,否则就是XSD;

获取Document:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
if(logger.isDebugEnabled()) {
logger.debug(“Using JAXP provider [” + factory.getClass().getName() + “]”);
}

    DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);

}

使用了DocumentLoader类去执行,这个类只是个接口,真正执行的时DefaultDocumentLoader类;
执行过程:通过SAX解析xml文档的套路大致一样。首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象;

对于EntityResolver的理解:

这个的作用就是:对于解析一个xml,sax首先读取xml文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证,默认的寻找规则,通过网络(实现上就是声明的DTD的URI地址)来相应的DTD声明,并进行认证,下载出错怎么办?
所以引入了EntityResolver,它提供了一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给sax即可,这样避免了通过网络来寻找相应的声明。
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if(systemId != null) {
if(systemId.endsWith(“.dtd”)) {
//dtd从这里解析
return this.dtdResolver.resolveEntity(publicId, systemId);
}

        if(systemId.endsWith(".xsd")) {
            //通过调用META-INF/Spring.schemas解析
            return this.schemaResolver.resolveEntity(publicId, systemId);
        }
    }

    return null;

}

通过在META-INF/Spring.schemas文件中找到systemid所对应的xsd文件并加载!

解析以及注册BeanDefinitions:

    当把文件转换为Document后,接下来就是提取以及注册bean了

Document doc = this.doLoadDocument(inputSource, resource);
return this.registerBeanDefinitions(doc, resource);

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
//记录统计前BeanDefiniton的加载个数
int countBefore = this.getRegistry().getBeanDefinitionCount();
//加载注册bean
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
//记录本次加载的BeanDefinition个数
return this.getRegistry().getBeanDefinitionCount() – countBefore;
}
//正真开始解析
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug(“Loading bean definitions”);
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if(this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(“profile”);
if(StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, “,; “);
if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if(this.logger.isInfoEnabled()) {
this.logger.info(“Skipped XML bean definition file due to specified profiles [” + profileSpec + “] not matching: ” + this.getReaderContext().getResource());
}

                return;
            }
        }
    }

    this.preProcessXml(root);
    this.parseBeanDefinitions(root, this.delegate);
    this.postProcessXml(root);
    this.delegate = parent;

}

……

注意:从上代码中可以看到,在注册bean时,最开始时对profile属性的解析,这个属性可是使我们在配置文件中部署两套配置来适用于生产环境和开发环境。
//首先程序会获取beans节点是否定义了profile属性,如果定义了则会需要到环境变量中去寻找。

//处理了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)) {
                    this.parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }

}
因为在Spring中xml配置里面有两大类Bean的声明:1、默认的,;2、自定义的。
对于根节点或者子节点,如果是默认命名空间的话就采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析,判断是否为默认命名空间还是自定义命名空间的方法是使用node.getNamespaceURI()获取命名空间,并与Spring中固定的命名空间http://www.springframework.org/schema/beans进行对比,如果一致则认为时默认,否则就认为时自定义。


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring源码解析(一)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏