Spring 依赖注入(IoC)概念 IoC 容器和依赖注入模式

作者: Spring 开发者

Spring 依赖注入(IoC)概念 IoC 容器和依赖注入模式

  • [1. IoC 概念简介]
  • [1.1. IoC 是什么]
  • [1.2. IoC 能做什么]
  • [1.3. IoC 和 DI]
  • [1.4. IoC 容器]
  • [1.5. Bean]
  • [2. Spring IoC]
  • [2.1. 核心接口]
  • [2.2. IoC 容器工作步骤]
  • [2.3. 配置元数据]
  • [2.4. Spring Bean 概述]
  • [2.5. 依赖注入模式]
  • [2.6. 依赖注入类型]
  • [2.7. 被注入的数据类型]
  • [3. IoC 依赖查找]
  • [4. IoC 依赖注入]
  • [5. IoC 容器配置]
  • [5.1. Xml 配置]
  • [5.2. 注解配置]
  • [5.3. Java 配置]
  • [6. 最佳实践]
  • [6.1. singleton 的 Bean 如何注入 prototype 的 Bean]
  • [7. 参考资料]

    1. IoC 概念简介

    1.1. IoC 是什么

    IoC,是 Inversion of Control 的缩写,即控制反转。

    • 上层模块不应该依赖于下层模块,它们共同依赖于一个抽象
    • 抽象不能依赖于具体实现,具体实现依赖于抽象

    注:又称为依赖倒置原则。这是设计模式六大原则之一。 IoC 不是什么技术,而是一种设计思想。在 Java 开发中,IoC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解 Ioc 呢?理解 Ioc 的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:传统 JavaSE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器来控制对象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。 用图例说明一下,传统程序设计如图 2-1,都是主动去创建相关对象然后再组合起来: img 图 2-1 传统应用程序示意图 当有了 IoC/DI 的容器后,在客户端类中不再主动去创建这些对象了,如图 2-2 所示: img 图 2-2 有 IoC/DI 容器后程序结构示意图

    1.2. IoC 能做什么

    IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了 IoC 容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。 其实 IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。 IoC 很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

    1.3. IoC 和 DI

    其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:“依赖注入”,相对 IoC 而言,“依赖注入”明确描述了“被注入对象依赖 IoC 容器配置依赖对象”。

    注:如果想要更加深入的了解 IoC 和 DI,请参考大师级人物 Martin Fowler 的一篇经典文章 Inversion of Control Containers and the Dependency Injection pattern

    1.4. IoC 容器

    IoC 容器就是具有依赖注入功能的容器。IoC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中 new 相关的对象,应用程序由 IoC 容器进行组装。在 Spring 中 BeanFactory 是 IoC 容器的实际代表者。 Spring IoC 容器如何知道哪些是它管理的对象呢?这就需要配置文件,Spring IoC 容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。一般使用基于 xml 配置文件进行配置元数据,而且 Spring 与配置文件完全解耦的,可以使用其他任何可能的方式进行配置元数据,比如注解、基于 java 文件的、基于属性文件的配置都可以。 那 Spring IoC 容器管理的对象叫什么呢?

    1.5. Bean

    JavaBean 是一种 JAVA 语言写成的可重用组件。为写成 JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。JavaBean 对外部通过提供 getter / setter 方法来访问其成员。 由 IoC 容器管理的那些组成你应用程序的对象我们就叫它 Bean。Bean 就是由 Spring 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。那 IoC 怎样确定如何实例化 Bean、管理 Bean 之间的依赖关系以及管理 Bean 呢?这就需要配置元数据,在 Spring 中由 BeanDefinition 代表,后边会详细介绍,配置元数据指定如何实例化 Bean、如何组装 Bean 等。

    2. Spring IoC

    2.1. 核心接口

    org.springframework.beansorg.springframework.context 是 IoC 容器的基础。 在 Spring 中,有两种 IoC 容器:BeanFactoryApplicationContext

  • BeanFactory:BeanFactory 提供了 Spring 容器的配置框架和基本功能。
  • ApplicationContext:BeanFactory 的子接口。它还扩展了其他一些接口,以支持更丰富的功能,如:国际化、访问资源、事件机制、更方便的支持 AOP、在 web 应用中指定应用层上下文等。 实际开发中,更推荐使用 ApplicationContext 作为 IoC 容器,因为它的功能远多于 FactoryBean。 常见 ApplicationContext 实现:
  • ClassPathXmlApplicationContextApplicationContext 的实现,从 classpath 获取配置文件;
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath.xml");
    
  • FileSystemXmlApplicationContextApplicationContext 的实现,从文件系统获取配置文件。
    BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.xml");
    

    2.2. IoC 容器工作步骤

    使用 IoC 容器可分为三步骤:
  • 配置元数据:需要配置一些元数据来告诉 Spring,你希望容器如何工作,具体来说,就是如何去初始化、配置、管理 JavaBean 对象。
  • 实例化容器:由 IoC 容器解析配置的元数据。IoC 容器的 Bean Reader 读取并解析配置文件,根据定义生成 BeanDefinition 配置元数据对象,IoC 容器根据 BeanDefinition 进行实例化、配置及组装 Bean。
  • 使用容器:由客户端实例化容器,获取需要的 Bean。 img

    2.3. 配置元数据

    元数据(Metadata)又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息。 配置元数据的方式:
  • 基于 xml 配置:Spring 的传统配置方式。在 <beans> 标签中配置元数据内容。 缺点是当 JavaBean 过多时,产生的配置文件足以让你眼花缭乱。
  • 基于注解配置:Spring2.5 引入。可以大大简化你的配置。
  • 基于 Java 配置:可以使用 Java 类来定义 JavaBean 。 为了使用这个新特性,需要用到 @Configuration@Bean@Import@DependsOn 注解。

    2.4. Spring Bean 概述

    一个 Spring 容器管理一个或多个 bean。这些 bean 根据你配置的元数据(比如 xml 形式)来创建。 Spring IoC 容器本身,并不能识别你配置的元数据。为此,要将这些配置信息转为 Spring 能识别的格式——BeanDefinition 对象。
  • 命名 Bean
  • 指定 id 和 name 属性不是必须的。Spring 中,并非一定要指定 id 和 name 属性。实际上,Spring 会自动为其分配一个特殊名。如果你需要引用声明的 bean,这时你才需要一个标识。官方推荐驼峰命名法来命名。
  • 支持别名
  • 可能存在这样的场景,不同系统中对于同一 bean 的命名方式不一样。 为了适配,Spring 支持 <alias> 为 bean 添加别名的功能。
  • <alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
    <alias name="subsystemA-dataSource" alias="myApp-dataSource" />
    
  • 实例化 Bean
  • 构造器方式
  • <bean id="exampleBean" class="examples.ExampleBean"/>
    

    2.5. 依赖注入模式

    依赖注入模式可以分为手动模式和自动模式。

    手动注入

    配置或者编程的方式,提前安排注入规则
  • XML 资源配置元信息
  • Java 注解配置元信息
  • API 配置元信息

    自动注入

    实现方提供依赖自动关联的方式,按照內建的注入规则(官方不推荐)
  • Autowiring(自动绑定)
    | 模式        | 说明                                                                   |
    | ----------- | ---------------------------------------------------------------------- |
    | no          | 默认值,未激活 Autowiring,需要手动指定依赖注入对象。                  |
    | byName      | 根据被注入属性的名称作为 Bean 名称进行依赖查找,并将对象设置到该属性。 |
    | byType      | 根据被注入属性的类型作为依赖类型进行查找,并将对象设置到该属性。       |
    | constructor | 特殊 byType 类型,用于构造器参数。                                     |
    

    限制和不足参考:Limitations and Disadvantages of Autowiring 小节

    2.6. 依赖注入类型

    | 依赖注入类型 | 配置元数据举例                                     |
    | ------------ | -------------------------------------------------- |
    | Setter 方法  | `<proeprty name="user" ref="userBean"/>`           |
    | 构造器       | `<constructor-arg name="user" ref="userBean" />`   |
    | 字段         | `@Autowired User user;`                            |
    | 方法         | `@Autowired public void user(User user) `   |
    | 接口回调     | `class MyBean implements BeanFactoryAware ` |
    

    Setter 方法注入

    手动模式

  • XML 资源配置元信息
  • Java 注解配置元信息
  • API 配置元信息 自动模式
  • byName
  • byType

    构造器注入

    手动模式
  • XML 资源配置元信息
  • Java 注解配置元信息
  • API 配置元信息 自动模式
  • constructor

    字段注入

    手动模式(Java 注解配置元信息)
  • @Autowired
  • @Resource
  • @Inject(可选)

    方法注入

    手动模式(Java 注解配置元信息)
  • @Autowired
  • @Resource
  • @Inject(可选)
  • @Bean

    接口回调注入

    Aware 系列接口回调
    | 內建接口                         | 说明                                                       |
    | -------------------------------- | ---------------------------------------------------------- |
    | `BeanFactoryAware`               | 获取 IoC 容器- `BeanFactory`                               |
    | `ApplicationContextAware`        | 获取 Spring 应用上下文- `ApplicationContext` 对象          |
    | `EnvironmentAware`               | 获取 `Environment` 对象                                    |
    | `ResourceLoaderAware`            | 获取资源加载器对象- `ResourceLoader`                       |
    | `BeanClassLoaderAware`           | 获取加载当前 Bean Class 的 `ClassLoader`                   |
    | `BeanNameAware`                  | 获取当前 Bean 的名称                                       |
    | `MessageSourceAware`             | 获取 `MessageSource` 对象,用于 Spring 国际化              |
    | `ApplicationEventPublisherAware` | 获取 `ApplicationEventPublishAware` 对象,用于 Spring 事件 |
    | `EmbeddedValueResolverAware`     | 获取 `StringValueResolver` 对象,用于占位符处理            |
    

    依赖注入类型选择

  • 低依赖:构造器注入
  • 多依赖:Setter 方法注入
  • 便利性:字段注入
  • 声明类:方法注入

    2.7. 被注入的数据类型

    基础类型
  • 原生类型(Primitive):boolean、byte、char、short、int、float、long、double
  • 标量类型(Scalar):Number、Character、Boolean、Enum、Locale、Charset、Currency、Properties、UUID
  • 常规类型(General):Object、String、TimeZone、Calendar、Optional 等
  • Spring 类型:Resource、InputSource、Formatter 等 集合类型
  • 数组类型(Array):原生类型、标量类型、常规类型、Spring 类型
  • 集合类型(Collection)
  • Collection:List、Set(SortedSet、NavigableSet、EnumSet)
  • Map:Properties

    3. IoC 依赖查找

    依赖查找是主动或手动的依赖查找方式,通常需要依赖容器或标准 API 实现
  • 根据 Bean 名称查找
  • 实时查找
  • 延迟查找
  • 根据 Bean 类型查找
  • 根据 Bean 名称 + 类型查找
  • 根据 Java 注解查找 【示例】Spring IoC 依赖查找 xml 元信息配置
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
        <property name="targetBeanName" value="user" />
    </bean>
    <bean id="user" class="io.github.dunwu.spring.core.domain.User"
        p:name="张三" p:age="18">
    </bean>
    <bean id="superUser" class="io.github.dunwu.spring.core.domain.SysUser" parent="user">
        <property name="name" value="李四" />
        <property name="age" value="19" />
        <property name="address" value="南京" />
    </bean>
    </beans>
    
    依赖查找示例代码:
    public class BeanFactoryDemo {
    public static void main(String[] args) {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring/spring-dependency-lookup.xml");
        lookupByType(beanFactory);
        lookupByNameInRealTime(beanFactory);
        lookupByNameInLazy(beanFactory);
        lookupByAnnotation(beanFactory);
    }
    public static void lookupByType(BeanFactory beanFactory) {
        SysUser user = beanFactory.getBean(SysUser.class);
        System.out.println("SysUser = " + user);
    }
    public static void lookupByNameInRealTime(BeanFactory beanFactory) {
        User user = (User) beanFactory.getBean("user");
        System.out.println("user = " + user);
    }
    public static void lookupByNameInLazy(BeanFactory beanFactory) {
        ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
        User user = objectFactory.getObject();
        System.out.println("user = " + user);
    }
    public static void lookupByAnnotation(BeanFactory beanFactory) {
        if (beanFactory instanceof ListableBeanFactory) {
            ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
            Map<String, Object> users = listableBeanFactory.getBeansWithAnnotation(Super.class);
            System.out.println("users = " + users);
        }
    }
    }
    

    4. IoC 依赖注入

    DI,是 Dependency Injection 的缩写,即依赖注入。依赖注入是 IoC 的最常见形式。依赖注入是手动或自动绑定的方式,无需依赖特定的容器或 API。 容器全权负责组件的装配,它会把符合依赖关系的对象通过 JavaBean 属性或者构造函数传递给需要的对象。 DI 是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。 理解 DI 的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
  • 谁依赖于谁:当然是应用程序依赖于 IoC 容器;
  • 为什么需要依赖:应用程序需要 IoC 容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是 IoC 容器注入应用程序某个对象,应用程序依赖的对象;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。 IoC 依赖注入 API
  • 根据 Bean 名称注入
  • 根据 Bean 类型注入
  • 注入容器内建 Bean 对象
  • 注入非 Bean 对象
  • 注入类型
  • 实时注入
  • 延迟注入

    5. IoC 容器配置

    IoC 容器的配置有三种方式:
  • 基于 xml 配置
  • 基于注解配置
  • 基于 Java 配置 作为 Spring 传统的配置方式,xml 配置方式一般为大家所熟知。 如果厌倦了 xml 配置,Spring 也提供了注解配置方式或 Java 配置方式来简化配置。 本文,将对 Java 配置 IoC 容器做详细的介绍。

    5.1. 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">
    <import resource="resource1.xml" />
    <bean id="bean1" class=""></bean>
    <bean id="bean2" class=""></bean>
    <bean name="bean2" class=""></bean>
    <alias alias="bean3" name="bean2"/>
    <import resource="resource2.xml" />
    </beans>
    
    标签说明:
  • <beans> 是 Spring 配置文件的根节点。
  • <bean> 用来定义一个 JavaBean。id 属性是它的标识,在文件中必须唯一;class 属性是它关联的类。
  • <alias> 用来定义 Bean 的别名。
  • <import> 用来导入其他配置文件的 Bean 定义。这是为了加载多个配置文件,当然也可以把这些配置文件构造为一个数组(new String[] {“config1.xml”, config2.xml})传给 ApplicationContext 实现类进行加载多个配置文件,那一个更适合由用户决定;这两种方式都是通过调用 Bean Definition Reader 读取 Bean 定义,内部实现没有任何区别。<import> 标签可以放在 <beans> 下的任何位置,没有顺序关系。

    实例化容器

    实例化容器的过程: 定位资源(XML 配置文件) 读取配置信息(Resource) 转化为 Spring 可识别的数据形式(BeanDefinition)
    ApplicationContext context =
      new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
    
    组合 xml 配置文件 配置的 Bean 功能各不相同,都放在一个 xml 文件中,不便管理。 Java 设计模式讲究职责单一原则。配置其实也是如此,功能不同的 JavaBean 应该被组织在不同的 xml 文件中。然后使用 import 标签把它们统一导入。
    <import resource="classpath:spring/applicationContext.xml"/>
    <import resource="/WEB-INF/spring/service.xml"/>
    

    使用容器

    使用容器的方式就是通过getBean获取 IoC 容器中的 JavaBean。 Spring 也有其他方法去获得 JavaBean,但是 Spring 并不推荐其他方式。
    // create and configure beans
    ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] );
    // retrieve configured instance
    PetStoreService service = context.getBean("petStore", PetStoreService.class);
    // use configured instance
    List<String> userList = service.getUsernameList();
    

    5.2. 注解配置

    Spring2.5 引入了注解。 于是,一个问题产生了:使用注解方式注入 JavaBean 是不是一定完爆 xml 方式? 未必。正所谓,仁者见仁智者见智。任何事物都有其优缺点,看你如何取舍。来看看注解的优缺点: 优点:大大减少了配置,并且可以使配置更加精细——类,方法,字段都可以用注解去标记。 缺点:使用注解,不可避免产生了侵入式编程,也产生了一些问题。
  • 你需要将注解加入你的源码并编译它;
  • 注解往往比较分散,不易管控。

    注:spring 中,先进行注解注入,然后才是 xml 注入,因此如果注入的目标相同,后者会覆盖前者。

    启动注解

    Spring 默认是不启用注解的。如果想使用注解,需要先在 xml 中启动注解。 启动方式:在 xml 中加入一个标签,很简单吧。

    <context:annotation-config/>
    

    注:<context:annotation-config/> 只会检索定义它的上下文。什么意思呢?就是说,如果你 为 DispatcherServlet 指定了一个WebApplicationContext,那么它只在 controller 中查找@Autowired注解,而不会检查其它的路径。

    @Required

    @Required 注解只能用于修饰 bean 属性的 setter 方法。受影响的 bean 属性必须在配置时被填充在 xml 配置文件中,否则容器将抛出BeanInitializationException

    public class AnnotationRequired {
    private String name;
    private String sex;
    public String getName() {
        return name;
    }
    /**
     * @Required 注解用于bean属性的setter方法并且它指示,受影响的bean属性必须在配置时被填充在xml配置文件中,
     *           否则容器将抛出BeanInitializationException。
     */
    @Required
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    }
    

    @Autowired

    @Autowired注解可用于修饰属性、setter 方法、构造方法。 @Autowired 注入过程

  • 元信息解析
  • 依赖查找
  • 依赖注入(字段、方法)

    注:@Autowired注解也可用于修饰构造方法,但如果类中只有默认构造方法,则没有必要。如果有多个构造器,至少应该修饰一个,来告诉容器哪一个必须使用。 可以使用 JSR330 的注解@Inject来替代@Autowired范例

    public class AnnotationAutowired {
    private static final Logger log = LoggerFactory.getLogger(AnnotationRequired.class);
    @Autowired
    private Apple fieldA;
    private Banana fieldB;
    private Orange fieldC;
    public Apple getFieldA() {
        return fieldA;
    }
    public void setFieldA(Apple fieldA) {
        this.fieldA = fieldA;
    }
    public Banana getFieldB() {
        return fieldB;
    }
    @Autowired
    public void setFieldB(Banana fieldB) {
        this.fieldB = fieldB;
    }
    public Orange getFieldC() {
        return fieldC;
    }
    public void setFieldC(Orange fieldC) {
        this.fieldC = fieldC;
    }
    public AnnotationAutowired() 
    @Autowired
    public AnnotationAutowired(Orange fieldC) {
        this.fieldC = fieldC;
    }
    public static void main(String[] args) throws Exception {
        AbstractApplicationContext ctx =
                        new ClassPathXmlApplicationContext("spring/spring-annotation.xml");
        AnnotationAutowired annotationAutowired =
                        (AnnotationAutowired) ctx.getBean("annotationAutowired");
        log.debug("fieldA: {}, fieldB:{}, fieldC:{}", annotationAutowired.getFieldA().getName(),
                        annotationAutowired.getFieldB().getName(),
                        annotationAutowired.getFieldC().getName());
        ctx.close();
    }
    }
    

    xml 中的配置

    <!-- 测试@Autowired -->
    <bean id="apple" class="org.zp.notes.spring.beans.annotation.sample.Apple"/>
    <bean id="potato" class="org.zp.notes.spring.beans.annotation.sample.Banana"/>
    <bean id="tomato" class="org.zp.notes.spring.beans.annotation.sample.Orange"/>
    <bean id="annotationAutowired" class="org.zp.notes.spring.beans.annotation.sample.AnnotationAutowired"/>
    

    @Qualifier

    @Autowired注解中,提到了如果发现有多个候选的 bean 都符合修饰类型,Spring 就会抓瞎了。 那么,如何解决这个问题。 可以通过@Qualifier指定 bean 名称来锁定真正需要的那个 bean。 范例

    public class AnnotationQualifier {
    private static final Logger log = LoggerFactory.getLogger(AnnotationQualifier.class);
    @Autowired
    @Qualifier("dog") /** 去除这行,会报异常 */
    Animal dog;
    Animal cat;
    public Animal getDog() {
        return dog;
    }
    public void setDog(Animal dog) {
        this.dog = dog;
    }
    public Animal getCat() {
        return cat;
    }
    @Autowired
    public void setCat(@Qualifier("cat") Animal cat) {
        this.cat = cat;
    }
    public static void main(String[] args) throws Exception {
        AbstractApplicationContext ctx =
                new ClassPathXmlApplicationContext("spring/spring-annotation.xml");
        AnnotationQualifier annotationQualifier =
                (AnnotationQualifier) ctx.getBean("annotationQualifier");
        log.debug("Dog name: ", annotationQualifier.getDog().getName());
        log.debug("Cat name: ", annotationQualifier.getCat().getName());
        ctx.close();
    }
    }
    abstract class Animal {
    public String getName() {
        return null;
    }
    }
    class Dog extends Animal {
    public String getName() {
        return "狗";
    }
    }
    class Cat extends Animal {
    public String getName() {
        return "猫";
    }
    }
    

    xml 中的配置

    <!-- 测试@Qualifier -->
    <bean id="dog" class="org.zp.notes.spring.beans.annotation.sample.Dog"/>
    <bean id="cat" class="org.zp.notes.spring.beans.annotation.sample.Cat"/>
    <bean id="annotationQualifier" class="org.zp.notes.spring.beans.annotation.sample.AnnotationQualifier"/>
    

    @Resource

    Spring 支持 JSP250 规定的注解@Resource。这个注解根据指定的名称来注入 bean。 如果没有为@Resource指定名称,它会像@Autowired一样按照类型去寻找匹配。 在 Spring 中,由CommonAnnotationBeanPostProcessor来处理@Resource注解。 范例

    public class AnnotationResource {
    private static final Logger log = LoggerFactory.getLogger(AnnotationResource.class);
    @Resource(name = "flower")
    Plant flower;
    @Resource(name = "tree")
    Plant tree;
    public Plant getFlower() {
        return flower;
    }
    public void setFlower(Plant flower) {
        this.flower = flower;
    }
    public Plant getTree() {
        return tree;
    }
    public void setTree(Plant tree) {
        this.tree = tree;
    }
    public static void main(String[] args) throws Exception {
        AbstractApplicationContext ctx =
                        new ClassPathXmlApplicationContext("spring/spring-annotation.xml");
        AnnotationResource annotationResource =
                        (AnnotationResource) ctx.getBean("annotationResource");
        log.debug("type: {}, name: {}", annotationResource.getFlower().getClass(), annotationResource.getFlower().getName());
        log.debug("type: {}, name: {}", annotationResource.getTree().getClass(), annotationResource.getTree().getName());
        ctx.close();
    }
    }
    

    xml 的配置

    <!-- 测试@Resource -->
    <bean id="flower" class="org.zp.notes.spring.beans.annotation.sample.Flower"/>
    <bean id="tree" class="org.zp.notes.spring.beans.annotation.sample.Tree"/>
    <bean id="annotationResource" class="org.zp.notes.spring.beans.annotation.sample.AnnotationResource"/>
    

    @PostConstruct@PreDestroy

    @PostConstruct@PreDestroy 是 JSR 250 规定的用于生命周期的注解。 从其名号就可以看出,一个是在构造之后调用的方法,一个是销毁之前调用的方法。

    public class AnnotationPostConstructAndPreDestroy {
    private static final Logger log = LoggerFactory.getLogger(AnnotationPostConstructAndPreDestroy.class);
    @PostConstruct
    public void init() {
        log.debug("call @PostConstruct method");
    }
    @PreDestroy
    public void destroy() {
        log.debug("call @PreDestroy method");
    }
    }
    

    @Inject

    从 Spring3.0 开始,Spring 支持 JSR 330 标准注解(依赖注入)。 注:如果要使用 JSR 330 注解,需要使用外部 jar 包。 若你使用 maven 管理 jar 包,只需要添加依赖到 pom.xml 即可:

    <dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
    </dependency>
    

    @Inject@Autowired 一样,可以修饰属性、setter 方法、构造方法。 范例

    public class AnnotationInject {
    private static final Logger log = LoggerFactory.getLogger(AnnotationInject.class);
    @Inject
    Apple fieldA;
    Banana fieldB;
    Orange fieldC;
    public Apple getFieldA() {
        return fieldA;
    }
    public void setFieldA(Apple fieldA) {
        this.fieldA = fieldA;
    }
    public Banana getFieldB() {
        return fieldB;
    }
    @Inject
    public void setFieldB(Banana fieldB) {
        this.fieldB = fieldB;
    }
    public Orange getFieldC() {
        return fieldC;
    }
    public AnnotationInject() 
    @Inject
    public AnnotationInject(Orange fieldC) {
        this.fieldC = fieldC;
    }
    public static void main(String[] args) throws Exception {
        AbstractApplicationContext ctx =
                        new ClassPathXmlApplicationContext("spring/spring-annotation.xml");
        AnnotationInject annotationInject = (AnnotationInject) ctx.getBean("annotationInject");
        log.debug("type: , name: ", annotationInject.getFieldA().getClass(),
                        annotationInject.getFieldA().getName());
        log.debug("type: , name: ", annotationInject.getFieldB().getClass(),
                        annotationInject.getFieldB().getName());
        log.debug("type: , name: ", annotationInject.getFieldC().getClass(),
                        annotationInject.getFieldC().getName());
        ctx.close();
    }
    }
    

    5.3. Java 配置

    基于 Java 配置 Spring IoC 容器,实际上是Spring 允许用户定义一个类,在这个类中去管理 IoC 容器的配置。 为了让 Spring 识别这个定义类为一个 Spring 配置类,需要用到两个注解:@Configuration@Bean。 如果你熟悉 Spring 的 xml 配置方式,你可以将@Configuration等价于<beans>标签;将@Bean等价于<bean>标签。

    @Bean

    @Bean 的修饰目标只能是方法或注解。 @Bean 只能定义在 @Configuration@Component 注解修饰的类中。

    声明一个 bean

    此外,@Configuration 类允许在同一个类中通过@Bean 定义内部 bean 依赖。 声明一个 bean,只需要在 bean 属性的 set 方法上标注@Bean 即可。

    @Configuration
    public class AnnotationConfiguration {
    private static final Logger log = LoggerFactory.getLogger(JavaComponentScan.class);
    @Bean
    public Job getPolice() {
        return new Police();
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AnnotationConfiguration.class);
        ctx.scan("org.zp.notes.spring.beans");
        ctx.refresh();
        Job job = (Job) ctx.getBean("police");
        log.debug("job: , work: ", job.getClass(), job.work());
    }
    }
    public interface Job {
    String work();
    }
    @Component("police")
    public class Police implements Job {
    @Override
    public String work() {
        return "抓罪犯";
    }
    }
    

    这等价于配置

    <beans>
    <bean id="police" class="org.zp.notes.spring.ioc.sample.job.Police"/>
    </beans>
    

    @Bean 注解用来表明一个方法实例化、配置合初始化一个被 Spring IoC 容器管理的新对象。 如果你熟悉 Spring 的 xml 配置,你可以将@Bean 视为等价于<beans>标签。 @Bean 注解可以用于任何的 Spring @Component bean,然而,通常被用于@Configuration bean。

    @Configuration

    @Configuration 是一个类级别的注解,用来标记被修饰类的对象是一个BeanDefinition@Configuration 声明 bean 是通过被 @Bean 修饰的公共方法。此外,@Configuration 允许在同一个类中通过 @Bean 定义内部 bean 依赖。

    @Configuration
    public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
    }
    

    这等价于配置

    <beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
    </beans>
    

    AnnotationConfigApplicationContext 实例化 IoC 容器。

    6. 最佳实践

    6.1. singleton 的 Bean 如何注入 prototype 的 Bean

    Spring 创建的 Bean 默认是单例的,但当 Bean 遇到继承的时候,可能会忽略这一点。 假设有一个 SayService 抽象类,其中维护了一个类型是 ArrayList 的字段 data,用于保存方法处理的中间数据。每次调用 say 方法都会往 data 加入新数据,可以认为 SayService 是有状态,如果 SayService 是单例的话必然会 OOM。

    /**
    
  • SayService 是有状态,如果 SayService 是单例的话必然会 OOM */ @Slf4j public abstract class SayService { List data = new ArrayList<>(); public void say() { data.add(IntStream.rangeClosed(1, 1000000)
     .mapToObj(__ -> "a")
     .collect(Collectors.joining("")) + UUID.randomUUID().toString());
    
    log.info(“I'm size:“, this, data.size()); } }
    但实际开发的时候,开发同学没有过多思考就把 SayHello 和 SayBye 类加上了 @Service 注解,让它们成为了 Bean,也没有考虑到父类是有状态的。
    
    @Service @Slf4j public class SayBye extends SayService { @Override public void say() { super.say(); log.info(“bye”); } } @Service @Slf4j public class SayHello extends SayService { @Override public void say() {
    super.say();
    log.info("hello");
    
    } }
    在为类标记上 @Service 注解把类型交由容器管理前,首先评估一下类是否有状态,然后为 Bean 设置合适的 Scope。
    调用代码:
    
    @Slf4j @RestController @RequestMapping(“beansingletonandorder”) public class BeanSingletonAndOrderController { @Autowired List sayServiceList; @Autowired private ApplicationContext applicationContext; @GetMapping(“test”) public void test() { log.info(“====================“); sayServiceList.forEach(SayService::say); } }
    可能有人认为,为 SayHello 和 SayBye 两个类都标记了 @Scope 注解,设置了 PROTOTYPE 的生命周期就可以解决上面的问题。
    
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    但实际上还是有问题。因为@RestController 注解 =@Controller 注解 +@ResponseBody 注解,又因为 @Controller 标记了 @Component 元注解,所以 @RestController 注解其实也是一个 Spring Bean。
    Bean 默认是单例的,所以单例的 Controller 注入的 Service 也是一次性创建的,即使 Service 本身标识了 prototype 的范围也没用。
    修复方式是,让 Service 以代理方式注入。这样虽然 Controller 本身是单例的,但每次都能从代理获取 Service。这样一来,prototype 范围的配置才能真正生效。
    
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProx)
    ## 7. 参考资料
    
  • Spring 官方文档之 Core Technologies

更多推荐

更多
  • Spring Boot实战-00开篇词从零开始:为什么要学习SpringBoot? 你为什么需要学习这门课程?,这门课程是如何设计的?,讲师寄语, 你好,我是鉴湘,有 10 年以上大型 Java EE 和分布架进行系统开发和维护有着丰富的实践经验。 在我的从业生涯中,曾经带过不少项目,以我所带领的电商项目开发团队为例
  • Spring Boot实战-04定制配置:如何创建和管理自定义的配置信息? 如何在应用程序中嵌入系统配置信息?,如何创建和使用自定义配置信息?,如何组织和整合配置信息?,如何覆写内置的配置类?,小结与预告,03 讲中,我们介绍了 Spring Boot ...
  • Spring Boot实战-09数据抽象:SpringData如何对数据访问过程进行统一抽象? Repository 接口及实现,多样化查询支持,Spring Data 中的组件,小结与预告, 事实上,JdbcTemplate 是相对偏底层的一个工具类,作为系统开发最重要的基础功能之一,族中另一个重要成员 Spring Data
  • Spring Boot实战-13服务调用:如何正确理解RestTemplate远程调用实现原理? 初始化 RestTemplate 实例,RestTemplate 核心执行流程,从源码解析到日常开发,小结与预告, 在 12 讲中,我们详细描述了如何使用 RestTemplate 访问 HTTP 端点的使用方法,它涉及 Res中的这些
  • Spring Boot实战-12服务调用:如何使用RestTemplate消费RESTful服务? 使用 RestTemplate 访问 HTTP 端点,实现 SpringCSS 案例中的服务交互,小结与预告,11 讲我们介绍了如何使用 Spring Boot 构建 RESTful 风格 Web 服务的实现方法,而 S 服务的构建后,
  • Spring Boot实战-结束语以终为始:SpringBoot总结和展望 Spring Boot 的创新性,Spring Boot 课程总结,Spring Boot 的发展和演进, 终于到了课程的最后一讲啦,这一讲我们将对整个 Spring Boot 课程进行总结。 Spring Boring Boot 提供
  • Spring Boot实战-08数据访问:如何剖析JdbcTemplate数据访问实现原理? 从模板方法模式和回调机制说起,JDBC API 到 JdbcTemplate 的演变,JdbcTemplate 源码解析,从源码解析到日常开发,小结与预告,07 讲中,我们介绍了使用 JdbcTemplate ...
  • Spring Boot实战-03多维配置:如何使用SpringBoot中的配置体系? 创建第一个 Spring Boot Web 应用程序,Spring Boot 中的配置体系,小结与预告, 配置体系是基于 Spring Boot 框架开发应用程序的基础,而自动配置也是该框架的核心功能之一。今eb 应用程序开始吧。 创
  • Spring Boot实战-21指标定制:如何实现自定义度量指标和Actuator端点? Actuator 中的度量指标,自定义 Actuator 端点,小结与预告,20 讲中我们引入了 Spring Boot Actuator 组件来满足 这一讲我们继续讨论如何扩展 Actuator 端点,但更多关注与度量指标相关的内容
  • Spring Boot实战-02案例驱动:如何剖析一个SpringWeb应用程序? Spring MVC VS Spring Boot,剖析一个 Spring Web 应用程序,案例驱动:SpringCSS,小结与预告, 在 01 讲中,我们提到 Spring 家族具备很多款开源框架,开发人员可以基于这些开发框架实现各种 ...
  • 近期文章

    更多
    文章目录

      推荐作者

      更多