2. spring初级容器XmlBeanFactory初始化

一、本文目录

  1. spring的简单使用方法
  2. spring初级容器XmlBeanFactory简单介绍
  3. spring初级容器XmlBeanFactory初始化

二、spring的简单使用

  1. 我们首先新建一个Student类,作为我们的示例bean
  2. spring的初衷,就是装载一个一个的bean,这些bean,其实就是简单的Java对象
public class Student {
    private String name="JHXY";

    private int age;
   // 省略getter、setter、toString方法
  1. spring对应的applicationContext.xml文件中配置student实例bean
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="student" class="com.jhxy.common.Student"/>
</beans>
  1. 测试代码
public static void main(String[] args) {
      
        XmlBeanFactory xmlBeanFactory =
                new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

        // 从XmlBeanFactory即:spring容器中,获取student bean
        Student student = (Student) xmlBeanFactory.getBean("student");

        System.out.println(student.getName());
    }
  1. 测试结果


    image.png
  2. XmlBeanFactory基本工作原理

  1. spring最基本的容器:XmlBeanFactory,我们熟知的ApplicationContent相当于spring的高级容器
  2. ApplicationContenxt高级容器在XmlBeanFactory基础上,添加了很多扩展功能和特性
  3. ClassPathResource封装了applicationContext.xml文件,作为XmlBeanFactory构造方法参数,创建XmlBeanFactory

三、spring初级容器XmlBeanFactory简单介绍

  1. 通过下载Spring源码,使用Intellij进行代码调试,我们已经将spring的源码下载到了本地,通过Intellij进行源码阅读,通过上面的简单使用,我们知道,spring的初级容器XmlBeanFactory在初始化的时候,其实就是通过简单的new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));创建spring的初级容器
  2. 首先,我们来看下构造函数的参数,ClassPathResource对应的Resource是什么
  3. 我们先来看下ClassPathResource对应的类继承关系


    Resource类继承图
  4. 我们可以看到
  1. ClassPathResource实现了Resource接口
  2. Resource接口继承了InputStreamSource
  3. 和ClassPathResource相似的还有InputStreamResource,ByteArrayResource,FileSystemResource
  1. 其实spring将所有的资源都抽象成一个InputStreamSource,这样,不同的来源使用不同的实现类。
  2. Resource 接口中的方法


    Resource接口中的方法
  1. exists():对资源状态的判断,资源是否存在
  2. isReadable():对资源状态的判断,是否是可读状态
  3. isOpen():对资源状态的判断,资源是否打开状态
  4. isFile():对资源状态的判断,判断是否是文件类型
  1. 通过类继承图可以知道,Resource接口继承了InputStreamSource,这就意味着所有的资源只要封装了Resource接口,就可以通过调用InputStreamSource的getInputStream方法来获取资源对应的InputStream输入流了
  2. 而资源是多种多样的,我们平时项目中的applicationContext.xml其实就是项目的classpath下的xml,ClassPathResource就是用来加载classpath路径下的资源文件
  3. 所以,各种Resource是如何加载资源的,我们通过示例中的ClassPathResource的getInputStream方法可以看出,ClasspathResource就是通过class或者classLoader的底层方法来加载的
/**
     * This implementation opens an InputStream for the given class path resource.
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see java.lang.Class#getResourceAsStream(String)
     */
    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        else {
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }
  1. 综上,我们可以得出结论:Resource就是spring内部资源的一个抽象,而InputStreamResource的接口实现,使我们对各种来源的资源都可以轻松的获取对应的输入流InputStream

四、spring初级容器XmlBeanFactory的初始化

  1. 我们知道,创建spring初级容器XmlBeanFactory,通过XmlBeanFactory构造函数直接创建,先来看下XmlBeanFactory的构造函数
public class XmlBeanFactory extends DefaultListableBeanFactory {

    //XmlBeanDefinitionReader用于读取资源的reader组件
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);


    /**
     * Create a new XmlBeanFactory with the given resource,
     * which must be parsable using DOM.
     * @param resource the 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 the 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);
        //通过 XmlBeanDefinitionReader进行加载资源
        this.reader.loadBeanDefinitions(resource);
    }

}
  1. XmlBeanFactory构造函数中,首先要调用父类的构造方法,我们一路走下去,最终走到AbstractAutowireCapableBeanFactory中
  1. 在AbstractAutowireCapableBeanFactory中,ignoreDependencyInterface方法设置了一些类,分别是BeanNameAware、BeanFactoryAware、BeanClassLoaderAware
    2.ignoreDependencyInterface方法,就是将参数中的类添加到集合ignoredDependencyInterfaces中
public AbstractAutowireCapableBeanFactory() {
        super();
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }
  1. 我们再来看下这三个感知接口的类继承关系图


    感知接口Aware类继承图
  2. 通过接口名字Aware,应该能猜到,这些都是感知相关的接口,当bean实现了这些接口,在spring实例化bean的时候,就可以通过感知接口中的方法注入相应的数据
  3. 我们首先通过一个例子来看下,BeanNameAware的作用
public class Student implements BeanNameAware{
    private String name="JHXY";
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("beanName:" + name);
    }
}

applicationContext和之前的配置一样,测试代码

 public static void main(String[] args) {
        XmlBeanFactory xmlBeanFactory =
                new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        // 从XmlBeanFactory即:spring容器中,获取student bean
        Student student = (Student) xmlBeanFactory.getBean("student");
        System.out.println(student.getName());
    }

测试结果


BeanNameAware感知接口测试结果
  1. 通过测试结果可以看到,BeanNameAware中的setBeanName方法被调用,并且bean name就是我们在zpplicationContext中配置的Student 的id的属性值,student
  2. 那现在有个疑问,为什么Student实现了BeanNameAware接口之后,setBeanName方法就会被调用?setBeanName什么时候被调用,调用之后为什么就能拿到beanName?不着急,我们继续看
  3. ignoreDependencyInterface方法是干什么的?通过注释我们可以知道,在自动装配的时候,忽略指定的接口依赖
   /**
     * Ignore the given dependency interface for autowiring.
     * <p>This will typically be used by application contexts to register
     * dependencies that are resolved in other ways, like BeanFactory through
     * BeanFactoryAware or ApplicationContext through ApplicationContextAware.
     * <p>By default, only the BeanFactoryAware interface is ignored.
     * For further types to ignore, invoke this method for each type.
     * @see org.springframework.beans.factory.BeanFactoryAware
     * @see org.springframework.context.ApplicationContextAware
     */
      public void ignoreDependencyInterface(Class<?> ifc) {
        this.ignoredDependencyInterfaces.add(ifc);
    }
  1. 我们顺藤摸瓜,看看ignoredDependencyInterfaces集合在哪里被调用,如下代码

根据注释,一个bean的属性,是否要从依赖排查中剔除,这句话什么意思呢?

也就是说,我们一个bean的某个属性,是否要被注入对应的依赖,还要看一下你这个属性对应的类是否实现了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware这些接口

/**
     * Determine whether the given bean property is excluded from dependency checks.
     * <p>This implementation excludes properties defined by CGLIB and
     * properties whose type matches an ignored dependency type or which
     * are defined by an ignored dependency interface.
     * @param pd the PropertyDescriptor of the bean property
     * @return whether the bean property is excluded
     * @see #ignoreDependencyType(Class)
     * @see #ignoreDependencyInterface(Class)
     */
    protected boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) {
        return (AutowireUtils.isExcludedFromDependencyCheck(pd) ||
                this.ignoredDependencyTypes.contains(pd.getPropertyType()) ||
                AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces));
    }

我们继续向下看AutowireUtils.isSetterDefinedInInterface()

在isSetterDefinedInInterface方法中

1.bean对应的属性,是否实现了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware
2.bean属性对应的setter方法,在这三个感知接口中是否也有相同的方法
如果满足以上两种情况,isSetterDefinedInInterface就会返回true,spring在自动装配这个bean时,就不会为这个属性注入值


image.png
  1. 我们还是通过一个案例来看下
public class BeanNameAwareTest implements BeanNameAware {
    private String beanName;
    public String getBeanName() {
        return beanName;
    }
    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
}

我们将属性设置为beanName,且该类实现了BeanNameAware接口,这样,setBeanName方法即是beanName的setter方法,且在感知接口中也有setBeanName方法
满足了这两个条件之后,spring就不会为bean beanNameAwareTest的属性beanName注入任何值

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

   <bean id="beanNameAwareTest" class="org.springframework.jhxy.BeanNameAwareTest">
        <property name="beanName" value="beanName"/>
    </bean>
</beans>

我们为属性beanName设置了属性值“beanName”,我们通过测试结果来看下“beanName”这个值能否注入到bean beanNameAwareTest对应的beanName属性中

public static void main(String[] args) {

        XmlBeanFactory xmlBeanFactory =
                new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        BeanNameAwareTest beanNameAwareTest = (BeanNameAwareTest) xmlBeanFactory.getBean("beanNameAwareTest");
        System.out.println(beanNameAwareTest.getBeanName());
    }

测试结果
可以看到,我们设置的beanName的属性并没有变成"beanName",拿到的依旧是beanNameAwareTest,所以说,如果一个bean实现了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware的话,并且想通过spring自动装配给属性赋值,那么属性的setter方法,就不能和感知接口中的setter方法相同。
spring这样设计,主要是想,如果实现了BeanNameAware,对应的beanName属性值,就应该是这个bean在spring容器中的名字,此时,如果我们从外部xml或者注解中,注入一个新的bean的名称,spring默认就会忽略掉外部注入的名称,确保bean的名称唯一


image.png

OK
我们接着向下看初始化spring容器的逻辑

  1. XmlBeanDefinitionReader如何加载资源,这里我们可以看到,会将Resource封装成EncodedResource
/**
     * Load bean definitions from the specified XML file.
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //将Resource封装太EncodedResource
        return loadBeanDefinitions(new EncodedResource(resource));
    }
  1. 我们看下EncodedResource部分关键源码
public class EncodedResource implements InputStreamSource {

    //这里的resource是跟资源有关的
    private final Resource resource;
    //编码
    @Nullable
    private final String encoding;
    //字符集
    @Nullable
    private final Charset charset;


    /**
     * Create a new {@code EncodedResource} for the given {@code Resource},
     * not specifying an explicit encoding or {@code Charset}.
     * @param resource the {@code Resource} to hold (never {@code null})
     */
    //构造方法中,除了资源resource不为空,编码和字符集都为空
    public EncodedResource(Resource resource) {
        this(resource, null, null);
    }
/**
     * Open a {@code java.io.Reader} for the specified resource, using the specified
     * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
     * (if any).
     * @throws IOException if opening the Reader failed
     * @see #requiresReader()
     * @see #getInputStream()
     */
    //通过上面的构造方法可知,默认charset和encoding为null,所以getReader(),根据资源获取resource的输入流
    public Reader getReader() throws IOException {
        if (this.charset != null) {
            return new InputStreamReader(this.resource.getInputStream(), this.charset);
        }
        else if (this.encoding != null) {
            return new InputStreamReader(this.resource.getInputStream(), this.encoding);
        }
        else {
            return new InputStreamReader(this.resource.getInputStream());
        }
    }
// 省略部分代码.....
  1. 接着loadBeanDefinitions继续向下看
/**
     * Load bean definitions from the specified XML file.
     * @param encodedResource the resource descriptor for the XML file,
     * allowing to specify an encoding to use for parsing the file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }

        //获取EncodedResource的输入流
        try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            //正式开始加载资源
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

/**
     * Actually load bean definitions from the specified XML file.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #doLoadDocument
     * @see #registerBeanDefinitions
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            //将传进来的inputSource和resource,封装成Document对象
            Document doc = doLoadDocument(inputSource, resource);
            //解析document对象,并将解析的bean注入到spring中
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
        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);
        }
    }

/**
     * Actually load the specified document using the configured DocumentLoader.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the DOM Document
     * @throws Exception when thrown from the DocumentLoader
     * @see #setDocumentLoader
     * @see DocumentLoader#loadDocument
     */
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

spring在加载的过程中,将XmlBeanDefinitionReader委托给DocumentLoader进行加载
加载xml时,将会按照xml规范和格式进行加载xml中 的bean

结尾

  1. 受限于篇幅问题,我们下一篇笔记接着学习spring加载XmlBeadFactory


    02_spring基础数据准备.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容