[spring]applicationContext.xml详解一beans标签

一、概述

今天来学习一下Spring的核心配置文件applicationContext.xml,主要来看一下其中经常使用的标签,当然由于Spring早就支持注解的方式,所以我们以后的侧重点将也是注解方式,但对于applicationContext.xml中的各项配置,其实与注解是很像的,等我们理解了xml配置之后,很容易就理解注解配置了。

注:学习的Spring版本是4.3.14。

二、各种标签

1. beans标签

Spring配置文件的根元素是beans节点,在该节点内,我们可以配置Spring内置的各种命名空间以及bean默认的几项配置,通过配置各种命名空间,然后使用各命名空间的元素来完成对Spring的配置。

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

前面我们说过命名空间,这里就不多说了。Spring内置了大约10种左右的命名空间供我们选择配置:

命名空间 用途
aop 为声明切面及将@AspectJ注解的类代理为Spring切面提供了配置元素
beans 声明及配置Bean,是Spring最核心也是最原始的命名空间
context 为配置Spring应用上下文提供了配置元素,包括自动检测,自动装配bean等
jee 提供了于Java EE API的集成,比如JNDI与EJB
jms Java消息相关的配置
lang 支持配置由Groovy、JRuby或BeanShell等脚本实现的Bean
mvc 提供Spring MVC相关的配置,比如控制器,拦截器,视图管理器等相关
oxm 支持Spring的对象到XML的映射配置
tx 声明式的事务配置
util 提供各种各样的工具类元素配置,包括把集合配置为Bean,支持属性占位符元素

当然,Spring的命名空间不止如此,只是展示了我们常用的而已,其他的等我们用到的时候再了解不迟。而我们如果需要某个命名空间下的元素时,引入相应的命名空间即可。比如说我们使用到了context命名空间:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:property-placeholder ignore-resource-not-found="false" ignore-unresolvable="false"/>
</beans>

接下来,我们来看下beans标签中除了命名空间外的其他默认元素。

1.1 default-init-method元素

如果在上下文中定义的很多bean都有相同名字的初始化方法,我们没有必要为每一个bean声明init-method属性,这时候我们就可以使用beans元素的default-init-method属性。该属性为应用上下文中所有的bean设置了共同的初始化方法。

1.2 default-destory-method元素

这个元素于上述default-init-method类似,该属性是用于方法的销毁。

1.3 default-lazy-init元素

用于延迟加载,我们都知道,一般情况下,Spring在启动的时候会初始化所有相关的bean,这样就会出现启动的时候会特别慢的问题。这时候可以配置该属性设置为延迟加载,用到的时候再去加载。当然如果该配置文件配置了bean标签或者引入了其他的配置文件,以具体的bean中的配置或其他配置文件的配置优先;

  • true,设置为true,则所有相关的bean的初始化被延迟加载;
  • false,默认为false,即不延迟加载;
  • 假如设置为延迟加载后,记得考虑比如定时任务等相关的操作,所以是否使用该属性,按需选择;
1.4 default-autowire元素

Spring中的bean默认注入的方式。一般情况下,当我们需要往一个bean里注入另一个bean的时候,如果没有配置该属性,就需要手动通过ref标签引入。我们以一个简单的例子来看下:
(1). 两个对象:Student,Score

public class Student {
    private String id;
    private String name;
    private Score score;
}

public class Score {
    private Integer math;
}

(2). bean配置:

<bean id="student" class="entity.Student">
    <property name="id" value="id"/>
    <property name="name" value="name"/>
</bean>

<bean id="score" class="entity.Score">
    <property name="math" value="100"></property>
</bean>

(3). main方法

public static void main(String[] args) {
    ApplicationContext ac=new ClassPathXmlApplicationContext("/resource/applicationContext.xml");
    Student student= ac.getBean("student", Student.class);
    System.out.println(student.getScore().getMath());
}

首先,我们配置default-autowire属性,也就是默认不自动注入,这时候,我们如果直接获取的话,就获取不到Score对象,提示空指针异常:

Exception in thread "main" java.lang.NullPointerException
    at test.Main.main(Main.java:15)

我们配置default-autowire属性为byName之后,再看下结果:

100

这时候打印成功,也就是说,如果我们不配置该属性的话,需要手动使用ref标签来引用:

<bean id="student" class="entity.Student">
        <property name="id" value="id"/>
        <property name="name" value="name"/>
        <property name="score" ref="score"/>
    </bean>

当然,如果我们的对象是单个对象的话,那无论配置是否配置,都没什么影响。

下面我们来看下该属性的几个可选值:

  • no,不使用自动注入,所以该Bean对象中其他Bean的引用必须通过ref标签来引用;
  • byName,把与beanA的属性具有相同名字的beanB自动注入到beanA的对应属性中,如果没有,则不注入。这里的相同指的是beanB配置的id或name其中一个相同即可;
  • byType,与byName相对,这里指的是相同类型,也就是相同的class,这种情况下,由于根据类型来注入,所以beanB只能有一个存在,否则会编译不通过;
  • constructor,与byType类似,通过构造方法的参数来匹配,如果没有构造方法,或没有与构造方法参数类型一致的bean,则会提示异常;

这里判断是否存在类型一致先通过byType判断,如果只有一个类型的bean存在,则成功;如果有多个类型一致的,再根据byName,如果只有一个name相同的,则成功,否则失败,提示异常;

  • default,默认配置,由上级标签<beans>的default-autowire属性确定使用哪种装配方式,该属性一般配置于具体的bean标签中,而配置在beans标签中和配置为no类似。

注意:在以前版本中,default-autowire还支持一个可选值:autodetect,不过在Spring4之后已经废弃掉。

还有两点简单说下:

  1. default-lazy-init类似,如果bean中配置了autowire属性,则具体bean中的注入类型的优先级将高于beans里的配置;
  2. 当然,配置了该属性后可能会导致我们对其中的细节不够了解,也会弱化对象间的关系,出现问题的时候可能不太好定位,所以我们可以根据需要选择配置或不配置default-autowire
1.5 default-autowire-candidates元素

自动注入bean的候选者,比如说,default-autowire中我们使用byType的时候通常只能有一个类型为ClassA的bean。但是我们想在配置文件中有多个类型为ClassA的bean,就可以通过配置default-autowire-candidates来作为自动注入bean的候选者;

<beans ...   
   default-autowire-candidates="*score" default-autowire="byType">
    <bean id="student" class="entity.Student">
        <property name="id" value="id"/>
        <property name="name" value="name"/>
    </bean>

    <bean id="score2" class="entity.Score" >
        <property name="math" value="100"></property>
    </bean>

    <bean id="score" class="entity.Score" >
        <property name="math" value="200"></property>
    </bean>
</beans>

在上面这个小例子中,我们通过配置该属性为*socre,让容器在进行自动注入的时候选择name或id以score结尾的bean来进行注入。

  1. default-autowire-candidates的值可以是某个bean的id,也可以是匹配字符串,比如*score,则会将以score结尾的作为候选注入的bean,也可以指定多个字符串,通过逗号进行分割;
  2. A default bean name pattern for identifying autowire candidates:
    e.g. "*Service", "data*", "*Service*", "data*Service".
    Also accepts a comma-separated list of patterns: e.g. "*Service,*Dao".
  3. 有关候选bean的细节,我们可以查看官方文档中bean的属性autowire-candidate的文档来更深一步学习。
1.6 default-merge元素

这个属性用的可能不太多,这是用于集合的继承相关的操作。比如说,如果父类Bean包含list,map,set等一些集合元素,那么继承父类的子类将自动继承父类Bean的集合元素;当然,子类Bean可以选择合并父类的集合元素,也可以选择替换父类的的集合元素,Spring通过beans标签的default-merge和bean标签内的list标签的merge来支持这种操作;

我们通过简单的例子来看一下:定义对象Tutorial:

public class Tutorial {
    private String name;
    private List topicsList = new ArrayList<>();
}

配置两个bean,先不配置default-merge属性:

<bean id="parent" class="entity.Tutorial">
    <property name="name" value="Java parent"/>
    <property name="topicsList">
        <list>
            <value>Java Core</value>
            <value>Java Concurrency</value>
        </list>
    </property>
</bean>

<bean id="children" parent="parent">
    <property name="name" value="Java children"/>
    <property name="topicsList">
        <list>
            <value>EJB</value>
            <value>Servlet</value>
        </list>
    </property>
</bean>

指定parent的Bean集合数据为[Java Code, Java Concurrency],而children中的集合数据为[EJB, Servlet],这时候我们测试下chidren是否合并了父类的集合元素:

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/resource/applicationContext.xml");
    try {
        Tutorial parent = context.getBean("parent", Tutorial.class);
        Tutorial children = context.getBean("children", Tutorial.class);
        // 获取子类数组
        List childrenList = children.getTopicsList();
        // 获取父类数组
        List parentList = parent.getTopicsList();
        System.out.println(children.getName() + " contain " + parent.getName() + "? " +
                childrenList.containsAll(parentList));
        System.out.println(children.getName() + ": " + childrenList);
    } finally {
        context.close();
    }
}

打印结果为:

Java children contain Java parent? false
Java children: [EJB, Servlet]

可以看到,children并没有合并父类的集合元素,只包含本身的元素。我们设置default-merge=true,再来看一下结果:

Java children contain Java parent? true
Java children: [Java Core, Java Concurrency, EJB, Servlet]

此时,我们可以看到,子类已经合并了父类的集合元素了。

从上面这个简单的小例子我们大概了解了该标签的用处,该标签有truefalse两个可选值,默认情况下是false,就是不合并父类的集合元素。同理,如果 list 标签配置了merge属性,则会覆盖掉beans标签里的该项配置。如果要查看更多实例,可以参考:Spring Collection Merging Example

2. bean标签

bean元素是Spring中最基本的配置单元,用来管理对象。通过该元素,Spring将创建一个对象,并在容器加载的时候实例化该对象。我们来看一下它的一些属性:

2.1 id属性

bean的唯一标识,命名格式必须符合XML中ID属性的命名规范,不能有特殊字符;

在Spring3.1版本之前,该id属性被定义为xsd:ID,通过XML的id规范限制了它的唯一性,而在Spring3.1中,该id属性被定义为xsd:string类型,尽管不再使用XML解析器,但id的唯一性仍由容器强制执行。

2.1 name属性

bean的名称,命名可以比较随意,可以有特殊字符,并且一个bean可以有多个名称:name="name1,name2,name3",用逗号或分号或空格隔开(当然,在同一个name配置中,只能有一种符号分割,不能同时有多种)。name可以算作是 id 的别名,我们在获取bean的时候,可以通过id,也可以通过name来获取。在没有id的情况下,则name的第一个名称默认为id;

  1. 同一个Spring配置文件中,id,name是不能重复的;但如果一个Spring从多个配置文件中加载,则多个配置文件中是允许id或者name重复的,但后面加载的bean会覆盖掉前面加载的bean;
  2. 如果一个bean标签未指定id,name属性,则Spring会给一个默认的id,值为类的全名,比如类的路径是com.company.Score,则默认的id值是com.company.Score
  3. 如果多个同类型的bean未指定id,name属性,则Spring会按照其出现的次序,分别给其指定 id 为类全名#1类全名#2

举个小例子看一下:

<bean class="entity.Score">
    <property name="math" value="100"/>
</bean>

<bean class="entity.Score">
    <property name="math" value="200"/>
</bean>

<bean class="entity.Score">
    <property name="math" value="300"/>
</bean>

获取bean的方式:

Score score = ac.getBean("entity.Score", Score.class);
Score score1 = ac.getBean("entity.Score#1", Score.class);
Score score2 = ac.getBean("entity.Score#2", Score.class);
System.out.println(score.getMath());
System.out.println(score1.getMath());
System.out.println(score2.getMath());

打印结果:

100
200
300

id和name这段解释可以参考官网:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/beans.html#beans-beanname

2.3 class属性

顾名思义,该属性就是当前对象所引用的类的全路径。

 <bean name="student2,student3,student4" class="entity.Student"/>
2.4 autowire属性

在上面beans标签的学习中,我们已经了解过default-autowire属性,这两个属性是一样的,用于注入配置,只不过一个是全局默认的,一个是针对某一个bean的。在bean中配置完autowire属性后,该bean的注入方式会覆盖掉beans标签所配置的默认注入方式。该属性的配置可选值和default-autowire一样,就不多说了。

2.5 autowire-candidate属性

  同样,该属性和beans标签中的default-autowire-candidates元素类似,意思就注入bean的候选者。再多说一句,前面我们说到配置有autowire属性的bean,Spring在实例化某个bean的时候会在容器中查找匹配的bean,然后通过autowire的配置对bean进行属性注入,这些被查找的bean我们称为候选bean;
  该属性的配置有三个值:truefalsedefault。配置为false的时候意味着将该bean从候选bean中排除掉,也就是注入的时候不注入该bean;

另外再说两点:

  1. 该属性只影响到基于类型的注入,而不会影响到名称的引用。也就是如果是基于name的autowire,该属性将不会有影响。
  2. 同样,bean标签中的autowire-candidate属性的值将优先于beans标签的default-autowire-candidates配置。我们以一个例子来说一下:
<beans ...
    default-autowire="byType" default-autowire-candidates="*score1">
    
    <bean id="score" class="entity.Score" autowire-candidate="true">
        <property name="math" value="100"/>
    </bean>
    
    <bean class="entity.Score" >
        <property name="math" value="200"/>
    </bean>
    
    <bean id="student" class="entity.Student" >
        <property name="id" value="id"/>
        <property name="name" value="name"/>
    </bean>
</beans>

通过beans标签的default-autowire-candidates属性设置同类型的候选bean是id以socre1结尾的bean,而我们的bean的id是score,但我们配置了该bean的autowire-candidate为true,由于bean的优先级高,所以能注入成功。

2.6 destroy-method属性和init-method属性

同样,在上文beans中,我们已经介绍过default-init-method了,这两个方法用于该bean的初始化方法和销毁方法。也就是,当Spring容器加载该bean的时候,会调用init-method执行初始化操作,然后当Spring容器销毁该bean的时候(比如调用close方法),会调用destroy-method来执行销毁操作。

public static void main(String[] args) {
    ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("/resource/applicationContext.xml");
    Student student= ac.getBean("student", Student.class);
    System.out.println(student.getScore().getMath());
    ac.close();
}

这里还需要说下,destroy-method只有在bean的scope配置为singleton,也就是单例模式下才会生效,原因等我们接下来学习到scope的时候再细说。

2.7 lazy-init元素

  前文beans里说过,为了避免Spring容器启动的时候加载所有的bean,导致启动很慢的情况,所以beans标签提供了default-lazy-init标签来设置是否开启懒加载模式。
  同样,bean标签里的lazy-init元素也是用于对单个bean判断,是否需要开启懒加载机制,这样如果开启的话,那该bean的加载将不再是容器启动的时候进行调用,而是在第一次进行getBean的时候才进行加载。

并且,该元素也是有truefalse可选,默认情况下是false,即不开启懒加载机制。而且bean里lazy-init的优先级也是高于最外层beans标签中default-lazy-init元素的。

我们还是用上文的Student对象,添加一个默认构造方法:

public class Student {
    private String id;
    private String name;
    private Score score;

    public Student() {
        System.out.println("test lazy init by construct");
    }
}

然后先不设置lazy-init元素,即采用默认值:

<bean id="student" class="entity.Student" >
    <property name="id" value="id"/>
    <property name="name" value="name"/>
</bean>

然后测试下:

ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("/resource/applicationContext.xml");

可以看到,我们的构造方法被调用了:

test lazy init by construct

也就是说,我们只是启动了容器,并没有去直接获取bean,但构造方法还是被执行了。

接下来,我们来配置下该属性为true,然后再看下结果:

<bean id="student" class="entity.Student" lazy-init="true">
    <property name="id" value="id"/>
    <property name="name" value="name"/>
</bean>

再看下打印结果,可以看到,程序并没有执行我们的构造方法。然后我们手动获取下bean:

ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("/resource/applicationContext.xml");
Student student= ac.getBean("student", Student.class);

然后再看下打印结果:

test lazy init by construct

这时候又执行了我们的构造方法。可以看到,程序在懒加载的时候,会延迟到第一次调用该bean的时候才进行加载该bean,这样对于程序启动的压力就会少许多。但或许会有额外的问题,比如说实例化的bean配置有问题,而这个问题也会延迟到运行的时候才会发觉。

不过这里再多说一点:

  1. 如果一个bean1设置了立即加载,而该bean引用了一个延迟加载的bean2,那么bean1在容器启动的时候被实例化,而bean2由于也被bean1引用,所以也会立即实例化,而这种情况也符合延迟加载的bean在第一次调用时才被实例化的规则;
  2. 对于定时任务类似的这种东西,一般是不设置为延迟加载的;
2.8 abstractparent标签

  很多时候,我们为了避免bean中属性的重复,会用到另外两个元素abstractparent。使用标签abstract修饰的bean被称为抽象的bean,这种bean不是用于实例化,而是用于继承的情况,也就是用于具体的子类继承的父类bean。该元素有truefalse两个值,默认情况下该值是false,当指定为true的时候,就是抽象bean,无法实例化。
  该值一般配合bean的另一个元素parent来使用,parent用于bean中的继承关系,使用该元素,可以继承父类的属性。这种子类bean的出现,是为了对父类bean中公共属性的封装,避免XML中属性的重复。

  1. Spring中的抽象bean和Java中的抽象类有相同点,但也有不同点。比如说都不能直接实例化,只能通过子类实例化的时候,先实例化父类,再实例化子类,相当于间接的实例化父类;
  2. 另一点是Spring中的抽象bean甚至不需要映射到某个类就可以被子类所引用,比如:
<bean id="test1" abstract="true">
    <property name="name" value="name"/>
    <property name="id" value="id"/>
</bean>
<bean id="children" class="entity.Children" parent="test1">
    <property name="test" value="test/>
</bean>

这种情况就是将公共的属性进行统一注入,避免XML中各种属性配置的重复,但这种情况下,就需要子类必须要有相应的属性。对例子而言,也就是Children这个类中必须要有nameid属性。

我们分两部分来看一个例子,一是抽象bean无法初始化,二是子类bean可以继承父类bean的属性:
首先定义Parent对象:

public class Parent {
    private String name;

    public Parent() {
        System.out.println("Parent初始化");
    }
}

配置bean:

<bean id="parent" class="entity.Parent" abstract="true">
    <property name="name" value="name"/>
</bean>

测试下:

ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("/resource/applicationContext.xml");

可以看到,程序并未打印我们在Parent中设置的构造方法,也就是说我们没有配置懒加载,但Parent对象并没有实例化。

然后我们再来看下属性的继承,再定义子类Children:

public class Children extends Parent{
    private String id;

    public Children() {
        System.out.println("Children初始化");
    }
}

配置子类的bean:

<bean id="children" class="entity.Children" parent="parent">
    <property name="id" value="id"/>
</bean>

看一下最终打印结果:

ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("/resource/applicationContext.xml");
Children children= ac.getBean("children", Children.class);
System.out.println(children.getName() + "," + children.getId());
ac.close();

output:


Parent初始化
Children初始化
name,id

可以看到,先初始化了父类,再初始化子类,然后子类Children继承了Parent中的name属性。如果我们把parent这项配置给去掉,我这里为了方便不上传这部分代码,直接看下打印结果:

Parent初始化
Children初始化
null,id

很显然,父类Parent中的name属性并没有被继承过来。
最后,再简单说下这里:

  1. 如果父类bean没有定义class属性,则子类bean必须定义class属性,并且子类中必须拥有父bean中所有属性;
  2. 还有一种情况,如果父bean有class属性,而子bean没有,那么子类的bean就和父bean是完全一样的bean,当然,子类的bean也就不能注入任何新的属性。
2.9 depends-on标签

  如果一个bean是另一个bean的依赖项,通常将一个bean设置为另一个bean的属性,然后在XML中可以通过ref标签来引用。而有时,bean之间的关系并没有那么直接,比如实例化DAO之前先实例化DataBase这种情况,而这时候就可以使用depends-on标签。
  该标签用于指定bean初始化时的顺序,也就是说一个beanA初始化之前必须先初始化另一个beanB。对于depends-on而言,并不需要beanA持有beanB的一个引用,只是一种强制性的在beanA初始化之前对beanB进行初始化。

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
  1. 很多情况下,depends-on标签可以使用bean的构造器注入或setter注入代替;还有一点,该bean的类型应该是单例的,因为单例的bean才会被Spring进行管理;
  2. 若要对多个bean进行依赖,depends-on提供了一个bean名称的列表,使用分号,逗号或空格来作为有效的分隔符;
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
  1. depends-on可以指定依赖bean在具体bean初始化之前先初始化,也可以指定依赖bean在具体bean销毁之前先销毁,同样,这时候bean的类型也只能是单例的。
    参考官网文件:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring/beans-factory-dependson
2.10 primary标签

  我们在前面学习类型的自动注入的时候,可能会出现多个候选者,然后我们可以通过autowire-candidate注解可以设置某个或多个bean在候选bean中被排除掉,或者成为候选bean。而现在,primary提供了另一种方式。
  primary元素是说在类型注入的时候,如果多个bean被自动列入候选者之中,那么可以通过使用primary元素或@primary注解让该bean成为优先选择者,如果候选者之中只有一个primary修饰的bean,那么这个bean将会是自动注入的bean。

该属性同样有truefalse两个值,默认情况下是false;

还拿上面的例子来说:

<bean id="score" class="entity.Score" >
    <property name="math" value="100"/>
</bean>
<bean id="score2" class="entity.Score" primary="true">
    <property name="math" value="200"/>
</bean>
<bean id="student" class="entity.Student">
    <property name="id" value="id"/>
</bean>

测试代码就不再写了,获取Student中的引用Score的math值:

200

可以看到,由于score2的优先级高,Student类型中自动注入的就是score2类型。

@primary注解实现类似功能的还有@Qualifier注解,等我们接下来学习到的时候再来学习。

2.11 factory-beanfactory-methd标签

  在前面的学习中,我们都是根据配置中的class全名通过反射进行实例化bean,而factory-beanfactory-method标签可以让我们通过工厂模式的方法来实例化bean。而工厂模式又大致分为两种,一种是静态工厂方法,另一种是实例工厂方法。

  1. 静态工厂,是指Factory本身并不需要实例化,而这个Factory类中提供了一个静态方法来生成bean对象;
  2. 而实例工厂是说Factory类中的方法不是静态的,这也就要求我们先实例一个工厂对象,然后通过这个工厂对象去获取我们所需要的bean对象。

我们先看下静态工厂的简单例子:

public class Student {
    private String id;
    private String name;
}
    
public class StudentFactory {
    public StudentFactory() {
        System.out.println("执行StudentFactory的构造方法进行初始化操作");
    }

    private static Student getInstance() {
        return new Student();
    }
}

我们定义了Student和静态工厂StudentFactory,再简单看下配置:

<bean id="student" class="entity.StudentFactory" factory-method="getInstance">
    <property name="name" value="name"/>
</bean>

同样,我们再测试下:

Student student =  ac.getBean("student", Student.class);
System.out.println(student.getName());

成功打印:

name

可以看到,程序并没有执行工厂类的默认构造方法,也就是没有初始化工厂类,而是通过工厂类的静态方法完成了我们bean对象的初始化工作。

接下来我们再简单看下实例工厂:
首先,将StudentFactory中的static去掉,变为实例方法,然后再看下配置:

<bean id="studentFactory" class="entity.StudentFactory"/>
<bean id="student" factory-bean="studentFactory" factory-method="getInstance">
    <property name="name" value="name"/>
</bean>

我们先实例化工厂类,再依靠工厂类去获取我们的bean,然后再看下执行结果:

执行StudentFactory的构造方法进行初始化操作
name

可以,看到,程序先初始化了工厂类,然后再通过实例方法实例化了我们的bean对象。

工厂模式还是挺方便的,我们在实例化对象的时候,可以尝试使用工厂模式来执行我们的初始化操作。

2.12 scope标签

scope标签表示的bean的作用域,默认情况下我们所创建的bean都是单例的。Spring支持以下几种作用域:

  1. singleton,默认作用域,在一个Spring容器中,一个bean只会有一个实例,这意味着所有的bean都是共享的,很适合无状态的bean定义;
  2. prototype,该作用域与singleton相反,每次请求都会返回新的实例,从某种意义上类似于Java的new操作符,适合有状态的bean定义。但需要注意一点就是Spring对于该模式的bean,创建完实例后,就交给对象本身管理自己的生命周期,不会自动做一些销毁的操作。所以由于该模式下生命周期并不是由Spring进行管理,所以这种情况下配置的如destory-method便不会生效。所以如果可以的话,我们可以在合适的适合调用Spring的销毁方法,让Spring来销毁对象释放资源。
  3. request,该模式是说每次HTTP请求中,每个Bean都会生成一个实例,也就是说每次HTTP请求都将会产生不同的实例。和prototype类似,但该作用域仅在Web请求中才有效;
  4. session,该模式是说每次HTTP Session中,每个bean都会生成一个实例,同样,作用域仅在Web请求中有效;
  5. globalSession,每个全局的HTTP Session,每个bean都会生成一个实例,该作用域仅在Portlet 上下文中有效,使用较少;Portlet规范是类似于Servlet的一种规范,有兴趣的童鞋可自行了解下。
  6. application,该模式和单例模式有点像,但applicationServletContext中是单例的,而singleton的应用范围是ApplicationContext,同样,仅在Web请求中有效;简单看下注解及XML配置:
@Component
@Scope("application")
public class BeanClass {
}
 
//or
 
@Component
@ApplicationScope
public class BeanClass {
}
<bean id="beanId" class="com.howtodoinjava.BeanClass" scope="application" />
  1. WebSocket,该模式用于HTTP中远程主机间进行双向通信,同样,也是仅在Web请求下有效。通常 WebSocket范围内的bean通常是单例的,并且比任何单独的WebSocket会话要活的更长。注解和XML如下:
@Component
@Scope("websocket")
public class BeanClass {
}
<bean id="beanId" class="com.howtodoinjava.BeanClass" scope="websocket" />
  1. 还可以自定义线程的scope,这里就不多说了,想查看更多有关scope的内容,可参考官方文档,上面讲的非常清楚:
    https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/beans.html#beans-factory-scopes
    还可以参考:Spring Bean Scopes

需要注意下,因为singleton适合无状态的情况,所以在多线程的时候,如果bean被定义为单例模式,可能会出现线程安全问题,这里需要注意下。

总结下:

  1. 在Spring中,我们使用最多的就是singletonprototype模式,singleton是一种无状态的bean类型,当项目中不存在多线程共享对象的时候可以选择singleton模式,这样可以稍微节约资源。使用prototype的时候记得注意对象的销毁操作。
  2. 在Spring配置文件XML中直接配置application,websocket,会报错,还没搞清楚为什么。
3. alias标签

这个标签用于为bean提供别名。根据官方文档解释:

  1. 在bean本身定义中,我们可以通过id和name属性来为bean提供多个名称,这些名称等价于相同的bean,在某种情况下是很有用的,比如应用程序中的每个组件,使用特定于该组件本身的bean名称来引用公共的依赖关系。
  2. 但有的时候这种方式定义的别名并不总是能满足我们的所有需求,这时候就可以通过标签alias来实现另一种别名的定义。比如说在大型系统中,每个子系统都有自己的一套配置,组件A通过subsystemA-dataSource名称定义了一个数据源bean,而组件B想通过subsystemB-dataSource引用这一个数据源,而主系统也想通过myApp-dataSource来引用该数据源,这种情况下就可以使用alias标签来完成这种操作。
  3. 这样一来,每个组件及主程序就可以通过一个唯一的名字来引用同一个数据源,而相互间不会受到干扰。
  4. 官方文档地址:https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/html/beans.html#beans-beanname-alias

使用方式很简单:

<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />

不过需要保证alias唯一,不能重复。

4. description标签

这个标签就不多说了,就是对该配置文件的描述信息,不过配置的时候注意下顺序,一般用于配置的最顶层。

<description>Spring MVC Test</description>
5. import标签

在我们的Spring配置中,随着我们项目的越来越大,配置文件的越来越复杂,模块化则是我们需要考虑的问题了,而import标签则是充当了这样的角色,我们可以通过import标签将多个Spring配置文件引入到我们的applicationContext文件中。

比如我们针对数据库配置,缓存配置,MQ配置,日志配置等模块,为每个模块配置相应的文件:

applicationContext-cache.xml
applicationContext-db.xml
applicationContext-mp.xml
applicationContext-log.xml

然后在applicationContext配置文件中使用import进行引入:

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

    <import resource="classpath*:spring/applicationContext-cache.xml"/>
    <import resource="classpath*:spring/applicationContext-db.xml"/>
    <import resource="classpath*:spring/applicationContext-log.xml"/>
    <import resource="classpath*:spring/applicationContext-mq.xml"/>
    
</beans>

这样,当我们需要新加配置的时候,只需要添加对应的配置文件,然后再使用import标签进行引入即可。这里再简单说下,resource的几种选择:

  1. <import resource="applicationContext-test.xml"/>,默认情况下,Spring会根据相对路径来加载对应的配置文件;
  2. <import resource="classpath:"/>,配置为classpath的情况下,是去class路径及相应的jar包下加载。而classpath:为精确配置,只能匹配一个,而classpath*:是一种通配符的形式,可以加载多个;
  3. <import resource="file:"/>,file是加载一个或多个文件系统的resource,比如file:D:/*.txt将加载D盘下所有的txt文件;
  4. <import resource="http:"/>,http就是从网络下加载相应的resource;

针对classpath,后续我们会再仔细讨论这个问题,现在先大致了解下即可。

参考资料:
Spring中的destroy-method方法详解
https://docs.spring.io/spring/docs/4.3.14/spring-framework-reference/beans.html

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