Spring详解(六)----Spring Bean的装配(基于XML的方式)

1、回顾依赖注入的三种方式

       在前面第三章中(Spring详解(三)——认识IoC控制反转/DI依赖注入)我们介绍了什么是依赖注入和它们的简单应用,它有3种方式:

  • 构造器注入
  • setter方法注入
  • 接口注入

       其中构造器注入和setter注入是最主要的方式,下面我们对它们进行简单回顾一下。

       ①、构造器注入:顾名思义就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象。在大部分的情况下,我们都是通过类的构造方法来创建类对象, Spring 也可以采用反射的方式, 通过使用构造方法来完成注入,这就是构造器注入的原理。首先我们要创建一个具体的类、构造方法并设置对应的参数,这里以User为例:

/**
 * 用户实体类
 */
public class User {
    private int userId;
    private String userName;
    private int userAge;
    private String userPwd;
    private String userAddress;

    //getter、setter、toString方法省略......

    //有参构造器
    public User(int userId, String userName, int userAge,
                String userPwd, String userAddress) {
        this.userId = userId;
        this.userName = userName;
        this.userAge = userAge;
        this.userPwd = userPwd;
        this.userAddress = userAddress;
    }

}

       如果我们在实体类中创建了有参的构造器,而没有显示的创建无参构造器,那么是不能再通过无参的构造器创建对象了,为了使 Spring 能够正确创建这个对象,可以像如下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">

    <!--将指定类都配置给Spring,让Spring创建其对象的实例,一个bean对应一个对象
    如果类中创建了有参构造器,必须完成初始化-->
    <bean >
        <constructor-arg index="0" value="2020"/>
        <constructor-arg index="1" value="菜逼唐"/>
        <constructor-arg index="2" value="18"/>
        <constructor-arg index="3" value="123456"/>
        <constructor-arg index="4" value="地球中国"/>
    </bean>
</beans>

       constructor-arg元素用于定义类构造方法的参数,其中index 用于定义参数的位置(从0开始),而 value 则是设置值,通过这样的定义 Spring 便知道使用 哪个构造方法去创建对象了。虽然这样注入还是比较简单的,但是缺点也很明显,由于这里的参数比较少,所以可读性还是不错的,但是如果参数很多,那么这种构造方法就比较复杂了,这个时候应该考虑 setter 注入。


       ②、setter方法注入:setter 注入是 Spring 中最主流的注入方式,它利用 Java Bean 规范所定义的 setter 方法来完成注入,灵活且可读性高。它消除了使用构造器注入时出现多个参数的可能性,首先可以把构造方法声明为无参数的,然后使用 setter 注入为其设置对应的值,其实也是通过 Java 反射技术得以现实的。这里去掉上面User类中的有参数的构造方法,然后做如下的Spring配置。

    <bean >
        <property name="userId" value="2020"/>
        <property name="userName" value="菜逼唐"/>
        <property name="userAge" value="18"/>
        <property name="userPwd" value="123456"/>
        <property name="userAddress" value="地球中国"/>
    </bean>

       这样Spring就会通过反射调用没有参数的构造方法生成对象,同时通过反射对应的setter注入配置的值了。这种方式是Spring最主要的方式,在实际的工作中是最常用的,所以下面都是基于setter方法注入的举例。


       ③、接口注入:接口注入是现在非常不提倡的一种方式,这种方式基本处于“退役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter方法注入则不需要如此,所以现在我们一般推荐使用构造器注入和setter注入。

2、装配 Bean 概述

       通过前面的学习,应该对 Spring IoC 的理念和设计有了基本的认识,这一篇文章将介绍的是如何将自己开发的 Bean 装配到 Spring IoC 容器中。在 Spring 中提供了3种方法进行配置:

  1. XML 中显示配置(Spring的XML配置文件)
  2. Java 的接口和类中实现配置(比如:@Configuration+@Bean)
  3. 隐式 Bean 的发现机制和自动装配原则(比如:@Component+@Autowired)

       在现实的工作中,这3 种方式都会被用到,并且在学习和工作中常常混合使用,所以我们需要明确3种方式的优先级,也就是我们应该怎么选择使用哪种方式去把 Bean 发布到 Spring IoC 容器中。所以这里给出关于这 3 种方法优先级的建议(优先级从高到低):

       1)、基于约定优于配置的原则,最优先的应该是通过隐式 Bean 发现机制和自动装配的原则。这样的好处是减少程序开发者的决定权,简单又不失灵活,所以这种方式在我们的实际开发中用的最多。

       2)、在没有办法使用自动装配原则的情况下应该优先考虑 Java 接口和类中实现配置,这样的好处是避免 XML 置的泛滥,也更为容易 。这种场景典型的例子是 一个父类有多个子类,比如学生类有两个子类,一个男学生类和女学生类,通过 IoC 容器初始化一个学生类,容器将无法知道使用哪个子类去初始化,这个时候可以使用 Java 的注解配置去指定。

       3)、如果上述的两种方法都无法使用的情况下,那么只能选择 XML 去配置 Spring IoC 容器。这种方式的好处就是简单易懂,对于初学者非常友好。这种场景的例子是由于现实工作中常常用到第三方的类库,有些类并不是我们开发的,我们无法修改里面的代码,这个时候就通过 XML 方式配置使用了。

       通俗来讲,当配置的类是你自身正在开发的工程,那么应该考虑 Java 配置为主,而 Java 配置又分为自动装配和 Bean 名称配置。在没有歧义的基础上,优先使用自动装配,这样就可以减少大量的 XML 置。如果所需配置的类并不是你的工程开发的,那么建议使用 XML 的方式。

       本章都是通过 XML 的方式来配置 Bean,这样会更好的理解。使用 XML 装配 Bean 需要定义对应的 XML,这里需要引入对应的 XML 模式(XSD)文件,这些文件会定义配置 Spring Bean 的一些元素。

Spring详解(六)----Spring Bean的装配(基于XML的方式)

       这样我们就可以在里面定义对应的 Spring Bean了。

3、Bean 装配简单值

       这里先来讨论最简单的装配,比如基本的属性和对象,代码如下:

/**
 * 用户实体类
 */
public class User {
    private int userId;
    private String userName;
    private int userAge;
    private String userPwd;
    private String userAddress;
    //女朋友
    private GirlFriend girlFriend;
    //getter、setter、toString方法省略......
}

       GirlFriend实体:

/**
 * GirlFriend实体
 */
public class GirlFriend {
    private String girlName;
    private int girlAge;
    private String girlHeight;
    //getter、setter、toString方法省略......
}

       Spring的xml配置文件:

<?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">

    <!--实例化GirlFriend-->
    <bean >
        <property name="girlName" value="王美丽"/>
        <property name="girlAge" value="18"/>
        <property name="girlHeight" value="170"/>
    </bean>
    
    <!--实例化User-->
    <bean >
        <!--注入普通值:使用 value 属性-->
        <property name="userId" value="2020"/>
        <property name="userName" value="菜逼唐"/>
        <property name="userAge" value="18"/>
        <property name="userPwd" value="123456"/>
        <property name="userAddress" value="地球中国"/>
        <!--注入对象:使用 ref 属性-->
        <property name="girlFriend" ref="girlFriend"/>
    </bean>
</beans>

       上面就是一个最简单最基本的配置Bean了,这里简单来解释一下:

  • id  属性是标识符(别名),用来让Spring找到这个Bean,id属性不是一个必须的属性,如果我们没有声明它,那么 Spring 将会采用“全限定名#{number}“的格式生成编号。例如这里,如果没有声明 “,后面以此类推。但是我们一般都会显示声明自定义的id,因为自动生成的id比较繁琐,不便于维护。
  • class 属性显然就是一个类的全限定名 。
  • property 元素是定义类的属性,其中的 name 属性定义的是属性的名称,而 value 是它的值,ref 是用来引入对象的。

       简单来测试一下,测试代码如下:

public class SpringTest {
    public static void main(String[] args) {
        //1.初始化Spring容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.通过容器获取实例,getBean()方法中的参数是bean标签中的id
        User user =  applicationContext.getBean("user1", User.class);
        //3.调用实例中的属性
        System.out.println(user.getUserName()+"------"+user.getGirlFriend());
    }
}

       运行结果:

Spring详解(六)----Spring Bean的装配(基于XML的方式)

       最后注意:注入基本值使用 value 属性,注入对象使用 ref 属性。

4、Bean 装配集合

       有些时候我们需要装配一些复杂的Bean,比如 Set、Map、List、Array 和 Properties 等,所以我们将上面的User改一下,假如这个User是个“海王”呢?他有好几个GirlFriend。我们对User类添加了一些属性(记得更改setter、getter和tostring方法):

/**
 * 用户实体类
 */
public class User {
    private int userId;
    private String userName;
    private int userAge;
    private String userPwd;
    private String userAddress;
    //女朋友
    private GirlFriend girlFriend;

    private List<GirlFriend> lists;
    private Set<GirlFriend> sets;
    private Map<String, GirlFriend> maps;
    private Properties properties;
    private String[] array;
    //getter、setter、toString方法省略......
}

       Spring的xml配置:

<?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">

    <!--实例化GirlFriend-->
    <bean >
        <property name="girlName" value="王美丽"/>
        <property name="girlAge" value="18"/>
        <property name="girlHeight" value="170"/>
    </bean>
    <bean >
        <property name="girlName" value="杨美丽"/>
        <property name="girlAge" value="19"/>
        <property name="girlHeight" value="171"/>
    </bean>
    <bean >
        <property name="girlName" value="李美丽"/>
        <property name="girlAge" value="20"/>
        <property name="girlHeight" value="172"/>
    </bean>

    <!--实例化User-->
    <bean >
        <!--注入普通值:使用 value 属性-->
        <property name="userId" value="2020"/>
        <property name="userName" value="菜逼唐"/>
        <property name="userAge" value="18"/>
        <property name="userPwd" value="123456"/>
        <property name="userAddress" value="地球中国"/>
        <!--注入对象:使用 ref 属性-->
        <property name="girlFriend" ref="girlFriend1"/>

        <!--注入List集合-->
        <property name="lists">
            <list>
                <ref bean="girlFriend1"/>
                <ref bean="girlFriend2"/>
                <ref bean="girlFriend3"/>
            </list>
        </property>
        <!--注入Set集合-->
        <property name="sets">
            <set>
                <ref bean="girlFriend1"/>
                <ref bean="girlFriend2"/>
                <ref bean="girlFriend3"/>
            </set>
        </property>
        <!--注入Map集合-->
        <property name="maps">
            <map>
                <entry key="正牌女友" value-ref="girlFriend1"/>
                <entry key="备胎1" value-ref="girlFriend2"/>
                <entry key="备胎2" value-ref="girlFriend3"/>
            </map>
        </property>
        <!--注入Properties-->
        <property name="properties">
            <props>
                <prop key="k1">v1</prop>
                <prop key="k2">v2</prop>
            </props>
        </property>
        <!--注入数组-->
        <property name="array">
            <array>
                <value>value1</value>
                <value>value2</value>
                <value>value3</value>
            </array>
        </property>
    </bean>
</beans>

       对集合的装配进行总结:

  • List 属性使用对应的 <list> 元素进行装配,然后通过多个 <value> 元素设值,如果是bean则通过<ref>元素设值。
  • Set 属性使用对应的 <set> 元素进行装配,然后通过多个 <value> 元素设值,如果是bean则通过<ref>元素设值。
  • Map 属性使用对应的 <map> 元素进行装配,然后通过多个 <entry> 元素设值,entry 中包含一个键值对(key-value)的设置,普通值使用key和value,bean使用key-ref和value-ref设值。
  • Properties 属性使用对应的 <properties> 元素进行装配,通过多个 <property> 元素设值,只是 properties 元素有一个必填属性 key ,然后可以设置值
  • 对于数组而言,可以使用 <array> 设置值,然后通过多个 <value> 元素设值。

       简单来测试一下,测试代码如下:

public class SpringTest {
    public static void main(String[] args) {
        //1.初始化Spring容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.通过容器获取实例,getBean()方法中的参数是bean标签中的id
        User user =  applicationContext.getBean("user2", User.class);
        //3.调用实例中的属性
        System.out.println("List集合:"+user.getLists());
        System.out.println("Set集合:"+user.getSets());
        System.out.println("Map集合:"+user.getMaps());
        System.out.println("Properties:"+user.getProperties());
        System.out.println("数组:");
        String[] array = user.getArray();
        for (String s : array) {
            System.out.println(s);
        }
    }
}

       运行结果:

Spring详解(六)----Spring Bean的装配(基于XML的方式)

5、命名空间装配 Bean(了解)

       除了使用上述的的方法来装配Bean之外,Spring还提供了对应的命名空间的定义。

  • c 命名空间:用于通过构造器注入的方式来配置 bean
  • p 命名空间:用于通过setter的注入方式来配置 bean
  • util 命名空间:工具类的命名空间,可以简化集合类元素的配置

       下面来简单介绍。要使用它们首先得犹如对应的命名空间和XML模式(XSD)文件。

Spring详解(六)----Spring Bean的装配(基于XML的方式)

       示例代码:

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

    <!--c 命名空间 实例化GirlFriend,给GirlFriend显示的创建了一个无参和有参构造器-->
    <bean />

    <!--p 命名空间 实例化GirlFriend-->
    <bean />


    <!--util 命名空间-->
    <!--List集合-->
    <util:list >
        <ref bean="girlFriend1"/>
        <ref bean="girlFriend2"/>
    </util:list>
    <!--Set集合-->
    <util:set >
        <ref bean="girlFriend1"/>
        <ref bean="girlFriend2"/>
    </util:set>
    <!--Map集合-->
    <util:map >
        <entry key="第一个女友" value-ref="girlFriend1"/>
        <entry key="第二个女友" value-ref="girlFriend2"/>
    </util:map>
    <!--Properties集合-->
    <util:properties >
        <prop key="k1">v1</prop>
    </util:properties>
    <!--实例化User-->
    <bean 
          p:userId="2020"
          p:userName="菜逼唐"
          p:userAge="18"
          p:userPwd="123456"
          p:userAddress="地球中国"
          p:girlFriend-ref="girlFriend1"
          p:lists-ref="lists"
          p:sets-ref="sets"
          p:maps-ref="maps"
          p:properties-ref="properties">

    </bean>
</beans>

       总结:

  • c 命名空间:用于通过构造器注入的方式来配置 bean,c:_0 表示构造方法的第一个参数,c:_1 表示构造方法的第而个参数,以此类推。
  • p 命名空间:用于通过setter的注入方式来配置 bean,p:属性名 表示为属性设值,p:list-ref 表示采用List属性,引用其上下文对应好的Bean,这里显然是util命名空间定义的List,Map和Set同理。
  • util 命名空间:工具类的命名空间,可以简化集合类元素的配置。下表提供了 util-命名空间提供的所有元素:
  • util元素 描述
    <util:constant> 引用某个类型的 public static 域,并将其暴露为 bean
    <util:list> 创建一个 java.util.List 类型的 bean,其中包含值或引用
    <util:map> 创建一个 java.util.map 类型的 bean,其中包含值或引用
    <util:properties> 创建一个 java.util.Properties 类型的 bean
    <util:property-path> 引用一个 bean 的属性(或内嵌属性),并将其暴露为 bean
    <util:set> 创建一个 java.util.Set 类型的 bean,其中包含值或引用


       参考资料:

  • 《Java EE 互联网轻量级框架整合开发》
  • 《Spring 实战》(第四版)