第3章 在Spring中引入IoC和DI

依赖注入是IOC的一种特殊形式,尽管这两个术语经常可以互换使用。

3.1 控制反转和依赖注入

IOC的核心是DI,旨在提供一种更简单的机制来设置组件依赖项,并在整个生命周期中管理这些依赖项。需要某些依赖项的组件通常被称为依赖对象,或者在IOC的情况下被称为目标对象。

IOC可以分解为两种子类型:依赖注入(DI)和依赖查找。

3.2 控制反转的类型

使用依赖查找时,组件必须获取对依赖项的引用,而是用依赖注入时,依赖项将通过IOC容器注入组件。

依赖查找有两种类型:依赖拉取(dependency pull,DL)和上下文依赖查找(contextualized dependency lookup,CDL)。

依赖注入有两种风格:构造函数和setter依赖注入。

3.2.6 setter注入与构造函数注入

使用接口来定义依赖项的能力是setter注入的一个经常被提及的好处,但实际上,应该尽量保持setter仅用于诸如接口。除非完全确定特定业务接口的所有实现都需要一个特定的依赖项,否则让每个实现类定义自己的依赖项并为业务方法保留业务接口。

虽然不应该在业务接口中放置用于依赖项的setter方法,但在业务接口中放置用来获取配置参数的setter和getter方法却是一个好主意。可以将配置参数视为依赖项的特例。

应该根据使用情况选择诸如类型。基于setter的注入预序在不创建新对象的情况下交换依赖项,并且还可以让类选择适当的默认值,而无需显式注入对象。但想要确保将依赖项传递给组件和设计不可变对象时,构造函数注入是一个不错的选择。

3.4 Spring中的依赖注入

3.4.1 bean和BeanFactory

Spring的依赖注入容器的核心是BeanFactory接口。BeanFactory负责管理组件,包括依赖项以及它们的生命周期。在Spring中,术语bean用于引用由容器管理的任何组件,bean配置由实现BeanDefinition接口的类的实例表示。bean配置不仅存储有关bean本身的信息,还存储有关它所依赖的bean的信息。

3.4.2 BeanFactory实现

在声明Spring XSD位置时,最好不要包含版本号。这个问题已经由Spring代为处理,因为版本化的XSD文件是通过spring.schema文件的指针配置的。该文件驻留在定义为项目依赖项的spring-beans模块中。这样做也可以防止升级到新版本的Spring时对所有的bean文件进行修改。

3.4.3 ApplicationContext

在Spring中,ApplicationContext接口是BeanFactory的一个扩展。在开发基于Spring的应用程序时,建议通过ApplicationContext接口与Spring进行交互。

3.5 配置ApplicationContext

3.5.2 基本配置概述

<context:component-scan>标记告诉Spring扫描代码,从而找到使用@Component@Controller@Service@Repository注解的注入bean以及支持在指定包(及其所有子包)下使用@Autowired@Inject@Resource注解的bean。在<context:component-scan>标记中,可以使用逗号、分号和空格作为分隔符来定义多个包。此外,该标记支持组件扫描的包含和排除,从而实现更细粒度的控制。

3.5.3 声明Spring组件

@ComponentScan注解与<context:component-scan>标签等效。

配置类可以使用@ImportResource从一个或多个XML文件中导入bean定义。

p名称空间没有在XSD文件中定义,而只存在于Spring Core中;因此,在schemaLocation属性中没有声明XSD。

c名称空间没有在XSD文件中定义,而只存在于Spring Core中;因此,在schemaLocation属性中没有声明XSD。

当有多个构造函数参数或者你的类有多个构造函数时,需要为每个<constructor-arg>标记指定一个index属性,以指定构造函数签名中参数的索引,从0开始。

对于注解式的构造函数注入,可以通过将注解@Value直接应用于目标构造函数方法来避免混淆。

@Autowired注解只能应用于一个构造函数方法,如果将该注解应用于多个构造函数方法,Spring会在启动时报错。

Spring中支持的第三种依赖注入被称为字段注入。依赖项直接注入字段中,不需要构造函数或setter。字段注入通过使用@Autowired注解来注解类成员完成。

通过分别使用<property><constructor-args>标记,可以将注入参数用于setter注入和构造函数注入。

@Valuea可以直接应用于属性声明语句。

@Value("tc..p1")
private String p1;

被注入的类型不一定时目标bean上所定义的确切类型:类型只需要兼容即可。兼容意味着如果目标bean上声明的类型是一个接口,那么注入类型必须实现此接口。如果声明的类型是一个类,那么注入类型必须是相同类型或子类型。

注入和ApplicationContext嵌套

Spring支持ApplicationContext的层次结构,一个上下文可以成为另一个上下文的父级。

当父子上下文中存在名称相同的bean时,<ref>标签通过bean属性获得子上下文中的bean,通过parent属性获取父上下文中的bean。

注入集合

可以选择<list><map><set><props>来表示ListMapSetProperties实例。<props>标记只允许将String作为值传入。

利用Spring提供的util名称空间来声明用来存储集合属性的bean,可以极大地简化配置。

对于集合类型注入,必须明确指示Spring通过指定@Resource注解支持的bean名称来执行注入。因为@Autowired注解的语义定义方式是,始终将数组、集合和映射视为相应bean的集合,而目标bean类型从声明的集合值类型派生。例如,如果一个类具有List<ContentHolder>类型的属性并且定义了@Autowired注解,那么Spring会尝试将当前ApplicationContext中所有ContentHolder类型的bean注入该属性。

3.5.4 使用方法注入

除了构造函数注入和setter注入,Spring提供的另一个不常用的DI功能是方法注入。Spring方法注入功能有两种形式:查找方法注入(Lookup Method Injection)和方法替换(Method Replacement)。查找方法注入提供了一种机制,bean可以获得它的一个依赖项;而方法替换允许随意替换bean上任何方法的实现,而无需修改原始代码。

<bean id="abstractLookupDemoBean" class="study.hwj.chapter03.lookup.AbstractLookupDemoBean">
<lookup-method name="getMySinger" bean="singer"/>
</bean>

StopWatch类是Spring提供的一个实用类。用来执行简单的性能测试。

@Scope注解:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Singer {
...
}

@Lookup注解:

@Lookup("singer")
public void sing() {}

查找方法注入的注意事项

当想要使用两个具有不同生命周期的bean时,可以使用查找方法注入。当bean共享相同的生命周期时,应该避免使用查找方法注入,尤其当这些bean都是单例时。

基于XML配置的查找方法没必要一定抽象化,抽象化可以防止忘记配置查找方法,也可以使用空白的实现。基于注解的配置强制方法的空白实现;否则,不会创建bean。

方法替换

通过使用方法替换,可以任意替换任何bean的任何方法的实现,而无需更改所修改bean的源代码。

Spring在内部使用CGLIB将想要替换的方法的调用重定向为另一个实现了MethodReplacer接口的bean。

在有重载方法的情况下,可以使用<arg-type>标记指定要匹配的方法签名。<arg-type>标记支持模式匹配,因此String可以与java.lang.String以及java.lang.StringBuffer匹配。

<bean id="methodReplacer" class="study.hwj.chapter03.methodreplace.FormatMessageReplacer"/>

<bean id="replacementTarget" class="study.hwj.chapter03.methodreplace.ReplacementTarget">
<replaced-method name="formatMessage" replacer="methodReplacer">
<arg-type>String</arg-type>
</replaced-method>
</bean>

建议为每个方法或一组重载的方法使用一个MethodReplacer。应该避免针对多个不相关的方法使用单个MethodReplacer

3.5.5 了解bean命名

每个bean在其所在的ApplicationContext中至少包含一个唯一的名称。如果为<bean>标记赋予一个id属性,那么该属性的值将用作名称。如果没有指定id属性,Spring会查找name属性,如果定义了name属性,那么将使用name属性中定义的第一个名称(name属性可以定义多个名称)。如果既没有id,也没有name,那么Spring使用该bean的类名作为名称。如果声明了多个没有ID或名称的相同类型的bean,那么Spring将在ApplicationContext初始化期间,在注入时抛出异常。

作为一种惯例,应该使用id属性作为bean的名称,然后使用名称别名将bean与其他名称相关联。

bean名称别名

Spring允许一个bean拥有多个名称。可以通过在<bean>的name属性中指定以空格、逗号或分号分隔的名称列表来实现多个名称。name属性可以替代id属性,或者组合使用这两个属性。除了使用name属性,还可以使用<alias>标记定义别名。

可以通过调用ApplicationContext.getAliases(String)并传入任何bean的名称或id来获取bean的别名列表。别名列表以字符串数组返回(不包含传入的名称)。

Spring IOC对name和id属性值的处理方式不同。

<!-- 这个bean的id为【jon johnny,jonathan;jim】 -->
<bean id="jon johnny,jonathan;jim" class="java.lang.String"/>

使用注解配置的bean命名

当使用注解声明bean定义时,bean命名与XML稍有不同。

@Component注解没有任何参数,在这种情况下遵循的惯例是将bean命名为类本身,但是将首字母小写,其他构造型注解也遵循该惯例。使用@Component("singer")相当于使用@Component注解Singer类。如果项以不同的方式命名bean,@Component注解必须接受bean名称作为参数。由于@Component的参数成为bean的唯一标识符,以这种方式声明bean时不可以使用别名。

使用@Bean注解方法声明时,没有为@Bean提供任何参数时,方法名就是bean的id。要声明别名,可以使用name属性,如果该属性的值是要给字符串数组,第一个数组值成为id,其他值成为别名。

在Spring4.2中引入了@AliasFor注解。该注解用于为注解属性声明别名。还可以声明元注解属性的别名。

使用@AliasFor为注解的属性创建别名是有一定局限性的。@AliasFor不能用于任何构造型注解(@Component及其特例),还有@Qualifier注解,因为出现的晚。

3.5.6 了解bean实例化模式

选择实例化模式

关于<bean>scope属性:

单例(singleton)应该在下列情况下使用:

  1. 没有状态的共享对象;
  2. 具有只读状态的共享对象;
  3. 具有共享状态的共享对象;
  4. 具有可写状态的高通量(hign-throughput)对象;

非单例(prototype):

  1. 具有可写状态的对象;
  2. 具有私有状态的对象;

实现bean作用域

Spring从版本4开始支持以下bean作用域:

  1. 单例作用域:默认;
  2. 原型作用域;
  3. 请求作用域:用于Web应用程序,针对每个HTTP请求;
  4. 会话作用域:用于Web应用程序,针对每个HTTP会话;
  5. 全局会话作用域:用于基于Portlet的Web应用程序。带有全局会话作用域的bean可以在同一个Spring MVC驱动的Web应用程序的所有Portlet之间共享;
  6. 线程作用域:当一个新线程请求bean实例时,Spring将创建一个新的bean实例,而对于同一个线程,返回相同的实例。线程作用域默认情况下未注册;
  7. 自定义作用域:通过实现 org.springframework.beans.factory.config.Scope 接口创建自定义作用域,并在Spring配置中注册自定义作用域(对于XML,使用 org.springframework.beans.factory.config.CustomScopeConfigurer );

3.6 解析依赖项

可以使用<bean>depends-on属性为Spring提供有关bean的依赖项的附加信息。

ApplicationContextAware 接口是一个特定于Spring的接口,它为 ApplicationContextAware 对象强制实现一个setter。Spring IOC容器会自动进行检测,并且注入bean段创建时所在的 ApplicationContextAware 。这一切都是在调用bean的构造函数之后完成的,因此在构造函数中使用 ApplicationContextAware 将导致 NullPointerException 异常。

当开发应用程序时,应该避免应用程序使用此功能;相反,应该通过setter和构造函数注入协定来定义依赖项。但是如果将Spring与遗留代码集成在一起,可能会发现代码中定义的依赖项要求向Spring框架提供额外的信息。

3.7 自动装配bean

Spring支持五种自动装配模式:

  1. byName模式:Spring尝试将每个属性连接到同名的bean。如果目标bean中具有名为foo的属性并且在ApplicationContext中定义了foo bean,那么foo bean将被分配给目标bean的foo属性;
  2. byType模式:Spring通过在ApplicationContext中自动使用相同类型的bean来尝试连接目标bean模式的每个属性;
  3. 构造函数模式:与byType模式功能上相同。Spring试图匹配构造函数中最大数量的参数;
  4. 默认模式:Spring将自动在构造函数模式和byType模式之间进行选择。如果bean有一个默认的(无参)构造函数,那么就是用byType模式;否则,使用构造函数模式;
  5. 无:默认。

使用注解定义bean时,默认的自动装配模式是byType。

当有多个相同类型的bean时,可以使用primaryqualifier指定。

在大多数情况下,不应该使用自动装配。

3.8 设置bean继承

如果使用共享的属性值声明大量具有相同值的bean,则应该避免使用复制粘贴来共享值,而是应该在配置中设置集成层次结构。

bean继承不必与Java继承层次结构相匹配。与其说bean继承是一项继承功能,还不如说更像是模板功能。如果要更改子bean的类型,该类型必须扩展父bean的类型。

20191103 《Spring5高级编程》笔记-第3章的更多相关文章

  1. C#高级编程笔记之第二章:核心C#

    变量的初始化和作用域 C#的预定义数据类型 流控制 枚举 名称空间 预处理命令 C#编程的推荐规则和约定 变量的初始化和作用域 初始化 C#有两个方法可以一确保变量在使用前进行了初始化: 变量是字段, ...

  2. C#高级编程笔记之第一章:.NET体系结构

    1.1 C#与.NET的关系 C#不能孤立地使用,必须与.NET Framework一起使用一起考虑. (1)C#的体系结构和方法论反映了.NET基础方法论. (2)多数情况下,C#的特定语言功能取决 ...

  3. 20191105 《Spring5高级编程》笔记-【目录】

    背景 开始时间:2019/09/18 21:30 Spring5高级编程 版次:2019-01-01(第5版) Spring5最新版本:5.1.9 CURRENT GA 官方文档 Spring Fra ...

  4. 读《C#高级编程》第1章问题

    读<C#高级编程>第1章 .Net机构体系笔记 网红的话:爸爸说我将来会是一个牛逼的程序员,因为我有一个梦,虽然脑壳笨但是做事情很能坚持. 本章主要是了解.Net的结构,都是一些概念,并没 ...

  5. Android高级编程笔记(四)深入探讨Activity(转)

    在应用程序中至少包含一个用来处理应用程序的主UI功能的主界面屏幕.这个主界面一般由多个Fragment组成,并由一组次要Activity支持.要在屏幕之间切换,就必须要启动一个新的Activity.一 ...

  6. C#高级编程9 第18章 部署

    C#高级编程9 第18章 部署 使用 XCopy 进行部署 本主题演示如何通过将应用程序文件从一台计算机复制到另一台计算机来部署应用程序. 1.将项目中生成的程序集复制到目标计算机,生成的程序集位于项 ...

  7. C#高级编程9 第17章 使用VS2013-C#特性

    C#高级编程9 第17章 使用VS2013 编辑定位到 如果默认勾选了这项,请去掉勾选,因为勾选之后解决方案的目录会根据当前文件选中. 可以设置项目并行生成数 版本控制软件设置 所有文本编辑器行号显示 ...

  8. C#高级编程9 第16章 错误和异常

    C#高级编程9 第16章 错误和异常 了解这章可以学会如何处理系统异常以及错误信息. System.Exception类是.NET运行库抛出的异常,可以继承它定义自己的异常类. try块代码包含的代码 ...

  9. C#高级编程笔记之第三章:对象和类型

    类和结构的区别 类成员 匿名类型 结构 弱引用 部分类 Object类,其他类都从该类派生而来 扩展方法 3.2 类和结构 类与结构的区别是它们在内存中的存储方式.访问方式(类似存储在堆上的引用类型, ...

  10. UNIX环境高级编程笔记之文件I/O

    一.总结 在写之前,先唠几句,<UNIX环境高级编程>,简称APUE,这本书简直是本神书,像我这种小白,基本上每看完一章都是“哇”这种很吃惊的表情.其实大概三年前,那会大三,我就买了这本书 ...

随机推荐

  1. phonetic

    Simple Classification of English Vowels and Consonants 1.Classifation of English Vowels a)Monophtong ...

  2. Mybatis SQL 使用JAVA 静态资源

    常量:${@com.htsc.backtest.component.Global@PAGE_SIZE} 静态方法:${@com.htsc.backtest.component.Global@doMet ...

  3. python 删除/app/*/logs/*/*.logs指定多少天的文件

    # encoding: utf-8 import sys import getopt import os import glob import time import datetime def rem ...

  4. 【JavaScript】DOM之BOM

    BOM 1.BOM是什么 提供了独立页面内容,与浏览器相关的一系列对象,管理窗口之间通信 2.Window对象 具有双重角色,对象即是允许JS访问浏览器窗口的一个对象,和ECMAScript规范中的G ...

  5. flask中自定义日志类

    一:项目架构 二:自定义日志类 1. 建立log.conf的配置文件 log.conf [log] LOG_PATH = /log/ LOG_NAME = info.log 2. 定义日志类 LogC ...

  6. 详述 DB2 分页查询及 Java 实现的示例_java - JAVA

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 博主说:有时候,我们需要对数据库中现有的数据进行大量处理操作(例如表中的某个字段需要全部更新等),如果直接使用selec ...

  7. 【ZJOI2008】树的统计

    题目 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: ...

  8. [luogu]P3941 入阵曲[前缀和][压行]

    [luogu]P3941 入阵曲 题目描述 小 F 很喜欢数学,但是到了高中以后数学总是考不好. 有一天,他在数学课上发起了呆:他想起了过去的一年.一年前,当他初识算法竞赛的 时候,觉得整个世界都焕然 ...

  9. 【FJ省队训练&&NOIP夏令营】酱油&&滚粗记

    FJOI2016省队训练滚粗记 2016.07.03~2016.07.06(Day1~5) 在学校期末考.因为才省选二试too young too simple爆蛋了所以下半个学期只能滚回去读文化课, ...

  10. RedisTemplate访问Redis数据结构(四)——Set

    Redis的Set是string类型的无序集合.集合成员是唯一的,这就意味着集合中不能出现重复的数据,Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1). SetOper ...