Spring源码学习

加载资源

首先Spring大部分功能主要是根据配置进行切入点,所以我们首先要分析的就是XmlBeanFactory

XmlBeanFactory首先调用参数为resource的构造方法:

public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);
    }

构造方法的内部又调用构造方法

 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);
    }

根据分析,加载资源主要使用的是XmlBeanDefinitionReader类进行,但是在创建创建reader之前,调用了父类的构造方法。(这个我们稍后分析)

我们首先分析参数Resource类

UML图如下:

Spring配置文件读取主要通过ClassPathResource实现的,而ClassPath的父类Resource封装了底层资源,并且对于不同来源的资源文件都有一个对应Resource实现:

  • 文件:FileSystemResource

  • Classpath资源:ClassPathResource

  • URL资源:UrlResource

  • InputStream:InputResource

  • Byte数组:ByteResource

Resourse结构:

public interface Resource extends InputStreamSource {
    //是否存在
    boolean exists();
    //是否可读
    default boolean isReadable() {
        return this.exists();
    }
    //是否打开
    default boolean isOpen() {
        return false;
    }
    //是否是文件
    default boolean isFile() {
        return false;
    }
    //转换为URL
    URL getURL() throws IOException;
    //转换为URI
    URI getURI() throws IOException;
    //转换为文件
    File getFile() throws IOException;
     //NIO管道
    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }
    //获取的资源长度
    long contentLength() throws IOException;
    //上一次修改的时间
    long lastModified() throws IOException;
    //基于当前资源创建相对资源
    Resource createRelative(String relativePath) throws IOException;
    //获取文件名
    @Nullable
    String getFilename();
    //描述信息(打印错误信息)
    String getDescription();
}

Resource接口对于资源文件进行统一处理:

Resource resource = new ClassPathSource("bean.xml");
InputStream is =  resource.getInputStream();

ClassPathSource获取输入流逻辑如下,通过class或者classLoader提高的底层方法获取到输入流

FileSystemSource获取输入流逻辑如下,简单暴力直接new了一个输入流:

有了输入流,换句话说,就是我们可以读取配置文件了,而Spring的读取配置文件全权委托给XmlBeanDefinitionReader类进行读取

ignoredDependencyInterfaces

XmlBeanDefinitionReader.reader读取文件之前,调用了父类的构造方法:

而在父类中再次调用父类的构造方法

而在该方法中,郝佳老师提醒我们关注ignoredDependencyInterfaces方法,此方法的存在的意义就是忽略给定接口的自动装配功能

书中摘要:

总体意思不是很理解,通过网上查阅资料,大致意思就是忽略自动装配类的Set方法。使其不能自动注入

举例:

普通的pojo

@Component
public class BeanB {
}

接口关键方法setBeanB

public interface BeanInterface {

    public void setBeanB(BeanB beanB);

}

需要自动装配的类beanA

public class BeanA implements BeanInterface {

    private BeanB beanB;

    public BeanB getBeanB() {
        return beanB;
    }

    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

配置类:

@Configuration
public class BeanConfig {

    //开启自动装配
    @Bean(autowire = Autowire.BY_TYPE)
    public BeanA beanA() {
        return new BeanA();
    }
}

测试类

   public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(SpringContextDemo.class);
        context.refresh();
        BeanA a = context.getBean(BeanA.class);
        System.out.println(a.getBeanB());
    }

执行结果:

beanB存在值

添加ignoreDependencyInterface方法逻辑

   public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(SpringContextDemo.class);
        //加一行代码
        context.getBeanFactory().ignoreDependencyInterface(BeanInterface.class);
        context.refresh();
        BeanA a = context.getBean(BeanA.class);
        System.out.println(a.getBeanB());
    }

执行结果:

  • 在未添加ignoreDependencyInterface方法逻辑时,Spring可以通过set方法在自动注入为BeanInterface的BeanB赋值,而beanA的自动装配需要依赖于BeanB,所以通过BeanA获取BeanB存在值

  • 添加ignoreDependencyInterface方法逻辑时,Spring忽略了BeanInterface的set方法,所以为装配BeanB,所以通过BeanA获取BeanB为null

ignoreDependencyInterface是在Bean自动装配的BY_NAME和BY_TYPE模式下忽略该接口的set方法里面的依赖注入

读取文件

当获取到字节流时,我们就可以读取配置文件,但是Spring将读取文件的任务全权委托给XmlBeanDefinitionReader,现在学习这个类

XmlBeanDefinitionReader类有一个方法,loadBeanDefinitions:

 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Loading XML bean definitions from " + encodedResource);
        }

        Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        } else {
            int var6;
            try {
                InputStream inputStream = encodedResource.getResource().getInputStream();
                Throwable var4 = null;

                try {
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }

                    var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                } catch (Throwable var24) {
                    var4 = var24;
                    throw var24;
                } finally {
                    if (inputStream != null) {
                        if (var4 != null) {
                            try {
                                inputStream.close();
                            } catch (Throwable var23) {
                                var4.addSuppressed(var23);
                            }
                        } else {
                            inputStream.close();
                        }
                    }

                }
            } catch (IOException var26) {
                throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var26);
            } finally {
                currentResources.remove(encodedResource);
                if (currentResources.isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }

            }

            return var6;
        }
    }

首先Spring对于输入的Resouce进行了编码处理,根据传入的Resouce和编码以及字符集得到一个新的经过编码的Resouce

校验参数并打印日志

通过属性来记录已经加载的资源

通过encodeResource得到Resource并获得输出流,根据输入流生成InputSource

逻辑核心部分,开始加载

关闭流,并删除已加载的资源,避免重复加载

进入方法doLoadBeanDefinitions()

这个方法就做了两件事:

  • 加载XML资源得到对应的Document

  • 注册Bean信息

校验文件类型

进入方法doLoadDocument(InputSource inputSource, Resource resource)

首先进行了XML文件类型的判断即方法getValidationModeForResource(Resource resource)

0==>NONE

1==>自动加载

2==>DTD

3==>XSD

protected int getValidationModeForResource(Resource resource) {
        //获取当前的XML文件的类型编码
        int validationModeToUse = this.getValidationMode();
        //如果不是1直接返回
        if (validationModeToUse != 1) {
            return validationModeToUse;
        } else {
            //否则执行此处
            int detectedMode = this.detectValidationMode(resource);
            return detectedMode != 1 ? detectedMode : 3;
        }
    }

方法detectValidationMode(resource)

    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);
            }
        }
    }

方法this.validationModeDetector.detectValidationMode(inputStream)

public int detectValidationMode(InputStream inputStream) throws IOException {
        try {
            //获取BufferedReader
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            Throwable var3 = null;

            int var6;
            try {
                //是否是DTD标识
                boolean isDtdValidated = false;
                
                String content;
                //一行一行读
                while((content = reader.readLine()) != null) {
                    content = this.consumeCommentTokens(content);
                    //跳过注释和空行
                    if (!this.inComment && StringUtils.hasText(content)) {
                        //如果存在“DOCTYPE”属性就是DTD
                        if (this.hasDoctype(content)) {
                            isDtdValidated = true;
                            break;
                        }
                        //读到开始符号“<” 文件类型校验一定在开始符号前
                        if (this.hasOpeningTag(content)) {
                            break;
                        }
                    }
                }
                //0==>NONE
                //1==>自动加载
                //2==>DTD
                //3==>XSD
                var6 = isDtdValidated ? 2 : 3;
            } catch (Throwable var16) {
                var3 = var16;
                throw var16;
            } finally {
                if (reader != null) {
                    if (var3 != null) {
                        try {
                            reader.close();
                        } catch (Throwable var15) {
                            var3.addSuppressed(var15);
                        }
                    } else {
                        reader.close();
                    }
                }

            }

            return var6;
        } catch (CharConversionException var18) {
            return 1;
        }
    }

加载XML文件并获取对应的Document对象

通过createDocumentBuilderFactory创建DocumentBuilderFactory,通过createDocumentBuilderFactory创建DocumentBuilder解析器对象,在根据inputSource解析返回Doucument对象

这里的逻辑不是很重要,重要的参数EntityResolver

EntityResolver

EntityResolver顾名思义就是实体分解器,实际上对于SAX而言,解析一个XML文件,SAX首先读取该文档上的声明,根据声明去寻找相应的DTD定义,一遍对文档进行一个验证。默认的寻找规则即通过网络(实际上就是声明的DTD的URI地址)来下载响应的DTD声明,并进行认证。当然,下载是一个很漫长的过程,而且当网络中断或不可用时,由于未找到想用DTD声明会报错。

而EntityResolver的作用就是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目的中的某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找响应的声明。

DTD文件:

publicId:-//mybatis.org//DTD Mapper 3.0//EN”

systemId:"http://mybatis.org/dtd/mybatis-3-mapper.dtd"

XSD文件:

publicId:null

systemId:" http://maven.apache.org/xsd/maven-4.0.0.xsd"

而在Spring中,主要是通过DelegatingEntityResolver类中的resolveEntity()方法来解析验证

而对于DTD文件:

public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
        if (logger.isTraceEnabled()) {
            logger.trace("Trying to resolve XML entity with public ID [" + publicId + "] and system ID [" + systemId + "]");
        }
        //当systemId不为空并且是以“.dtd”结尾时才会解析
        if (systemId != null && systemId.endsWith(".dtd")) {
            //获取最后“/”出现的位置
            int lastPathSeparator = systemId.lastIndexOf(47);
            //获取“Spring-beans”在最后“/”第一次出现的位置
            int dtdNameStart = systemId.indexOf("spring-beans", lastPathSeparator);
            //当不存在“Spring-beans”时
            if (dtdNameStart != -1) {
                //手动赋值
                String dtdFile = "spring-beans.dtd";
                if (logger.isTraceEnabled()) {
                    logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
                }

                try {
                    //在当前路径下查找指定的资源
                    Resource resource = new ClassPathResource(dtdFile, this.getClass());
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                    }

                    return source;
                } catch (FileNotFoundException var8) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", var8);
                    }
                }
            }
        }

        return null;
    }

对于XSD文件:

默认是去"META-INF/spring.schemas"文件夹中获取指定的资源

解析及注册Bean

Spring注册Bean主要通过registerBeanDefinitions方法实现

registerBeanDefinitions:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //实例化BeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
        //获取容器内已经注册的bean
        int countBefore = this.getRegistry().getBeanDefinitionCount();
        //加载及注册bean
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
        //此次加载及注册的bean的个数
        return this.getRegistry().getBeanDefinitionCount() - countBefore;
    }

这里的核心功能就是

documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));

我们进入registerBeanDefinitions方法:

而这个方法的核心功能就是this.doRegisterBeanDefinitions(doc.getDocumentElement());

进入doRegisterBeanDefinitions方法

protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        //专门处理解析
        this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
        //判断是否是默认路径(默认路径:"http://www.springframework.org/schema/beans")
        if (this.delegate.isDefaultNamespace(root)) {
            //处理“profile”属性
            String profileSpec = root.getAttribute("profile");
            if (StringUtils.hasText(profileSpec)) {
                //当获取的"profile"不为空时,按照“,”";"进行切割获取到字符串数组
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
                if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                    }

                    return;
                }
            }
        }
        //解析前增强
        this.preProcessXml(root);
        //解析bean
        this.parseBeanDefinitions(root, this.delegate);
        //解析后增强
        this.postProcessXml(root);
        this.delegate = parent;
    }

其实前置和后置方法都是无方法体的空方法,主要是便于子类实现,如果我们需要在Spring解析XML文件之前或者以后进行操作,那么我们就可以实现这个类并重写相关方法

我们进入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)) {
                        //解析默认标签
                        this.parseDefaultElement(ele, delegate);
                    } else {
                        //解析自定义标签
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            //解析自定义标签
            delegate.parseCustomElement(root);
        }

    }

关于默认标签的的解析我们下个章节在学习

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值