Spring框架1-容器的基本实现

spring的整体架构:

DefaultListableBeanFactory是整个bean加载的核心部分,它是Spring注册及加载bean的默认实现。

test: 

bean.xml中有很多bean的定义:

    <!-- bean 标签演示 -->
    <bean id="m416" class="com.muse.springdemo.entity.Gun">
        <property name="name" value="M416"/>
        <property name="bulletNums" value="45"/>
        <property name="desc" value="非常好用的一把枪"/>
    </bean>
    private static BeanFactory beanFactory;
    private static ApplicationContext applicationContext;

    static {
        beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
    }

(代码块1)

XmlBeanFactory继承自DefaultListableBeanFactory,不同的地方是在XmlBeanFactory中使用了自定 义的XML读取器XmlBeanDefinitionReader,实现了个性化的读取。

XmlBeanFactory():

@Deprecated
public class XmlBeanFactory extends DefaultListableBeanFactory {
    private final XmlBeanDefinitionReader reader;

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

(代码块2)

在代码块1中调用的【new XmlBeanFactory(new ClassPathResource("bean.xml"))】是XmlBeanFactory类中的第一个构造方法,该构造方法的实现是第二个构造方法,第二个构造方法的super指代的是DefaulListableBeanFactory类。

reader是用来读xml相关配置文件的,将xml中定义的bean注入到spring里。

XML配置文件的读取是Spring中重要的功能,而XmlBeanDefinitionReader可以实现该功能。这个类的继承关系:

XmlBeanDefinitionReader 和 AbstractBeanDefinitionReader就是将配置文件转换为definition。

类名解释
ResourceLoader定义资源加载器,主要应用于根据给定的资源文件地址返回对应的 Resource
DocumentLoader定义从资源文件加载到转换为Document的功能
BeanDefinitionDocumentReader定义读取Document并注册BeanDefinition功能
BeanDefinitionParserDelegate定义解析Element的各种方法
EnvironmentCapable定义获取Environment方法
BeanDefinitionReader主要定义资源文件读取并转换为BeanDefinition的各个功能
AbstractBeanDefinitionReader对EnvironmentCapable、BeanDefinitionReader类定义的功能进行 实现

创建 XmlBeanFactory

当我们希望从Spring中获取到bean时,可以先传入配置文件名称——bean.xml,创建 XmlBeanFactory实例对象,然后再调用getBean方法获得相应的bean实例。即:

XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
Gun gun = beanFactory.getBean("m416", Gun.class);

在获得BeanFactory实例对象的过程中,其实大体是经历了如下的几个步骤:

对资源文件进行加载 — — Resource

可以看到,第一步是获得了ClassPathResource。那么,Resource是Spring用于封装 底层资源的——File、URL、Classpath等。对于不同来源的资源文件都有相应的Resource 实现。

① 针对文件资源:FileSystemResource

② 针对Classpath资源:ClassPathResource

③ 针对URL资源:UrlResource

④ 针对InputStream资源:InputStreamResource

⑤ 针对Byte数组资源:ByteArrayResource ……

我们在日常开发工作中,如果需要对资源文件进行加载,也可以直接使用Spring提供的 XxxResource类。比如,我们要加载bean.xml文件,则可以将其先封装为Resource,然后 再调用getInputStream(),就可以获得到输入流了。那么针对于输入流的后续操作,与我们以往的处理方式是一样的。当然,除了能从Resource中获得InputStream之外,还可以 获得File、URI和URL等。

利用 loadBeanDefinitions(resource) 加载配置:   

loadBeanDefinitions(encodeResource) 执 行 步 骤 :

本方法就是针对EncodedResource实例对象进行bean的加载。具体执行如下3个步骤:

① 将入参encodedResource保存到currentResources中,用于记录当前被加载的资源。 如果发现已经存在了,则抛异常,终止资源加载。

② 从encodedResource中获得输入流InputStream,并创建inputSource实例对象。如 果在encodedResource中配置了编码(encoding),则为inputSource配置该编码。

③ 调用doLoadBeanDefinitions(...)方法从资源中加载bean。

需要注意的一点是,InputSource不是Spring提供的类,它的全路径名是 org.xml.sax.InputSource,用于通过SAX读取XML文件的方式来创建InputSource对象

classPathResource()类:

    public ClassPathResource(String path) {
        this(path, (ClassLoader)null);
    }

    public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }

        this.path = pathToUse;
        this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
    }

XmlBeanFactory类中:this.reader.loadBeanDefinitions(resource);

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return this.loadBeanDefinitions(new EncodedResource(resource));
    }

EncodedResource只是做了对字符集和编码的支持: 

    public EncodedResource(Resource resource) {
        this(resource, (String)null, (Charset)null);
    }
    private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
        Assert.notNull(resource, "Resource must not be null");
        this.resource = resource;
        this.encoding = encoding;
        this.charset = charset;
    }

loadBeanDefinitions(Resource resource):

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
        if (!currentResources.add(encodedResource)) {  // {@code true} if this set did not already contain the specified element  如果集合不包含该元素,返回true,如果已经包含了,返回false,加上!,则返回true,抛出异常;如果不包含,则把2资源加入set,并在!的作用下返回false,不抛异常。
            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());
                }             
            return var6;
        }
    }

doLoadBeanDefinitons(…)执行:真正地实现加载的一些行为:

在这个方法中,主要做了 两件事件:

① 加载XML配置文件,然后将其封装为Document实例对象。(Xml --> Resource--> Document --> BeanDefinition)

② 根据Document实例和Resource实例,执行Bean的注册。

this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            Document doc = this.doLoadDocument(inputSource, resource);
            int count = this.registerBeanDefinitions(doc, resource);
            return count;

this.doLoadDocument(inputSource, resource);

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

this.getValidationModeForResource(resource):

    protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = this.getValidationMode();   // 1
        if (validationModeToUse != 1) {
            return validationModeToUse;
        } else {
            int detectedMode = this.detectValidationMode(resource); // resource包含的是bean.xml文件
            return detectedMode != 1 ? detectedMode : 3;
        }
    }

detectValidationMode(Resource 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;
            IOException ex;
            try {
                inputStream = resource.getInputStream();
            } 
            try {
                return this.validationModeDetector.detectValidationMode(inputStream);
            } 
        }
    }

detectValidationMode(inputStream)

    public int detectValidationMode(InputStream inputStream) throws IOException {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            Throwable var3 = null;
            int var6;
            try {
                boolean isDtdValidated = false;
                String content;
                // 一行一行遍历bean.xml文件
                while((content = reader.readLine()) != null) {  // 配置文件的每一行:例如第一行:<?xml version="1.0" encoding="UTF-8"?>
                    content = this.consumeCommentTokens(content);
                    if (!this.inComment && StringUtils.hasText(content)) {
                        if (this.hasDoctype(content)) {  // return content.contains("DOCTYPE");
                            isDtdValidated = true;
                            break;
                        }

                        if (this.hasOpeningTag(content)) {
                            break;
                        }
                    }
                }
                var6 = isDtdValidated ? 2 : 3;
            } 
            return var6;
        } 
    }

xml 配 置 文 件 模 式 — — ValidationMode

DTD(Document Type Definition):它是一种XML约束模式语言,要使用DTD验证模式 的时候需要在XML文件的头部声明,并且它引用的是后缀名为.dtd的文 件。如下所示:

XSD(XML Schemas Definition):用于描述XML文档的结构。它引用的是后缀名为.xsd 的文件。如下所示:

doLoadDocument(InputSource inputSource, Resource resource) 中的.getEntityResolver():

    protected EntityResolver getEntityResolver() {
// entityResolver 默认为空
        if (this.entityResolver == null) {
            ResourceLoader resourceLoader = this.getResourceLoader();
            if (resourceLoader != null) {
                // 内部依然是将resourceLoader传给DelegatingEntityResolver去创建
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            } else {
                this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

最终都是要通过调用DelegatingEntityResolver的构造方法,其内部实现:

① BeansDtdResolver:是直接截取systemId最后的xx.dtd,然后去当前路径下寻找。

② PluggableSchemaResolver:是默认到META-INF/spring.schemas文件中找到 systemId所对应的XSD文件并加载。

DelegatingEntityResolver():

    public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
        this.dtdResolver = new BeansDtdResolver();
        this.schemaResolver = new PluggableSchemaResolver(classLoader);
    }

BeansDtdResolver():加载spring-beans.dtd

    private static final String DTD_EXTENSION = ".dtd";
    private static final String DTD_NAME = "spring-beans";
    private static final Log logger = LogFactory.getLog(BeansDtdResolver.class);
    @Nullable
    public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
        if (systemId != null && systemId.endsWith(".dtd")) {
            int lastPathSeparator = systemId.lastIndexOf(47);
            int dtdNameStart = systemId.indexOf("spring-beans", lastPathSeparator);
            if (dtdNameStart != -1) {
                String dtdFile = "spring-beans.dtd";                
                try {
                    Resource resource = new ClassPathResource(dtdFile, this.getClass());
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);                    
                    return source;
                } 
            }
        }
        return null;
    }

public和systemId是什么:

在接口EntityResolver中,只声明了一个方法resolveEntity(String publicId, String systemId),如果是下图中的XSD的配置文件,那么:

publicId=null

systemId=http://www.springframework.org/schema/beans/spring-beans.xsd

如果是下图中的DTD的配置文件,那么:

publicId=-//SPRING//DTD BEAN 2.0//EN

systemId=https://www.springframework.org/dtd/spring-beans-2.0.dtd

PluggableSchemaResolver(classLoader):

    public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
        this.classLoader = classLoader;
        this.schemaMappingsLocation = "META-INF/spring.schemas";
    }

doLoadDocument(InputSource inputSource, Resource resource)  的 .loadDocument()

    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
        DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            Document doc = this.doLoadDocument(inputSource, resource);
            int count = this.registerBeanDefinitions(doc, resource);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
}

registerBeanDefinitions(doc, resource):返回新加载了几个bean的定义

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();   // 创建DefaultBeanDefinitionDocumentReader对象
        int countBefore = this.getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));  // 解析并注册bean
        return this.getRegistry().getBeanDefinitionCount() - countBefore;
    }

由于BeanDefinitionDocumentReader只是接口,所以通过createBeanDefinitionDocumentReader()方法其实创建的是它的实现类—— DefaultBeanDefinitionDocumentReader。

.createBeanDefinitionDocumentReader():

    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return (BeanDefinitionDocumentReader)BeanUtils.instantiateClass(this.documentReaderClass);
    }
    public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
        Assert.notNull(clazz, "Class must not be null");
        if (clazz.isInterface()) {
            throw new BeanInstantiationException(clazz, "Specified class is an interface");
        } else {
            try {
                return instantiateClass(clazz.getDeclaredConstructor());
            } catch (NoSuchMethodException var3) {
                NoSuchMethodException ex = var3;
                Constructor<T> ctor = findPrimaryConstructor(clazz);
                if (ctor != null) {
                    return instantiateClass(ctor);
                } else {
                    throw new BeanInstantiationException(clazz, "No default constructor found", ex);
                }
            } catch (LinkageError var4) {
                LinkageError err = var4;
                throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
            }
        }
    }
    private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

.registerBeanDefinitions(doc, this.createReaderContext(resource)):

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        this.doRegisterBeanDefinitions(doc.getDocumentElement());
    }
    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.isDebugEnabled()) {
                        this.logger.debug("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;
    }

profile最主要的目的就是可以区分不同的环境,进而对不同环境进行配置。例如在dev、 test、preonline、online等环境有各自的配置文件,我们就可以通过设置profile来在特定 的环境下读取对应的配置文件。使用方式如下所示:

delegate为空,创建一个:

    protected BeanDefinitionParserDelegate createDelegate(XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
        BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
        delegate.initDefaults(root, parentDelegate);
        return delegate;
    }
    public void initDefaults(Element root, @Nullable BeanDefinitionParserDelegate parent) {
        this.populateDefaults(this.defaults, parent != null ? parent.defaults : null, root);
        this.readerContext.fireDefaultsRegistered(this.defaults);
    }

this.delegate.isDefaultNamespace(root):判断是不是默认的命名空间

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

this.parseBeanDefinitions(root, this.delegate):

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();  // 获得bean的列表?

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

    }

标签解析操作:

要执行xml解析的关键方法parseBeanDefinitions(root, this.delegate),它会根据表空间 来判断,是进行默认标签解析还是自定义标签解析。相关源码如下所示:

 

如果命名空间URI等于 "http://www.springframework.o rg/schema/beans"或者没有配置命名空间URI,则表示是默认表 空间,否则是自定义表空间

默认标签:<bean id = "m416" class = "com.muse.springbootdemo.entity.Gun">

自定义标签:<tx:annotation-driven>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值