Spring学习(四)Spring IOC

Spring 学习目录

前言

深入理解DIP、IoC、DI以及IoC容器https://www.cnblogs.com/liuhaorain/p/3747470.html

转载自:[Spring框架入门教程 (biancheng.net)](http://c.biancheng.net/spring/bean-autowiring.html)

为什么要使用IOC

传统开发模式的弊端

三层架构是经典的开发模式,我们一般将视图控制、业务逻辑和数据库操作分别抽离出来单独形成一个类,这样各个职责就非常清晰且易于复用和维护。大致代码如下:

用户DAO

public class UserDAO {

    private String database;

    public UserDAO(String dataBase) {
        this.database = dataBase;
    }
    public void doSomething() {
        System.out.println("保存用户!");
    }

}

用户Service

public class UserService {

    private UserDAO dao;

    public UserService(UserDAO dao) {
        this.dao = dao;
    }
    public void doSomething() {
        dao.doSomething();
    }

}

用户Controller

public class Controller {

    public UserService service;

    public Controller(UserService userService) {
        this.service = userService;
    }

    public void doSomething() {
        service.doSomething();
    }

}

接下来我们就必须手动一个一个创建对象,并将dao、service、controller依次组装起来,然后才能调用。

public static void main(String[] args) {

    UserDAO dao = new UserDAO("mysql");
    UserService service = new UserService(dao);
    Controller controller = new Controller(service);

    controller.doSomething();

}

分析一下这种做法的弊端有哪些呢?

  1. 在生成Controller的地方我们都必须先创建dao再创建service最后再创建Controller,这么一条繁琐的创建过程。
  2. 在这三层结构当中,上层都需要知道下层是如何创建的,上层必须自己创建下层,这样就形成了紧密耦合。为什么业务程序员在写业务的时候却需要知道数据库的密码并自己创建dao呢?不仅如此,当如果dao的数据库密码变化了,在每一处生成Controller的地方都需要进行修改。
  3. 通过new关键字生成了具体的对象,这是一种硬编码的方式,违反了面向接口编程的原则。当有一天我们从Hibernate更换到Mybatis的时候,在每一处new DAO的地方,我们都需要进行更换。
  4. 我们频繁的创建对象,浪费了资源。

这时候我们再来看看如果用SpringIOC的情况,刚才的代码变成如下。

public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
        Controller controller = (Controller) context.getBean("controller");
        controller.doSomething();
        
    }

很明显,使用IOC之后,我们只管向容器索取所需的Bean即可。IOC便解决了以下的痛点:

  1. Bean之间的解耦,这种解耦体现在我们没有在代码中去硬编码bean之间的依赖。(不通过new操作依次构建对象,由springIOC内部帮助我们实现了依赖注入)。一方面,IOC容器将通过new对象设置依赖的方式转变为运行期动态的进行设置依赖。
  2. IOC容器天然地给我们提供了单例。
  3. 当需要更换dao的时候,我们只需要在配置文件中更换dao的实现类,完全不会破坏到之前的代码。
  4. 上层现在不需要知道下层是如何创建的。

Spring IoC容器

IoC容器是Spring的核心,也可以称为Spring容器。Spring 通过 IoC 容器来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。

Spring 中使用的对象都由 IoC 容器管理,不需要我们手动使用 new 运算符创建对象。由 IoC 容器管理的对象称为 Spring Bean,Spring Bean 就是 Java 对象,和使用 new 运算符创建的对象没有区别。

Spring 通过读取 XML 或 Java 注解中的信息来获取哪些对象需要实例化。

Spring 提供 2 种不同类型的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器。

BeanFactory 容器

BeanFactory 是最简单的容器,由 org.springframework.beans.factory.BeanFactory 接口定义,采用懒加载(lazy-load),所以容器启动比较快。BeanFactory 提供了容器最基本的功能。

为了能够兼容 Spring 集成的第三方框架(如 BeanFactoryAware、InitializingBean、DisposableBean),所以目前仍然保留了该接口。

简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。

BeanFactory 接口有多个实现类,最常见的是 org.springframework.beans.factory.xml.XmlBeanFactory。使用 BeanFactory 需要创建 XmlBeanFactory 类的实例,通过 XmlBeanFactory 类的构造函数来传递 Resource 对象。如下所示。

Resource resource = new ClassPathResource("applicationContext.xml"); 
BeanFactory factory = new XmlBeanFactory(resource);  

ApplicationContext 容器

ApplicationContext 继承了 BeanFactory 接口,由 org.springframework.context.ApplicationContext 接口定义,对象在启动容器时加载。ApplicationContext 在 BeanFactory 的基础上增加了很多企业级功能,例如 AOP、国际化、事件支持等。

ApplicationContext 接口有两个常用的实现类,具体如下。

ClassPathXmlApplicationContext

该类从类路径 ClassPath 中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);

在上述代码中,configLocation 参数用于指定 Spring 配置文件的名称和位置,如 Beans.xml。

FileSystemXmlApplicationContext

该类从指定的文件系统路径中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。

ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不会从类路径中读取配置文件,而是通过参数指定配置文件的位置。即 FileSystemXmlApplicationContext 可以获取类路径之外的资源,如“F:/workspaces/Beans.xml”。

通常在 Java 项目中,会采用 ClassPathXmlApplicationContext 类实例化 ApplicationContext 容器的方式,而在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。Web 服务器实例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 实现的方式,它只需要在 web.xml 中添加如下代码:

<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <!--spring将加载spring目录下的applicationContext.xml文件-->
    <param-value>
        classpath:spring/applicationContext.xml
    </param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

需要注意的是,BeanFactory 和 ApplicationContext 都是通过 XML 配置文件加载 Bean 的。

二者的主要区别在于,如果 Bean 的某一个属性没有注入,使用 BeanFacotry 加载后,第一次调用 getBean() 方法时会抛出异常,而 ApplicationContext 则会在初始化时自检,这样有利于检查所依赖的属性是否注入。

因此,在实际开发中,通常都选择使用 ApplicationContext,只有在系统资源较少时,才考虑使用 BeanFactory。

Spring Bean定义

由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。

可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品,如果希望这个大工厂生产和管理 Bean,则需要告诉容器需要哪些 Bean,以及需要哪种方式装配 Bean。

Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。

  • Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
  • XML 配置文件是树形结构,相对于 Properties 文件来说更加灵活。XML 配置文件结构清晰,但是内容比较繁琐,适用于大型复杂的项目。

通常情况下,Spring 的配置文件使用 XML 格式。XML 配置文件的根元素是 <beans>,该元素包含了多个子元素 <bean>。每一个 <bean> 元素都定义了一个 Bean,并描述了该 Bean 如何被装配到 Spring 容器中。

在上一节中的 Beans.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-3.0.xsd">

    <bean id="helloWorld" class="com.springlearn.HelloWorld">
        <property name="message" value="Hello World!" />
    </bean>

</beans>

解释

  • xmlns="http://www.springframework.org/schema/beans"

    声明xml文件默认的命名空间,表示未使用其他命名空间的所有标签的默认命名空间。

  • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    声明XML Schema实例,声明后就可以使用schemaLocation属性。

  • xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd“

    指定Schema的位置这个属性必须结合命名空间使用。这个属性有两个值,第一个值表示需要使用的命名空间。第二个值表示供命名空间使用的XML schema的位置。

    上面配置的命名空间指定xsd规范文件,这样你在进行下面具体配置的时候就会根据这些xsd规范文件给出相应的提示,比如说每个标签是怎么写的,都有些什么属性是都可以智能提示的,在启动服务的时候也会根据xsd规范对配置进行校验。

    配置技巧:对于属性值的写法是有规律的,中间使用空格隔开,后面的值是前面的补充,也就是说,前面的值是去除了xsd文件后得来的。

上述代码中,使用 id 属性定义了 Bean,并使用 class 属性指定了 Bean 对应的类。

<bean> 元素中可以包含很多属性,其常用属性如下表所示。

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

Spring Bean作用域

Bean的生命周期,从创建到销毁。

在配置文件中,除了可以定义 Bean 的属性值和相互之间的依赖关系,还可以声明 Bean 的作用域。例如,如果每次获取 Bean 时,都需要一个 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

作用域的种类

Spring 容器在初始化一个 Bean 实例时,同时会指定该实例的作用域。Spring 5 支持以下 6 种作用域。

  • singleton

    默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例,Bean 以单例的方式存在。

  • prototype

    原型模式,表示每次通过 Spring 容器获取 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 应用中货真价实的单例模式。

  • websocket

    websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。

注意:Spring 5 版本之前还支持 global Session,该值表示在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。一般用于 Portlet 应用环境。Spring 5.2.0 版本中已经将该值移除了。

request、session、application、websocket 和 global Session 作用域只能在 Web 环境下使用,如果使用 ClassPathXmlApplicationContext 加载这些作用域中的任意一个的 Bean,就会抛出以下异常。

java.lang.IllegalStateException: No Scope registered for scope name 'xxx'

下面我们详细讲解常用的两个作用域:singleton 和 prototype。

singleton

singleton 是 Spring 容器默认的作用域。

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

通常情况下,这种单例模式对于无会话状态的 Bean(如 DAO 层、Service 层)来说,是最理想的选择。

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

<bean id="..." class="..." scope="singleton"/>
例子

我们重新改装MainApp类

package com.springlearn;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.setMessage("对象A");
        objA.getMessage();
        HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
        objB.getMessage();
    }
}

由于Spring 容器默认的作用域是singleton。

所以Bean.xml可以不用修改,当前你也可以加上scope

<?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="helloWorld" class="com.springlearn.HelloWorld" scope="singleton" />
</beans>

运行结果如下

message : 对象A
message : 对象A

从运行结果可以看出,两次输出内容相同,这说明 Spring 容器只创建了一个 HelloWorld 类的实例。由于 Spring 容器默认作用域是 singleton,所以如果省略 scope 属性,其输出结果也会是一个实例。

prototype

瞬时

对于 prototype 作用域的 Bean,Spring 容器会在每次请求该 Bean 时都创建一个新的 Bean 实例。prototype 作用域适用于需要保持会话状态的 Bean(如 Struts2 的 Action 类)。

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

<bean id="..." class="..." scope="prototype"/>
例子

修改配置文件 Beans.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-3.0.xsd">

    <bean id="helloWorld" class="com.springlearn.HelloWorld"  scope="prototype" />

</beans>

运行结果如下

message : 对象A
message : null

从运行结果可以看出,两次输出的内容并不相同,这说明在 prototype 作用域下,Spring 容器创建了两个不同的 HelloWorld 实例。

Spring Bean生命周期

在传统的 Java 应用中,Bean 的生命周期很简单,使用关键字 new 实例化 Bean,当不需要该 Bean 时,由 Java 自动进行垃圾回收。

Spring 中 Bean 的生命周期为:Bean 的定义 -> Bean 的初始化 -> Bean 的使用 -> Bean 的销毁。

Spring 根据 Bean 的作用域来选择管理方式。对于 singleton 作用域的 Bean,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁;而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

Spring Bean生命周期执行流程

Spring 容器在确保一个 Bean 能够使用之前,会进行很多工作。Spring 容器中 Bean 的生命周期流程如下图所示。

o_211215060522_111.jpg (1154×959) (cnblogs.com)

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 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

Spring 为 Bean 提供了细致全面的生命周期过程,实现特定的接口或设置 的属性都可以对 Bean 的生命周期过程产生影响。建议不要过多的使用 Bean 实现接口,因为这样会导致代码的耦合性过高。

了解 Spring 生命周期的意义就在于,可以利用 Bean 在其存活期间的指定时刻完成一些相关操作。一般情况下,会在 Bean 被初始化后和被销毁前执行一些相关操作。

Spring 官方提供了 3 种方法实现初始化回调和销毁回调:

  1. 实现 InitializingBean 和 DisposableBean 接口;
  2. 在 XML 中配置 init-method 和 destory-method;
  3. 使用 @PostConstruct 和 @PreDestory 注解。

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

不建议使用接口和注解,这会让 pojo 类和 Spring 框架紧耦合。

初始化回调

  1. 使用接口

    修改HelloWorld

    package com.springlearn;
    
    import org.springframework.beans.factory.InitializingBean;
    
    public class HelloWorld implements InitializingBean {
        private String message;
        public void setMessage(String message) {
            this.message = message;
        }
        public void getMessage() {
            System.out.println("message : " + message);
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.printf("调用接口:InitializingBean,方法:afterPropertiesSet,无参数");
        }
    }
    
    调用接口:InitializingBean,方法:afterPropertiesSet,无参数message : 对象A
    调用接口:InitializingBean,方法:afterPropertiesSet,无参数message : null
    
  2. 配置XML

    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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    
        <bean id="helloWorld" class="com.springlearn.HelloWorld" init-method="init" scope="prototype" />
    
    </beans>
    

    添加init()方法

    package com.springlearn;
    
    import org.springframework.beans.factory.InitializingBean;
    
    public class HelloWorld{
        private String message;
        public void setMessage(String message) {
            this.message = message;
        }
        public void getMessage() {
            System.out.println("message : " + message);
        }
        public void init() {
            System.out.println("调用init-method指定的初始化方法:init" );
        }
    }
    
    
  3. 使用注解

    使用 @PostConstruct 注解标明该方法为 Bean 初始化后的方法。

    public class HelloWrold {
        @PostConstruct
        public void init() {
            System.out.println("@PostConstruct注解指定的初始化方法:init" );
        }
    }
    

    使用@PostConstruct注解需要引入javax.annotation

    pom.xml中添加

    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>jsr250-api</artifactId>
        <version>1.0</version>
    </dependency>
    

销毁回调

  1. 使用接口

    org.springframework.beans.factory.DisposableBean 接口提供了以下方法:

    void destroy() throws Exception;
    

    您可以实现以上接口,在 destroy 方法内指定 Bean 初始化后需要执行的操作。

    <bean id="..." class="..." />
    public class HelloWrold implements InitializingBean {
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("调用接口:InitializingBean,方法:afterPropertiesSet,无参数");
        }
    }
    
  2. 配置XML

    <bean id="..." class="..." destroy-method="destroy"/>
    public class HelloWrold {
        public void destroy() {
            System.out.println("调用destroy-method指定的销毁方法:destroy" );
        }
    }
    
  3. 使用注解

    使用 @PreDestory 注解标明该方法为 Bean 销毁前执行的方法。

    public class HelloWrold {
        @PreDestory 
        public void init() {
            System.out.println("@PreDestory注解指定的初始化方法:destroy" );
        }
    }
    

示例

HelloWorld

package com.springlearn;

public class HelloWorld{
    private String message;
    public void setMessage(String message) {
        this.message = message;
    }
    public void getMessage() {
        System.out.println("message : " + message);
    }
    public void init() {
        System.out.println("Bean正在进行初始化");
    }
    public void destroy() {
        System.out.println("Bean将要被销毁");
    }
}

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

    <bean id="helloWorld" class="com.springlearn.HelloWorld"  init-method="init" destroy-method="destroy">
    <property name="message" value="Hello World!" />
    </bean>

</beans>

MainApp

package com.springlearn;


import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");

        Thread.currentThread();

        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.getMessage();
        //销毁容器
        context.registerShutdownHook();
    }
}

Bean正在进行初始化
message : Hello World!
Bean将要被销毁

默认的初始化和销毁方法

如果多个 Bean 需要使用相同的初始化或者销毁方法,不用为每个 bean 声明初始化和销毁方法,可以使用 default-init-method 和 default-destroy-method 属性,如下所示。

<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-init-method="init"
    default-destroy-method="destroy">
    <bean id="..." class="...">
        ...
    </bean>
</beans>

BeanPostProcessor(Spring后置处理器)

BeanPostProcessor 接口也被称为后置处理器,通过该接口可以自定义调用初始化前后执行的操作方法。

BeanPostProcessor 接口源码如下:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

postProcessBeforeInitialization 在 Bean 实例化、依赖注入后,初始化前调用。postProcessAfterInitialization 在 Bean 实例化、依赖注入、初始化都完成后调用。

当需要添加多个后置处理器实现类时,默认情况下 Spring 容器会根据后置处理器的定义顺序来依次调用。也可以通过实现 Ordered 接口的 getOrder 方法指定后置处理器的执行顺序。该方法返回值为整数,默认值为 0,值越大优先级越低。

新建InitHelloWorld 类代码如下

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("A Before : " + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("A After : " + beanName);
        return bean;
    }
    @Override
    public int getOrder() {
        return 5;
    }
}

需要注意的是,postProcessBeforeInitialization 和 postProcessAfterInitialization 方法返回值不能为 null,否则会报空指针异常或者通过 getBean() 方法获取不到 Bean 实例对象,因为后置处理器从Spring IoC 容器中取出 Bean 实例对象后没有再次放回到 IoC 容器中。

InitHelloWorld2 的代码如下。

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld2 implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("B Before : " + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("B After : " + beanName);
        return bean;
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

beans.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-3.0.xsd">
    <bean id="helloWorld" class="net.biancheng.HelloWorld"
        init-method="init" destroy-method="destroy">
        <property name="message" value="Hello World!" />
    </bean>
    <!-- 注册处理器 -->
    <bean class="net.biancheng.InitHelloWorld" />
    <bean class="net.biancheng.InitHelloWorld2" />
</beans>

MainApp

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
        obj.getMessage();
        context.registerShutdownHook();
    }
}
B Before : helloWorld
A Before : helloWorld
Bean正在初始化
B After : helloWorld
A After : helloWorld
Message : Hello World!
Bean将要被销毁

由运行结果可以看出,postProcessBeforeInitialization 方法是在 Bean 实例化和依赖注入后,自定义初始化方法前执行的。而 postProcessAfterInitialization 方法是在自定义初始化方法后执行的。由于 getOrder 方法返回值越大,优先级越低,所以 InitHelloWorld2 先执行。

Spring Bean继承

Bean 定义可以包含很多配置信息,包括构造函数参数、属性值和容器的一些具体信息,如初始化方法、销毁方法等。子 Bean 可以继承父 Bean 的配置数据,根据需要,子 Bean 可以重写值或添加其它值。

需要注意的是,Spring Bean 定义的继承与 Java 中的继承无关。您可以将父 Bean 的定义作为一个模板,其它子 Bean 从父 Bean 中继承所需的配置。

在配置文件中通过 parent 属性来指定继承的父 Bean。

package com.springlearn;

public class HelloWorld {
    private String message1;
    private String message2;
    public void setMessage1(String message) {
        this.message1 = message;
    }
    public void setMessage2(String message) {
        this.message2 = message;
    }
    public void getMessage1() {
        System.out.println("World Message1 : " + message1);
    }
    public void getMessage2() {
        System.out.println("World Message2 : " + message2);
    }
}
package com.springlearn;

public class HelloWorld2 {
    private String message1;
    private String message2;
    private String message3;
    public void setMessage1(String message) {
        this.message1 = message;
    }
    public void setMessage2(String message) {
        this.message2 = message;
    }
    public void setMessage3(String message) {
        this.message3 = message;
    }
    public void getMessage1() {
        System.out.println("HelloWorld2 Message1 : " + message1);
    }
    public void getMessage2() {
        System.out.println("HelloWorld2 Message2 : " + message2);
    }
    public void getMessage3() {
        System.out.println("HelloWorld2 Message3 : " + message3);
    }
}

在配置文件中,分别为 HelloWorld 中的 message1 和 message2 赋值。使用 parent 属性将 HelloWorld2 定义为 HelloWorld 的子类,并为 HelloChain 中的 message1 和 message3 赋值。Beans.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-3.0.xsd">

    <bean id="helloWorld" class="com.springlearn.HelloWorld">
        <property name="message1" value="Hello World! Parent" />
        <property name="message2" value="Hello World2!Parent" />
    </bean>
    <bean id="helloWorld2" class="com.springlearn.HelloWorld2" parent="helloWorld">
        <property name="message1" value="Hello World! Child" />
        <property name="message3" value="Hello World3!Child" />
    </bean>

</beans>
package com.springlearn;


import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.getMessage1();
        objA.getMessage2();
        HelloWorld2 objB =(HelloWorld2) context.getBean("helloWorld2");
        objB.getMessage1();
        objB.getMessage2();
        objB.getMessage3();

        //销毁容器
        context.registerShutdownHook();
    }
}

输出结果如下

World Message1 : Hello World! Parent
World Message2 : Hello World2!Parent
HelloWorld2 Message1 : Hello World! Child
HelloWorld2 Message2 : Hello World2!Parent
HelloWorld2 Message3 : Hello World3!Child

由结果可以看出,我们在创建 helloWorld2 时并没有给 message2 赋值,但是由于 Bean 的继承,将值传递给了 message2。

定义模板

您可以创建一个 Bean 定义模板,该模板只能被继承,不能被实例化。创建 Bean 定义模板时,不用指定 class 属性,而是指定 abstarct="true" 将该 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="beanTeamplate" class="com.springlearn.HelloWorld" abstract="true">
        <property name="message1" value="Hello World! Parent" />
        <property name="message2" value="Hello World2!Parent" />
    </bean>
    <bean id="helloWorld2" class="com.springlearn.HelloWorld2" parent="beanTeamplate">
        <property name="message1" value="Hello World! Child" />
        <property name="message3" value="Hello World3!Child" />
    </bean>

</beans>

Spring依赖注入

Spring 依赖注入(Dependency Injection,DI)和控制反转含义相同,它们是从两个角度描述的同一个概念。使用依赖注入可以更轻松的管理和测试应用程序。

当某个 Java 实例需要另一个 Java 实例时,传统的方法是由调用者创建被调用者的实例(例如,使用 new 关键字获得被调用者实例),而使用 Spring 框架后,被调用者的实例不再由调用者创建,而是由 Spring 容器创建,这称为控制反转。

Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,调用者通过 Spring 容器获得被调用者实例,这称为依赖注入。

依赖注入主要有两种实现方式,分别是 setter 注入(又称设值注入)和构造函数注入。具体介绍如下。

  • 构造函数注入

    指 IoC 容器使用构造函数注入被依赖的实例。可以通过调用带参数的构造函数实现依赖注入,每个参数代表一个依赖。

  • setter 注入

    指 IoC 容器使用 setter 方法注入被依赖的实例。通过调用无参构造器或无参 static 工厂方法实例化 Bean 后,调用该 Bean 的 setter 方法,即可实现基于 setter 的 DI。

在 Spring 实例化 Bean 的过程中,首先会调用默认的构造方法实例化 Bean 对象,然后通过 Java 的反射机制调用 setXxx() 方法进行属性的注入。因此,setter 注入要求 Bean 的对应类必须满足以下两点要求。

  • 必须提供一个默认的无参构造方法。
  • 必须为需要注入的属性提供对应的 setter 方法。

使用 setter 注入时,在 Spring 配置文件中,需要使用 <bean> 元素的子元素 <property> 为每个属性注入值。而使用构造注入时,在配置文件中,主要使用 <constructor-arg> 标签定义构造方法的参数,使用其 value 属性(或子元素)设置该参数的值。

构造函数注入

下面使用 <constructor-arg> 标签实现构造函数注入。

<constructor-arg> 标签中,包含 ref、value、type、index 等属性。value 属性用于注入基本数据类型以及字符串类型的值;ref 属性用于注入已经定义好的 Bean;type 属性用来指定对应的构造函数,当构造函数有多个参数时,可以使用 index 属性指定参数的位置,index 属性值从 0 开始。

package com.springlearn;

public class Man {
    private String name;
    private int age;
    public Man(){
        System.out.printf("在Man的构造函数内");
    }
    public Man(String name,int age){
        this.name = name;
        this.age = age;
        System.out.printf("在Man的有参数构造函数内");

    }

    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 void show() {
        System.out.println("名称:" + name + "\n年龄:" + age);
    }
}

package com.springlearn;

public class Person {
    private Man man;

    public Person(Man man) {
        this.man = man;
    }

    public void man(){
        man.show();
    }
}

<?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="man" class="com.springlearn.Man">
        <constructor-arg value="bianchengbang"></constructor-arg>
        <constructor-arg value="12" type="int"></constructor-arg>
    </bean>
    <bean id="person" class="com.springlearn.Person">
        <constructor-arg ref="man" type="com.springlearn.Man"></constructor-arg>
    </bean>
</beans>
package com.springlearn;


import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Person person = (Person) context.getBean("person");
        person.man();
    }
}

在Man的有参数构造函数内
在Person的有参构造函数内
名称:bianchengbang
年龄:12

setter注入

下面使用<property> 标签实现 setter 注入。

<property> 标签中,包含 name、ref、value 等属性。name 用于指定参数名称;value 属性用于注入基本数据类型以及字符串类型的值;ref 属性用于注入已经定义好的 Bean。

例 2

在例 1 的基础上修改 Person 类的内容,代码如下。

package com.springlearn;

public class Person {
    private Man man;

    public void man(){
        man.show();
    }
    public Man getMan() {
        return man;
    }

    public void setMan(Man man) {
        System.out.println("在setMan方法内");
        this.man = man;
    }
}

<?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="person" class="com.springlearn.Person">
        <property name="man" ref="man" />
    </bean>
    <bean id="man" class="com.springlearn.Man">
        <property name="name" value="bianchengbang" />
        <property name="age" value="12" />
    </bean>
</beans>

输出如下

在构造函数内
在setMan方法内
名称:bianchengbang
年龄:12

Spring注入内部Bean

Java 中在类内部定义的类称为内部类,同理在 Bean 中定义的 Bean 称为内部 Bean。注入内部 Bean 使用 <property><constructor-arg> 中的 <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="target">
            <!-- 定义内部Bean -->
            <bean class="..." />
        </property>
    </bean>
</beans>

内部 Bean 的定义不需要指定 id 和 name 。如果指定了,容器也不会将其作为区分 Bean 的标识符,反而会无视内部 Bean 的 scope 属性。所以内部 Bean 总是匿名的,而且总是随着外部 Bean 创建。

在实际开发中很少注入内部 Bean,因为开发者无法将内部的 Bean 注入外部 Bean 以外的其它 Bean。

Person 类代码如下。

public class Person {
    private Man man;
    public Man getMan() {
        return man;
    }
    public void setMan(Man man) {
        System.out.println("在setMan方法内");
        this.man = man;
    }
    public void man() {
        man.show();
    }
}

Man

public class Man {
    private String name;
    private int age;
    public Man() {
        System.out.println("在man的构造函数内");
    }
    public Man(String name, int age) {
        System.out.println("在man的有参构造函数内");
        this.name = name;
        this.age = age;
    }
    public void show() {
        System.out.println("名称:" + name + "\n年龄:" + 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;
    }
}
<?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="person" class="net.biancheng.Person">
        <property name="man">
            <bean class="net.biancheng.Man">
                <property name="name" value="bianchengbang" />
                <property name="age" value="12" />
            </bean>
        </property>
    </bean>
</beans>
<?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="person" class="com.springlearn.Person">
        <property name="man">
            <bean class="com.springlearn.Man">
                <property name="name" value="bianchengbang" />
                <property name="age" value="12" />
            </bean>
        </property>
    </bean>
</beans>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Person person = (Person) context.getBean("person");
        person.man();
    }
}

运行结果

在man的有参构造函数内
在Person的构造函数内
名称:bianchengbang
年龄:12

Spring注入集合

如果需要传递类似于 Java Collection 类型的值,例如 List、Set、Map 和 properties,可以使用 Spring 提供的集合配置标签,如下表所示。

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

JavaCollection 类代码如下。

import java.util.*;
public class JavaCollection {
    List manList;
    Set manSet;
    Map manMap;
    Properties manProp;
    public void setManList(List manList) {
        this.manList = manList;
    }
    public List getManList() {
        System.out.println("List Elements :" + manList);
        return manList;
    }
    public void setManSet(Set manSet) {
        this.manSet = manSet;
    }
    public Set getManSet() {
        System.out.println("Set Elements :" + manSet);
        return manSet;
    }
    public void setManMap(Map manMap) {
        this.manMap = manMap;
    }
    public Map getManMap() {
        System.out.println("Map Elements :" + manMap);
        return manMap;
    }
    public void setManProp(Properties manProp) {
        this.manProp = manProp;
    }
    public Properties getManProp() {
        System.out.println("Property Elements :" + manProp);
        return manProp;
    }
}
package com.springlearn;


import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        JavaCollection jc = (JavaCollection) context.getBean("javaCollection");
        jc.getManList();
        jc.getManSet();
        jc.getManMap();
        jc.getManProp();
    }
}

<?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="javaCollection" class="com.springlearn.JavaCollection">
        <property name="manList">
            <list>
                <value>编程帮</value>
                <value>百度</value>
                <value>C语言中文网</value>
                <value>C语言中文网</value>
            </list>
        </property>
        <property name="manSet">
            <set>
                <value>编程帮</value>
                <value>百度</value>
                <value>C语言中文网</value>
                <value>C语言中文网</value>
            </set>
        </property>
        <property name="manMap">
            <map>
                <entry key="1" value="编程帮" />
                <entry key="2" value="百度" />
                <entry key="3" value="C语言中文网" />
                <entry key="4" value="C语言中文网" />
            </map>
        </property>
        <property name="manProp">
            <props>
                <prop key="one">编程帮</prop>
                <prop key="one">编程帮</prop>
                <prop key="two">百度</prop>
                <prop key="three">C语言中文网</prop>
                <prop key="four">C语言中文网</prop>
            </props>
        </property>
    </bean>
</beans>
List Elements :[编程帮, 百度, C语言中文网, C语言中文网]
Set Elements :[编程帮, 百度, C语言中文网]
Map Elements :{1=编程帮, 2=百度, 3=C语言中文网, 4=C语言中文网}
Property Elements :{four=C语言中文网, one=编程帮, two=百度, three=C语言中文网}

注入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="..." class="...">
        <property name="manList">
            <list>
                <ref bean="man1" />
                <ref bean="man2" />
                <value>编程帮</value>
            </list>
        </property>
        <property name="manSet">
            <set>
                <ref bean="man1" />
                <ref bean="man2" />
                <value>编程帮</value>
            </set>
        </property>
        <property name="manMap">
            <map>
                <entry key="one" value="编程帮" />
                <entry key="two" value-ref="man1" />
                <entry key="three" value-ref="man2" />
            </map>
        </property>
    </bean>
</beans>

注入null和空字符串的值

Spring 会把属性的空参数直接当成空字符串来处理,如果您需要传递一个空字符串值,可以这样写:

<bean id = "..." class = "exampleBean">
    <property name = "email" value = ""/>
</bean>

等效于以下代码

exampleBean.setEmail("")

如果需要传递 NULL 值, 元素用来处理 Null 值。

<bean id = "..." class = "exampleBean">    
    <property name = "email">
        <null/>
    </property>
</bean>

等效于以下代码

exampleBean.setEmail(null)

Spring Bean 自动装配

Bean的装配可以理解为依赖关系注入,Bean的装配方式也就是Bean的依赖注入方式。Spring容器支持多种装配Bean的方式,如基于XML的Bean装配、基于Annotation的Bean装配和自动装配等。

Spring 基于 XML 的装配通常采用两种实现方式。即setter 注入和构造注入。

自动装配就是指 Spring 容器在不使用 <constructor-arg><property> 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。

使用自动装配需要配置 <bean> 元素的 autowire 属性。autowire 属性有五个值,具体说明如下表所示。

​ autowire 的属性和作用

名称 说明
no 默认值,表示不使用自动装配,Bean 依赖必须通过 ref 元素定义。
byName 根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。
byType 根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。
constructor 类似于 byType,根据构造方法参数的数据类型,进行 byType 模式的自动装配。
autodetect(3.0版本不支持) 如果 Bean 中有默认的构造方法,则用 constructor 模式,否则用 byType 模式。

示例

Person 类代码如下。

public class Person {
    private Man man;
    public Person() {
        System.out.println("在Person的构造函数内");
    }
    public Person(Man man) {
        System.out.println("在Person的有参构造函数内");
        this.man = man;
    }
    public void man() {
        man.show();
    }
    public Man getMan() {
        return man;
    }
    public void setMan(Man man) {
        this.man = man;
    }
}

Man类如下

public class Man {
    private String name;
    private int age;
    public Man() {
        System.out.println("在man的构造函数内");
    }
    public Man(String name, int age) {
        System.out.println("在man的有参构造函数内");
        this.name = name;
        this.age = age;
    }
    public void show() {
        System.out.println("名称:" + name + "\n年龄:" + 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;
    }
}

MainApp

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Person person = (Person) context.getBean("person");
        person.man();
    }
}
  • 不使用自动装配(autowire="no")

    autowire="no" 表示不使用自动装配,需要手动注入,Bean 依赖通过 ref 元素定义,Beans.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-3.0.xsd">
        <bean id="man" class="com.springlearn.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="person" class="com.springlearn.Person" autowire="no">
            <constructor-arg ref="man" type="com.springlearn.Man"/>
        </bean>
    </beans>
    

    运行结果如下。

    在man的有参构造函数内
    在Person的有参构造函数内
    名称:bianchengbang
    年龄:12
    
  • 按名称自动装配(autowire="byName")

    autowire="byName" 表示按属性名称自动装配,XML 文件中 Bean 的 id 必须与类中的属性名称相同。配置文件内容修改如下。

    <?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="man" class="com.springlearn.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="person" class="com.springlearn.Person" autowire="byName" />
    </beans>
    

    运行结果如下

    在man的有参构造函数内
    在Person的构造函数内
    名称:bianchengbang
    年龄:12
    

    如果更改 Bean 的名称,很可能不会注入依赖项。

    将 Bean 的名称更改为 man1,配置文件如下:

    <?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="man1" class="com.springlearn.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="person" class="com.springlearn.Person" autowire="byName" />
    </beans>
    

    注入失败,异常信息为:

    Exception in thread "main" java.lang.NullPointerException
    	at com.springlearn.Person.man(Person.java:13)
    	at com.springlearn.MainApp.main(MainApp.java:11)
    
  • 按类型自动装配(autowire="byType")

    XML 文件中 Bean 的 id 与类中的属性名称可以不同,但必须只有一个类型的 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="man1" class="com.springlearn.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="person" class="com.springlearn.Person" autowire="byType" />
    </beans>
    

    结果

    在man的有参构造函数内
    在Person的构造函数内
    名称:bianchengbang
    年龄:12
    

    如果您有相同类型的多个 Bean,则注入失败,并且引发异常。

    添加 id 为 man2 的 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="man1" class="com.springlearn.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="man2" class="com.springlearn.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="person" class="com.springlearn.Person" autowire="byType" />
    </beans>
    

    异常信息为:

    在man的有参构造函数内
    在man的有参构造函数内
    在Person的构造函数内
    一月 26, 2021 1:34:14 下午 org.springframework.context.support.AbstractApplicationContext refresh
    警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'person' defined in class path resource [Beans.xml]: Unsatisfied dependency expressed through bean property 'man'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'net.biancheng.Man' available: expected single matching bean but found 2: man1,man2
    ...
    
  • 构造函数自动装配(autowire="constructor")

    <?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="man1" class="com.springlearn.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="person" class="com.springlearn.Person" autowire="constructor" />
    </beans>
    
    在man的有参构造函数内
    在Person的有参构造函数内
    名称:bianchengbang
    年龄:12
    

自动装配的优缺点

优点

  • 自动装配只需要较少的代码就可以实现依赖注入。

缺点

  • 不能自动装配简单数据类型,比如 int、boolean、String 等。
  • 相比较显示装配,自动装配不受程序员控制。
posted @ 2021-12-17 16:53  青杉  阅读(1139)  评论(0编辑  收藏  举报