【Spring从入门到实战教程】第二章 Spring 配置Bean

二、Spring Bean的配置

    由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。所谓配置Bean就是告诉Spring的IOC容器将要去管理的对象。

2.1 配置bean的方式

2.1.1 传统的XML配置方式

Person.java:

public class Person {
    private String name;
    private int age;
    private double money;

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

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

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

applicationContext.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">
    <!--
    bean标签:让spring创建一个对象并放置在IOC容器内,一个bean标签就对应某个类的一个对象
    属性:
        id: 该bean对象的唯一标识符,bean的名称,不能重复的
        class: 需要创建对象的类型的全限定名,Spring通过反射机制创建该类的对象(要求:该类必须拥有无参构造方法)
    -->
    <bean id="person1" class="com.newcapec.bean.Person"/>
    <bean id="person2" class="com.newcapec.bean.Person">
</beans>

属性解析:

  • id : bean的名称,在IOC容器中必须是唯一的,无论后面拆分配置文件还是使用注解,都要求id不能重复;若id没有指定,Spring自动将类全限定性类名作为bean的名字 ;

  • class : java类的全限定名称;

BeanTest.java:

public class BeanTest {

    @Test
    public void testBeanXML(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Person p1 = (Person) ac.getBean("person1");
        System.out.println(p1);

        Person p2 = (Person) ac.getBean("person2");
        System.out.println(p2);

        //测试p1和p2是不是指向同一个地址
        System.out.println(p1 == p2);//false,验证一个Bean对应一个对象

        Person p3 = (Person) ac.getBean("person1");
        System.out.println(p1 == p3);//true
    }
}

2.1.2 基于Java注解的配置

    请关注后面的章节。

2.1.3 基于类的Java Config

    请关注后面的章节。

2.2 实例化Bean的方式

2.2.1 通过构造方法实例化Bean

    Spring IoC容器即能使用默认空构造方法也能使用有参数构造,通过反射机制来实例化Bean对象。

2.2.2 通过工厂实例化Bean

    请关注后面的章节。

2.2.3 FactoryBean实例化Bean

    请关注后面的章节。

2.3 Spring容器

    IoC 思想基于 IoC 容器实现的,IoC 容器底层其实就是一个 Bean 工厂。Spring 框架为我们提供了两种不同类型 IoC 容器,它们分别是 BeanFactory 和 ApplicationContext。

2.3.1 BeanFactory

    BeanFactory 是 IoC 容器的基本实现,也是 Spring 提供的最简单的 IoC 容器,它提供了 IoC 容器最基本的功能,由 org.springframework.beans.factory.BeanFactory 接口定义。

    BeanFactory 采用懒加载(lazy-load)机制,容器在加载配置文件时并不会立刻创建 Java 对象,只有程序中获取(使用)这个对对象时才会创建。
    
    注意:BeanFactory 是 Spring 内部使用接口,通常情况下不提供给开发人员使用。 

2.3.2 ApplicationContext

    ApplicationContext 是 BeanFactory 接口的子接口,是对 BeanFactory 的扩展。ApplicationContext 在 BeanFactory 的基础上增加了许多企业级的功能,例如 AOP(面向切面编程)、国际化、事务支持等。

2.3.3 ApplicationContext的主要实现类

  • ClassPathXmlApplicationContext:从类路径上加载配置文件;

  • FileSystemXmlApplicationContext:从文件系统中加载配置文件;

  • WebApplicationContext:专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作;

2.3.4 从容器中获取Bean

  • getBean(String name)方法,通过Bean的id从容器中获取Bean对象;

  • getBean(Class requiredType)方法,通过Bean的Class类型从容器中获取Bean对象;

  • getBean(String name, Class requiredType)方法,通过Bean的id和Class类型从容器中获取Bean对象;

注意:当IOC容器中存放有多个此类型的对象时,不能通过Class类型来获取Bean对象。

BeanTest.java

public class BeanTest {
    @Test
    public void testGetBean() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //1.参数为字符串类型
        Person p1 = (Person) ac.getBean("person1");
        System.out.println(p1);

        //2.参数为Class类型,缺点:当IOC容器中存在多个此类型对象时,抛出异常
        Person person = ac.getBean(Person.class);
        System.out.println(person);

        //3.参数为字符串类型+Class类型
        Person p2 = ac.getBean("person2", Person.class);
        System.out.println(p2);
    }
}

2.4 依赖注入

2.4.1 基于属性注入

    我们可以通过 Bean 的 setter 方法,将属性值注入到 Bean 的属性中。
    
    在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。
    
    使用 setter 注入的方式进行属性注入,大致步骤如下:
        1、在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法;
        2、在 Spring 的 XML 配置文件中,使用 <beans> 及其子元素 <bean> 对 Bean 进行定义;
        3、在 <bean> 元素内使用  <property> 元素对各个属性进行赋值,使用name属性指定Bean的属性名称,value属性或<value>子标签指定属性值。
        
    属性注入是实际应用中最常用的注入方式。

applicationContext.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">

    <!-- 通过setter方法注入属性值 -->
    <bean id="person" class="com.newcapec.bean.Person">
        <!--
        property标签:表示通过属性的set方法为属性赋值,也叫做依赖注入
        属性:
        	name: 对象中的属性名称
        	value: 属性值
        -->
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <property name="money">
            <value>3600.5</value>
        </property>
    </bean>
</beans>
public class DiTest {

    @Test
    public void testSet() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Person person = ac.getBean("person", Person.class);

        System.out.println(person);
    }
}

2.4.2 基于构造方法注入

    我们可以通过 Bean 的带参构造函数,以实现 Bean 的属性注入。
    
    使用构造函数实现属性注入大致步骤如下:
        1、在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性;
        
        2、在 Spring 的 XML 配置文件中,通过 <beans> 及其子元素 <bean> 对 Bean 进行定义;

        3、在 <bean> 元素内使用 <constructor-arg> 元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 <constructor-arg> 元素。

Car.java:

public class Car {
    private String name;
    private String type;
    private double price;
    private int doors;

    public Car(String name, String type, double price, int doors) {
        this.name = name;
        this.type = type;
        this.price = price;
        this.doors = doors;
    }

    public Car(String name, String type, int doors) {
        this.name = name;
        this.type = type;
        this.doors = doors;
    }

    public Car(String name, String type, double price) {
        this.name = name;
        this.type = type;
        this.price = price;
    }

    public Car(String n, String t) {
        this.name = n;
        this.type = t;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", type='" + type + '\'' +
                ", price=" + price +
                ", doors=" + doors +
                '}';
    }
}

applicationContext.xml:

注意:如果该类中有多个构造方法,通过index、type或name对构造方法进行精确选取。

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

    <!-- 通过构造方法注入属性值 -->
    <bean id="car1" class="com.newcapec.bean.Car">
        <!--
        constructor-arg : 表示创建该类型的对象时,使用的构造方法的参数
        属性:
            value : 构造方法的参数值
            index : 构造方法参数的索引
            type : 构造方法参数的类型
            name : 构造方法参数的名称
        -->
        <constructor-arg value="宝马"/>
        <constructor-arg value="轿车"/>
        <constructor-arg value="360000"/>
        <constructor-arg value="4"/>
    </bean>
</beans>
  • 按索引匹配构造方法参数

<bean id="car2" class="com.newcapec.bean.Car">
    <constructor-arg value="越野" index="1"/>
    <constructor-arg value="奔驰" index="0"/>
    <constructor-arg value="4" index="3"/>
    <constructor-arg value="560000" index="2"/>
</bean>
  • 按类型匹配构造方法参数

<bean id="car3" class="com.newcapec.bean.Car">
    <constructor-arg value="大众" type="java.lang.String"/>
    <constructor-arg value="商务车" type="java.lang.String"/>
    <constructor-arg value="290000" type="double"/>
</bean>
  • 按参数名称匹配构造方法参数

<bean id="car4" class="com.newcapec.bean.Car">
    <constructor-arg value="电动车" name="t"/>
    <constructor-arg value="特斯拉" name="n"/>
</bean>

测试:

public class DiTest {
    @Test
    public void testConstructor() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Car car1 = ac.getBean("car1", Car.class);
        System.out.println(car1);

        Car car2 = ac.getBean("car2", Car.class);
        System.out.println(car2);

        Car car3 = ac.getBean("car3", Car.class);
        System.out.println(car3);

        Car car4 = ac.getBean("car4", Car.class);
        System.out.println(car4);
    }
}

2.5 注入属性值

2.5.1 字面值

  • 可用字符串表示的值,可以通过<value>标签或value属性进行注入;

  • 基本数据类型及包装类类型,String等类型都可以采取字面值注入的方式。Spring会将字符串自动转换为相应的数据类型;

  • 如果字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来;

Person.java:

public class Person {
    private String name;
    private int age;
    private double money;
    private Date birthday;
    private boolean gender;

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

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public boolean isGender() {
        return gender;
    }

    public void setGender(boolean gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", money=" + money +
                ", birthday=" + birthday +
                ", gender=" + gender +
                '}';
    }
}

applicationContext.xml:

<!--注入属性值:字面值-->
<bean id="per" class="com.newcapec.bean.Person">
    <property name="name" value="李四"/>
    <!--特殊值写法-->
    <!--<property name="name">
            <value><![CDATA[1<2]]></value>
        </property>-->
    <property name="age" value="20"/>
    <property name="money" value="3000.8"/>
    <!--对于日期值,如果写其他格式,不识别-->
    <property name="birthday" value="1998/05/12"/>
    <property name="gender" value="true"/>
</bean>

测试:

@Test
public void testValue() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

    Person per = ac.getBean("per", Person.class);

    System.out.println(per);
}

2.5.2 引用其他的bean

    组成应用程序的Bean经常需要相互协作以完成应用程序的功能。要使Bean能够相互访问,就必须在Bean配置文件中指定对Bean的引用。

Customer.java:

public class Customer {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Order.java:

public class Order {
    private String orderNum;
    private double price;
    /**
     * 自定义类型的属性
     */
    private Customer customer;

    public Order() {
    }

    public Order(String orderNum, double price, Customer customer) {
        this.orderNum = orderNum;
        this.price = price;
        this.customer = customer;
    }

    public String getOrderNum() {
        return orderNum;
    }

    public void setOrderNum(String orderNum) {
        this.orderNum = orderNum;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderNum='" + orderNum + '\'' +
                ", price=" + price +
                ", customer=" + customer +
                '}';
    }
}
  • 2.5.2.1 内部Bean方式

    我们将定义在 <bean> 元素的 <property> 或 <constructor-arg> 元素内部的 Bean,称为“内部 Bean”。

一、setter 方式注入内部 Bean:

    我们可以通过 setter 方式注入内部 Bean。此时,我们只需要在 <bean> 标签下的 <property> 元素中,再次使用 <bean> 元素对内部 Bean 进行定义,格式如下。

<?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-3.0.xsd">

    <bean id="outerBean" class="……">
        <property name="……" >
            <!-- 定义内部 Bean -->
            <bean class="……">
                <property name="……" value="……" ></property>
                ……
            </bean>
        </property>
    </bean>
</beans>

    注意:内部 Bean 都是匿名的,不需要指定 id 和 name 的。即使指定了,IoC 容器也不会将它作为区分 Bean 的标识符,反而会无视 Bean 的 Scope 标签。因此内部 Bean 几乎总是匿名的,且总会随着外部的 Bean 创建。内部 Bean 是无法被注入到它所在的 Bean 以外的任何其他 Bean 的。

<bean id="order1" class="com.newcapec.bean.Order">
    <property name="orderNum" value="20220413001"/>
    <property name="price" value="998"/>
    <!--setter 方式注入内部 Bean-->
    <property name="customer">
        <bean class="com.newcapec.bean.Customer">
            <property name="username" value="tom"/>
            <property name="password" value="123456"/>
        </bean>
    </property>
</bean>

二、构造函数方式注入内部 Bean:

    我们可以通过构造方法注入内部 Bean。此时,我们只需要在 <bean> 标签下的 <constructor-arg> 元素中,再次使用 <bean> 元素对内部 Bean 进行定义,格式如下。

<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-3.0.xsd">

    <bean id="……" class="……">
        <constructor-arg name="……">
            <!--内部 Bean-->
            <bean class="……">
                <constructor-arg name="……" value="……"></constructor-arg>
                ……
            </bean>
        </constructor-arg>
    </bean>
</beans>

applicationContext.xml:

<bean id="order2" class="com.newcapec.bean.Order">
    <constructor-arg name="orderNum" value="20220413002"/>
    <constructor-arg name="price" value="799"/>
    <!--构造函数方式注入内部 Bean-->
    <constructor-arg name="customer">
        <bean class="com.newcapec.bean.Customer">
            <property name="username" value="jack"/>
            <property name="password" value="123456"/>
        </bean>
    </constructor-arg>
</bean>
  • 2.5.2.2 ref属性引用方式

    ref属性会根据名称从IOC容器中查找指定的bean,然后引用赋值。

一、<property>标签中的ref属性:

<bean id="customer3" class="com.newcapec.bean.Customer">
    <property name="username" value="jerry"/>
    <property name="password" value="123456"/>
</bean>
<bean id="order3" class="com.newcapec.bean.Order">
    <property name="orderNum" value="20220413003"/>
    <property name="price" value="1299"/>
    <!-- 通过ref属性来注入属性值:其中ref属性的值为其他bean的id-->
    <property name="customer" ref="customer3"/>
</bean>

二、<property>标签中的<ref>子标签:

<bean id="customer4" class="com.newcapec.bean.Customer">
    <property name="username" value="Spike"/>
    <property name="password" value="123456"/>
</bean>
<bean id="order4" class="com.newcapec.bean.Order">
    <property name="orderNum" value="20220413004"/>
    <property name="price" value="698"/>
    <!-- 通过ref子标签来注入其他的bean对象-->
    <property name="customer">
        <ref bean="customer4"/>
    </property>
</bean>

测试:

@Test
public void testRef() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

    Order order1 = ac.getBean("order1", Order.class);
    System.out.println(order1);

    Order order2 = ac.getBean("order2", Order.class);
    System.out.println(order2);

    Order order3 = ac.getBean("order3", Order.class);
    System.out.println(order3);

    Order order4 = ac.getBean("order4", Order.class);
    System.out.println(order4);
}

2.5.3 集合属性

    我们还可以在 Bean 标签下的 <property> 元素中,使用以下元素配置 Java 集合类型的属性和参数,例如 List、Set、Map 以及 Properties 等。

标签说明
<list>用于注入 list 类型的值,允许重复。
<set>用于注入 set 类型的值,不允许重复。
<map>用于注入 key-value 的集合,其中 key 和 value 都可以是任意类型。
<props>用于注入 key-value 的集合,其中 key 和 value 都是字符串类型。

    注意:对于集合中普通类型的值,可以直接通过value子标签或value属性直接注入,对于集合中自定义类型的值,需要通过ref标签或内部bean方式注入。

Course.java:

public class Course {
    private int id;
    private String cname;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    @Override
    public String toString() {
        return "Course{" +
                "id=" + id +
                ", cname='" + cname + '\'' +
                '}';
    }
}

Student.java:

public class Student {
    private int id;
    private String name;
    private List<Course> courseList;
    private Integer[] ids;
    private Set<String> stringSet;
    private Map<String, Course> courseMap;
    private Properties props;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Course> getCourseList() {
        return courseList;
    }

    public void setCourseList(List<Course> courseList) {
        this.courseList = courseList;
    }

    public Integer[] getIds() {
        return ids;
    }

    public void setIds(Integer[] ids) {
        this.ids = ids;
    }

    public Set<String> getStringSet() {
        return stringSet;
    }

    public void setStringSet(Set<String> stringSet) {
        this.stringSet = stringSet;
    }

    public Map<String, Course> getCourseMap() {
        return courseMap;
    }

    public void setCourseMap(Map<String, Course> courseMap) {
        this.courseMap = courseMap;
    }

    public Properties getProps() {
        return props;
    }

    public void setProps(Properties props) {
        this.props = props;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", courseList=" + courseList +
                ", ids=" + Arrays.toString(ids) +
                ", stringSet=" + stringSet +
                ", courseMap=" + courseMap +
                ", props=" + props +
                '}';
    }
}
  • 2.5.3.1 List集合

    配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。这些标签可以通过<value>指定简单的常量值,通过<ref>指定对其他bean的引用。

<bean id="course1" class="com.newcapec.bean.Course">
    <property name="id" value="10"/>
    <property name="cname" value="Java语言"/>
</bean>
<bean id="course2" class="com.newcapec.bean.Course">
    <property name="id" value="20"/>
    <property name="cname" value="Oracle数据库"/>
</bean>
<bean id="course3" class="com.newcapec.bean.Course">
    <property name="id" value="30"/>
    <property name="cname" value="Spring框架"/>
</bean>

<bean id="student" class="com.newcapec.bean.Student">
    <property name="id" value="1001"/>
    <property name="name" value="小明"/>
    <property name="courseList">
        <list>
            <ref bean="course1"/>
            <ref bean="course2"/>
            <ref bean="course3"/>
            <!--内部bean-->
            <bean class="com.newcapec.bean.Course">
                <property name="id" value="40"/>
                <property name="cname" value="Mybatis框架"/>
            </bean>
        </list>
    </property>
</bean>
@Test
public void testCollection() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

    Student student = ac.getBean("student", Student.class);
    System.out.println(student);
}
  • 2.5.3.2 Object[]数组

    配置数组类型的属性,使用`<array>`标签。

<bean id="student" class="com.newcapec.bean.Student">
    <property name="id" value="1001"/>
    <property name="name" value="小明"/>
    <!-- 数组:array子标签-->
    <property name="ids">
        <array>
            <value>10</value>
            <value>20</value>
            <value>30</value>
            <value>40</value>
        </array>
    </property>
</bean>
  • 2.5.3.3 Set集合

    配置java.util.Set类型的属性,使用`<set>`标签。

<bean id="student" class="com.newcapec.bean.Student">
    <property name="id" value="1001"/>
    <property name="name" value="小明"/>
    <!-- set集合:set子标签-->
    <property name="stringSet">
        <set>
            <value>hello</value>
            <value>goodbye</value>
            <value>how are you</value>
        </set>
    </property>
</bean>
  • 2.5.3.4 Map集合

    java.util.Map通过`<map>`标签定义,`<map>`标签里可以使用多个`<entry>`作为子标签。每个`<entry>`中包含一个键和一个值。简单类型使用key和value属性来定义,Bean引用通过key-ref和value-ref属性定义。

<bean id="student" class="com.newcapec.bean.Student">
    <property name="id" value="1001"/>
    <property name="name" value="小明"/>
    <!-- map集合:map子标签-->
    <property name="courseMap">
        <map>
            <!--
            entry标签:表示Map集合中的一组键值对
            key: 表示Map集合中的键为字面值
            key-ref: 表示Map集合中的键为自定义类型
            value: 表示Map集合中的值为字面值
            value-ref: 表示Map集合中的值为自定义类型
            -->
            <entry key="one" value-ref="course3"/>
            <entry key="two" value-ref="course1"/>
        </map>
    </property>
</bean>
  • 2.5.3.5 Properties

    使用`<props>`定义java.util.Properties,该标签使用多个`<prop>`作为子标签,每个`<prop>`标签必须定义key属性。

<bean id="student" class="com.newcapec.bean.Student">
    <property name="id" value="1001"/>
    <property name="name" value="小明"/>
    <!-- Properties类型的属性 -->
    <property name="props">
        <props>
            <!-- prop标签,表示Properties集合中的一个键值对,key属性对应的键,prop开始标签与结束标签之后的区域填写值-->
            <prop key="hello">你好</prop>
            <prop key="goodbye">再见</prop>
        </props>
    </property>
</bean>
  • 2.5.3.6 单例集合

    单例集合: 供多个Bean使用。使用`<util>`标签,将集合定义在Bean的外部。

    注意:需要导入util命名空间和标签规范。

xmlns:util="http://www.springframework.org/schema/util"

http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
<bean id="student" class="com.newcapec.bean.Student">
    <property name="id" value="1001"/>
    <property name="name" value="小明"/>
    <!-- 引入单例集合 -->
    <property name="courseList" ref="myList"></property>
</bean>

<!-- 外部集合:多个集合属性可同时引用 -->
<util:list id="myList">
    <ref bean="course1"/>
    <ref bean="course2"/>
    <ref bean="course3"/>
    <bean class="com.newcapec.bean.Course">
        <property name="id" value="40"/>
        <property name="cname" value="MySQL数据库"/>
    </bean>
</util:list>

2.5.4 短命名空间注入

    我们在通过构造函数或 setter 方法进行属性注入时,通常是在 <bean> 元素中嵌套 <property> 和 <constructor-arg> 元素来实现的。这种方式虽然结构清晰,但书写较繁琐。

    Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。

短命名空间简化的 XML 配置说明
p 命名空间<bean> 元素中嵌套的 <property> 元素是 setter 方式属性注入的一种快捷实现方式
c 命名空间<bean> 元素中嵌套的 <constructor> 元素是构造函数属性注入的一种快捷实现方式

Dept.java:

public class Dept {
    private int deptno;
    private String dname;
    private String loc;

    public Dept() {
    }

    public Dept(int deptno, String dname, String loc) {
        this.deptno = deptno;
        this.dname = dname;
        this.loc = loc;
    }

    public int getDeptno() {
        return deptno;
    }

    public void setDeptno(int deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptno=" + deptno +
                ", dname='" + dname + '\'' +
                ", loc='" + loc + '\'' +
                '}';
    }
}

Emp.java:

public class Emp {
    private int empno;
    private String ename;
    /**
     * 关系属性
     */
    private Dept dept;

    public Emp() {
    }

    public Emp(int empno, String ename, Dept dept) {
        this.empno = empno;
        this.ename = ename;
        this.dept = dept;
    }

    public int getEmpno() {
        return empno;
    }

    public void setEmpno(int empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", dept=" + dept +
                '}';
    }
}
  • 2.5.4.1 p 命名空间注入

    p 命名空间是 setter 方式属性注入的一种快捷实现方式。通过它,我们能够以 bean 属性的形式实现 setter 方式的属性注入,而不再使用嵌套的 <property> 元素,以实现简化 Spring 的 XML 配置的目的。

    首先我们需要在配置文件的 <beans> 元素中导入以下 XML 约束。

xmlns:p="http://www.springframework.org/schema/p"

在导入 XML 约束后,我们就能通过以下形式实现属性注入:

<bean id="Bean 唯一标志符" class="包名+类名" p:普通属性="普通属性值" p:对象属性-ref="对象的引用">

使用 p 命名空间注入依赖时,必须注意以下 3 点:

  • Java 类中必须有 setter 方法;

  • Java 类中必须有无参构造器(类中不包含任何带参构造函数的情况,无参构造函数默认存在);

  • 在使用 p 命名空间实现属性注入前,XML 配置的 <beans> 元素内必须先导入 p 命名空间的 XML 约束。

applicationContext.xml:

<!-- 通过p命名空间的方式,来简化依赖注入 -->
<bean id="dept" class="com.newcapec.bean.Dept" p:deptno="10" p:dname="研发部" p:loc="郑州"/>

<bean id="emp" class="com.newcapec.bean.Emp" p:empno="8000" p:ename="张三" p:dept-ref="dept"/>

测试:

@Test
public void testPNameSpace() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Emp emp = ac.getBean("emp", Emp.class);
    System.out.println(emp);
}
  • 2.5.4.2 c 命名空间注入

    c 命名空间是构造函数注入的一种快捷实现方式。通过它,我们能够以 <bean> 属性的形式实现构造函数方式的属性注入,而不再使用嵌套的 <constructor-arg> 元素,以实现简化 Spring 的 XML 配置的目的。

    首先我们需要在配置文件的 <beans> 元素中导入以下 XML 约束。

xmlns:c="http://www.springframework.org/schema/c"

在导入 XML 约束后,我们就能通过以下形式实现属性注入:

<bean id="Bean 唯一标志符" class="包名+类名" c:普通属性="普通属性值" c:对象属性-ref="对象的引用">

使用 c 命名空间注入依赖时,必须注意以下 2 点:

  • Java 类中必须包含对应的带参构造器;

  • 在使用 c 命名空间实现属性注入前,XML 配置的 <beans>元素内必须先导入 c 命名空间的 XML 约束。

applicationContext.xml:

<!-- 通过c命名空间的方式,来简化依赖注入 -->
<bean id="department" class="com.newcapec.bean.Dept" c:deptno="20" c:dname="产品部" c:loc="杭州"/>

<bean id="employee" class="com.newcapec.bean.Emp" c:empno="7369" c:ename="李四" c:dept-ref="department"/>

测试:

@Test
public void testCNameSpace() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    Emp emp = ac.getBean("employee", Emp.class);
    System.out.println(emp);
}

2.5.5 autowire自动装配

    我们把 Spring 在 Bean 与 Bean 之间建立依赖关系的行为称为“装配”。

    Spring 的 IOC 容器虽然功能强大,但它本身不过只是一个空壳而已,它自己并不能独自完成装配工作。需要我们主动将 Bean 放进去,并告诉它 Bean 和 Bean 之间的依赖关系,它才能按照我们的要求完成装配工作。

    在前面的学习中,我们都是在 XML 配置中通过 <constructor-arg>和 <property> 中的 ref 属性,手动维护 Bean 与 Bean 之间的依赖关系的。
    
    对于只包含少量 Bean 的应用来说,这种方式已经足够满足我们的需求了。但随着应用的不断发展,容器中包含的 Bean 会越来越多,Bean 和 Bean 之间的依赖关系也越来越复杂,这就使得我们所编写的 XML 配置也越来越复杂,越来越繁琐。

    我们知道,过于复杂的 XML 配置不但可读性差,而且编写起来极易出错,严重的降低了开发人员的开发效率。为了解决这一问题,Spring 框架还为我们提供了“自动装配”功能。
    
    Spring 的自动装配功能可以让 Spring 容器依据某种规则(自动装配的规则,有五种),为指定的 Bean 从应用的上下文(AppplicationContext 容器)中查找它所依赖的 Bean,并自动建立 Bean 之间的依赖关系。而这一过程是在完全不使用任何 <constructor-arg>和 <property> 元素 ref 属性的情况下进行的。

    Spring 的自动装配功能能够有效地简化 Spring 应用的 XML 配置,因此在配置数量相当多时采用自动装配降低工作量。

    Spring 框架式默认不支持自动装配的,要想使用自动装配,则需要对 Spring XML 配置文件中 <bean> 元素的 autowire 属性进行设置。

Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表:

属性值说明
byName按名称自动装配。 Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。
byType按类型自动装配。 Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。
constructor与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。 其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。
default表示默认采用上一级元素 <beans> 设置的自动装配规则(default-autowire)进行装配。
no默认值,表示不使用自动装配,Bean 的依赖关系必须通过 <constructor-arg><property> 元素的 ref 属性来定义。
  • 2.5.5.1 不使用自动装配(autowire="no")

    autowire="no" 表示不使用自动装配,此时我们必须通过 <bean> 元素的 <constructor-arg>和 <property> 元素的 ref 属性维护 Bean 的依赖关系。

  • 2.5.5.2 按名称自动装配(autowire="byName")

    `autowire="byName"` 表示按属性名称自动装配,XML 文件中 Bean 的 id 或 name 必须与类中的属性名称相同。
    
    注:如果Bean 的 id 或 name 必须与类中的属性名称匹配到,则自动装配,没匹配到,则默认值。

<!-- 自动装配 -->
<bean id="dept" class="com.newcapec.bean.Dept" p:deptno="10" p:dname="研发部" p:loc="郑州"/>
<bean id="department" class="com.newcapec.bean.Dept" c:deptno="20" c:dname="产品部" c:loc="杭州"/>

<bean id="emp" class="com.newcapec.bean.Emp" p:empno="7689" p:ename="王武" autowire="byName"/>
  • 2.5.5.3 按类型自动装配(autowire="byType")

    `autowire="byType"` 表示按类中对象属性数据类型进行自动装配。即使 XML 文件中 Bean 的 id 或 name 与类中的属性名不同,只要 Bean 的 class 属性值与类中的对象属性的类型相同,就可以完成自动装配。
    
    注意:如果同时存在多个相同类型的 Bean,则注入失败,并且引发异常。

<!-- 自动装配 -->
<bean id="dept" class="com.newcapec.bean.Dept" p:deptno="10" p:dname="研发部" p:loc="郑州"/>
<bean id="department" class="com.newcapec.bean.Dept" c:deptno="20" c:dname="产品部" c:loc="杭州"/>

<bean id="emp" class="com.newcapec.bean.Emp" p:empno="7689" p:ename="马六" autowire="byType"/>
  • 2.5.5.4 构造函数自动装配(autowire="constructor")

    autowire="constructor" 表示按照 Java 类中构造函数进行自动装配。
    
    很少使用,不再演示。

  • 2.5.5.5 默认的自动装配模式(autowire="default")

    默认采用上一级标签 <beans> 设置的自动装配规则(default-autowire)进行装配,Beans.xml 中的配置内容如下。 

<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-3.0.xsd" 
       default-autowire="byName">
    
    <!-- 自动装配 -->
    <bean id="dept" class="com.newcapec.bean.Dept" p:deptno="10" p:dname="研发部" p:loc="郑州"/>
    <bean id="department" class="com.newcapec.bean.Dept" c:deptno="20" c:dname="产品部" c:loc="杭州"/>

    <bean id="emp" class="com.newcapec.bean.Emp" p:empno="7689" p:ename="马六" autowire="default"/>
</beans>

XML配置里的Bean自动装配的缺点:

  • 在bean配置文件里设置autowire属性进行自动装配将会装配bean的所有属性。然而,若只希望装配个别属性时,autowire属性就不够灵活了;

  • autowire属性要么根据类型自动装配,要么根据名称自动装配,不能两者兼而有之;

  • 一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些;

2.6 使用外部属性文件

加入命名空间和标签规范:

xmlns:context="http://www.springframework.org/schema/context"

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

db.properties:

# Mysql相关配置
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&useSSL=false
jdbc.mysql.username=root
jdbc.mysql.password=root

引入外部属性文件:

<!-- 读取外部的资源文件-->
<context:property-placeholder location="classpath:db.properties"/>

MyDataSource.java:

public class MyDataSource {
    private String driver;
    private String url;
    private String username;
    private String password;

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "MyDataSource{" +
                "driver='" + driver + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

通过表达式访问属性文件中的数据:

<bean id="myDataSource" class="com.newcapec.bean.MyDataSource">
    <property name="driver" value="${jdbc.mysql.driver}"/>
    <property name="url" value="${jdbc.mysql.url}"/>
    <property name="username" value="${jdbc.mysql.username}"/>
    <property name="password" value="${jdbc.mysql.password}"/>
</bean>

测试:

public class ReadPropertiesTest {

    @Test
    public void test() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        MyDataSource myDataSource = ac.getBean("myDataSource", MyDataSource.class);
        System.out.println(myDataSource);
    }
}

2.7 Bean的作用域

    默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中,Bean 的实例只有一个。

    我们可以在 <bean> 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

    Spring 5 共提供了 6 种 scope 作用域,如下表。

作用范围描述
singleton默认值,单例模式。表示IOC容器在初始化时创建bean的实例,在整个容器的生命周期中只创建一个bean实例。
prototype原型模式,表示IOC容器在初始化时不创建bean的实例,而是在每次使用时都创建一个新的bean的实例。
request每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
session同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
application同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
websocketwebsocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。

    注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。
    
    本节,我们就只对 singleton 和 prototype 这两种 Bean 作用域进行详细的讲解,至于其他 Bean 作用域,我们会在后续的课程中陆续进行介绍。

Book.java:

public class Book {
    private int id;
    private String name;
    private double price;
    private String author;

    public Book(){
        System.out.println("Book对象被创建....");
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", author='" + author + '\'' +
                '}';
    }
}

2.7.1 singleton

    singleton 是 Spring 容器默认的作用域。当 Bean 的作用域为 singleton 时,Spring IoC 容器中只会存在一个共享的 Bean 实例。这个 Bean 实例将存储在高速缓存中,所有对于这个 Bean 的请求和引用,只要 id 与这个 Bean 定义相匹配,都会返回这个缓存中的对象实例。

    如果一个 Bean 定义的作用域为 singleton ,那么这个 Bean 就被称为 singleton bean。在 Spring IoC 容器中,singleton bean 是 Bean 的默认创建方式,可以更好地重用对象,节省重复创建对象的开销。

    在 Spring 配置文件中,可以使用 <bean> 元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:

applicationContext.xml:

<!-- singleton -->
<bean id="book" class="com.newcapec.bean.Book" p:id="101" p:name="西游记"
      p:price="98.5" p:author="吴承恩" scope="singleton"/>

2.7.2 prototype

    如果一个 Bean 定义的作用域为 prototype,那么这个 Bean 就被称为 prototype bean。对于 prototype bean 来说,Spring 容器会在每次请求该 Bean 时,都创建一个新的 Bean 实例。
    
    从某种意义上说,Spring  IoC 容器对于 prototype bean 的作用就相当于 Java 的 new 操作符。它只负责 Bean 的创建,至于后续的生命周期管理则都是由客户端代码完成的。
    
    在 Spring 配置文件中,可以使用 <bean> 元素的 scope 属性将 Bean 的作用域定义成 prototype,其配置方式如下所示:

<!-- prototype -->
<bean id="book" class="com.newcapec.bean.Book" p:id="101" p:name="西游记"
      p:price="98.5" p:author="吴承恩" scope="prototype"/>

测试:

public class ScopeTest {
    @Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("------------分割线--------------");

        Book book1 = ac.getBean("book",Book.class);
        System.out.println(book1);
        Book book2 = ac.getBean("book",Book.class);
        System.out.println(book2);

        System.out.println(book1 == book2);
    }
}

2.8 Bean的生命周期

    在传统的 Java 应用中,Bean 的生命周期很简单,使用 Java 关键字 new 进行 Bean 的实例化后,这个 Bean 就可以使用了。一旦这个 Bean 长期不被使用,Java 自动进行垃圾回收。

    相比之下,Spring 中 Bean 的生命周期较复杂,SpringIOC容器可以管理Bean的生命周期,Spring允许在Bean生命周期的特定点执行定制的任务,大致可以分为以下 5 个阶段:
        1、Bean 的实例化
        2、Bean 属性赋值
        3、Bean 的初始化
        4、Bean 的使用
        5、Bean 的销毁
        
    Spring 根据 Bean 的作用域来选择 Bean 的管理方式,
        对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;
        对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。

2.8.1 Spring 生命周期流程

    Spring Bean 的完整生命周期从创建 Spring IoC 容器开始,直到最终 Spring IoC 容器销毁 Bean 为止,其具体流程如下图所示。

Bean 生命周期的整个执行过程描述如下:

  1. Spring 启动,查找并加载需要被 Spring 管理的 Bean,对 Bean 进行实例化。

  2. 对 Bean 进行属性注入。

  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。

  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。

  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。

  6. 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。

  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。

  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。

  10. 如果在 <bean> 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。

  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

2.8.2 自定义 Bean 的生命周期

    我们可以在 Spring Bean 生命周期的某个特定时刻,指定一些生命周期回调方法完成一些自定义的操作,对 Bean 的生命周期进行管理。

Bean 的生命周期回调方法主要有两种:

  • 初始化回调方法:在 Spring Bean 被初始化后调用,执行一些自定义的回调操作。

  • 销毁回调方法:在 Spring Bean 被销毁前调用,执行一些自定义的回调操作。

我们可以通过以下 3 种方式自定义 Bean 的生命周期回调方法:

  • 通过接口实现

  • 通过 XML 配置实现

  • 使用注解实现

    如果一个 Bean 中有多种生命周期回调方法时,优先级顺序为:注解 > 接口 > XML 配置。

Dog.java:

public class Dog {
    private String name;
    private String owner;
    private int age;

    public Dog() {
        System.out.println("Dog对象被创建...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("调用setName方法....");
        this.name = name;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        System.out.println("调用setOwner方法....");
        this.owner = owner;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用setAge方法....");
        this.age = age;
    }

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

applicationContext.xml:

<bean id="dog" class="com.newcapec.bean.Dog" p:name="旺财" p:owner="小明" p:age="5"/>

测试:

public class LifeCycleTest {
    @Test
    public void testLifeCycle() {
        System.out.println("-------容器初始化阶段---------");
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        System.out.println("-------对象使用阶段---------");
        Dog dog = ac.getBean("dog", Dog.class);
        System.out.println(dog);

        System.out.println("-------容器关闭阶段---------");
        //手动关闭容器
        ac.close();//ApplicationContext没有close()
    }
}
  • 2.8.2.1 通过接口实现

    我们可以在 Spring Bean 的 Java 类中,通过实现 InitializingBean 和 DisposableBean 接口,指定 Bean 的生命周期回调方法。
    
    注意:通常情况下,我们不建议通过这种方式指定生命周期回调方法,这是由于这种方式会导致代码的耦合性过高。

回调方式接口方法说明
初始化回调InitializingBeanafterPropertiesSet()指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。
销毁回调DisposableBeandestroy()指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。

Dog.java:

public class Dog implements InitializingBean, DisposableBean {
    private String name;
    private String owner;
    private int age;

    public Dog() {
        System.out.println("Dog对象被创建...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("调用setName方法....");
        this.name = name;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        System.out.println("调用setOwner方法....");
        this.owner = owner;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用setAge方法....");
        this.age = age;
    }

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

    /**
     * 初始化回调逻辑
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(" 调用接口:InitializingBean,方法:afterPropertiesSet,无参数");
    }
    /**
     * 销毁回调逻辑
     *
     * @throws Exception
     */
    @Override
    public void destroy() throws Exception {
        System.out.println(" 调用接口:DisposableBean,方法:destroy,无参数");
    }
}
  • 2.8.2.2 通过 XML 配置实现

    我们还可以在 Spring 的 XML 配置中,通过 <bean> 元素中的 init-method 和 destory-method 属性,指定 Bean 的生命周期回调方法。

XML 配置属性描述
init-method指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。
destory-method指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。

Dog.java:

public class Dog {
    private String name;
    private String owner;
    private int age;

    public Dog() {
        System.out.println("Dog对象被创建...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("调用setName方法....");
        this.name = name;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        System.out.println("调用setOwner方法....");
        this.owner = owner;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用setAge方法....");
        this.age = age;
    }

    public void init(){
        System.out.println("Dog对象的初始化方法...");
    }
    
    public void destroy(){
        System.out.println("Dog对象的销毁方法...");
    }

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

applicationContext.xml:

<bean id="dog" class="com.newcapec.bean.Dog" p:name="旺财" p:owner="小明" p:age="5"  
      init-method="init" destroy-method="destroy"/>

2.9 Bean之间的关系

2.9.1 继承关系

  • Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean;

  • 子bean从父bean中继承配置, 包括bean的属性配置;

  • 子bean也可以覆盖从父bean继承过来的配置;

  • 父bean可以作为配置模板,也可以作为bean实例。若只想把父bean作为模板,可以设置<bean>的abstract属性为true,这样Spring将不会实例化这个bean;

  • 并不是<bean>元素里的所有属性都会被继承。比如: autowire,abstract等;

  • 也可以忽略父bean的class属性(不需要创建父bean对象,仅仅为子bean提供模版),让子bean指定自己的类,而共享相同的属性配置。但此时abstract必须设为true;

Animal.java:

public class Animal {
    private String name;
    private Integer age;

    public void setName(String name) {
        this.name = name;
    }

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

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

applicationContext.xml:

<!-- bean之间的继承关系 -->
<bean id="animal" class="com.newcapec.bean.Animal">
    <property name="name" value="动物"></property>
    <property name="age" value="10"></property>
</bean>
<bean id="dog" class="com.newcapec.bean.Dog" parent="animal">
    <property name="name" value="小狗"></property>
    <property name="owner" value="小黑"></property>
</bean>

测试:

public class RelationTest {
    @Test
    public void testExtends() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Dog dog = ac.getBean("dog", Dog.class);
        System.out.println(dog);
    }
}

定义模板:

    在父 Bean 的定义中,有一个十分重要的属性,那就是 abstract 属性。如果一个父 Bean 的 abstract 属性值为 true,则表明这个 Bean 是抽象的。

    抽象的父 Bean 只能作为模板被子 Bean 继承,它不能实例化,也不能被其他 Bean 引用,更不能在代码中根据 id 调用 getBean() 方法获取,否则就会返回错误。

    在父 Bean 的定义中,既可以指定 class 属性,也可以不指定 class 属性。如果父 Bean 定义没有明确地指定 class 属性,那么这个父 Bean 的 abstract 属性就必须为 true。

applicationContext.xml:

<!-- bean模版 -->
<bean id="animal" abstract="true">
    <property name="name" value="动物"></property>
    <property name="age" value="10"></property>
</bean>

<bean id="dog" class="com.newcapec.bean.Dog" parent="animal">
    <property name="name" value="小狗"></property>
    <property name="owner" value="小黑"></property>
</bean>

2.9.2 依赖关系

  • Spring允许用户通过depends-on属性设定bean前置依赖的bean,前置依赖的bean会在本bean实例化之前创建好;

  • 如果前置依赖于多个bean,则可以通过逗号或空格的方式配置bean的名称;

Address.java:

public class Address {
    public Address() {
        System.out.println("Address对象创建了....");
    }
}

User.java:

public class User {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public User() {
        System.out.println("User对象创建了....");
    }

    @Override
    public String toString() {
        return "User{" +
                "address=" + address +
                '}';
    }
}

applicationContext.xml:

<!-- bean之间的依赖关系 -->
<bean id="user" class="com.newcapec.bean.User" p:address-ref="address" depends-on="address"/>

<bean id="address" class="com.newcapec.bean.Address"/>

测试:

public class RelationTest {
    @Test
    public void testDependsOn(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        User user = ac.getBean("user", User.class);
        System.out.println(user);
    }
}

2.10 通过工厂实例化Bean

2.10.1 静态工厂

  • 调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中。当客户端需要对象时,只需要简单地调用静态方法,而无需关心创建对象的细节;

  • 要声明通过静态方法创建的bean,需要在bean的class属性里指定拥有该工厂的方法的类,同时在factory-method属性里指定工厂方法的名称。最后,使用<constrctor-arg>元素为该方法传递方法参数;

Cat.java:

public class Cat {
    private String name;
    private int age;

    public Cat() {
    }

    public Cat(String name, int age) {
        this.name = name;
        this.age = 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 "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

CatStaticFactory.java:

/**
 * 一个用于创建cat对象的静态工厂
 */
public class CatStaticFactory {
    /**
     * 提供一个创建对象的静态方法
     */
    public static Cat getInstance(String name, int age){
        return new Cat(name, age);
    }
}

applicationContext.xml:

<!-- 静态工厂-->
<bean id="cat1" class="com.newcapec.factory.CatStaticFactory" factory-method="getInstance">
    <constructor-arg value="汤姆猫"/>
    <constructor-arg value="2"/>
</bean>

测试:

public class FactoryTest {
    @Test
    public void testStaticFactory() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Cat cat = ac.getBean("cat1", Cat.class);
        System.out.println(cat);
    }
}

2.10.2 实例工厂

  • 将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节;

  • 要声明通过实例工厂方法创建的bean,在<bean>的factory-bean属性里指定拥有该工厂方法的bean,并且factory-method属性里指定该工厂方法的名称。最后,使用<construtor-arg>标签为工厂方法传递方法参数;

CatInstanceFactory.java:

/**
 * 一个用于创建cat对象的实例工厂
 */
public class CatInstanceFactory {
    /**
     * 提供一个创建对象的非静态方法
     */
    public Cat getInstance(String name, int age){
        return new Cat(name, age);
    }
}

applicationContext.xml:

<!-- 实例工厂-->
<bean id="instanceFactory" class="com.newcapec.factory.CatInstanceFactory"/>

<bean id="cat2" factory-bean="instanceFactory" factory-method="getInstance">
    <constructor-arg value="波斯猫"/>
    <constructor-arg value="3"/>
</bean>

测试:

public class FactoryTest {
    @Test
    public void testInstanceFactory() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Cat cat = ac.getBean("cat2", Cat.class);
        System.out.println(cat);
    }
}

2.11 FactoryBean实例化Bean

  • Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean;

  • 工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象;

CatFactoryBean.java:

public class CatFactoryBean implements FactoryBean<Cat> {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

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

    /**
     * 获取对象
     */
    @Override
    public Cat getObject() throws Exception {
        return new Cat(name, age);
    }

    /**
     * 生成对象的Class类型
     */
    @Override
    public Class<?> getObjectType() {
        return Cat.class;
    }

    /**
     * 设置该对象是否为单例模式
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

applicationContext.xml:

<!-- FactoryBean配置bean -->
<bean id="cat3" class="com.newcapec.factory.CatFactoryBean">
    <property name="name" value="加菲猫"/>
    <property name="age" value="5"/>
</bean>

测试:

public class FactoryTest {
    @Test
    public void testFactoryBean(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Cat cat = ac.getBean("cat3", Cat.class);
        System.out.println(cat);
    }
}

2.12 <beans> 元素常用的属性或子元素

    在 XML 配置的<beans> 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。

属性名称描述
idBean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。
name该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。
class该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
scope表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。
constructor-arg<bean> 元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。
property<bean>元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。
ref<property><constructor-arg> 等元素的子元索,用于指定对某个 Bean 实例的引用,即 <bean> 元素中的 id 或 name 属性。
value<property><constractor-arg> 等元素的子元素,用于直接指定一个常量值。
list用于封装 List 或数组类型的属性注入。
set用于封装 Set 类型的属性注入。
map用于封装 Map 类型的属性注入。
entry<map> 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值。
init-method容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
destroy-method容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
lazy-init懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是波哩个波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值