(二)《Spring实战》——Spring核心
第二章:装配Bean
在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
1. Spring配置的可选方案
Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
- 在XML中进行显式配置。
- 在Java中进行显式配置。
- 隐式的bean发现机制和自动装配。
Spring有多种可选方案来配置bean,这是非常棒的,但有时候你必须要在其中做出选择。
这方面,并没有唯一的正确答案。你所做出的选择必须要适合你和你的项目。而且,谁说我们只能选择其中的一种方案呢?Spring的配置风格是可以互相搭配的,所以你可以选择使用XML装配一些bean,使用Spring基于Java的配置(JavaConfig)来装配另一些bean,而将剩余的bean让Spring去自动发现。
即便如此,我的建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使XML。
2. 自动化装配bean
尽管显式装配技术(借助Java和XML来进行Spring装配)非常有用,但是在便利性方面,最强大的还是Spring的自动化配置。
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(autowiring):Spring自动满足bean之间的依赖。
为了阐述组件扫描和装配,我们需要创建几个bean,它们代表了一个音响系统中的组件。首先,要创建CompactDisc 类,Spring会发现它并将其创建为一个bean。然后,会创建一个CDPlayer 类,让Spring发现它,并将CompactDisc bean注入进来。
2.1 创建可被发现的bean
CompactDisc接口在Java中定义了CD的概念
package soundsystem; public interface CompactDisc {
void play();
}
CompactDisc 的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
带有@Component注解的CompactDisc实现类SgtPeppers
package soundsystem;
import org.springframework.stereotype.Component; @Component
public class SgtPeppers implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles"; public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
和CompactDisc 接口一样,SgtPeppers 的具体内容并不重要。你需要注意的就是SgtPeppers 类上使用了@Component 注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppers bean,因为这个类使用了@Component 注解,所以Spring会为你把事情处理妥当。
不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component 注解的类,并为其创建bean。
@ComponentScan注解启用了组件扫描
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; @Configuration
@ComponentScan
public class CDPlayerConfig {
}
如果没有其他配置的话,@ComponentScan 默认会扫描与配置类相同的包。因为CDPlayerConfig 类位于soundsystem 包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component 注解的类。这样的话,就能发现CompactDisc ,并且会在Spring中自动为其创建一个bean。
如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context 命名空间的<context:component-scan> 元素。
通过XML启用组件扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="soundsystem" /> </beans>
测试组件扫描能够发现CompactDisc
package soundsystem; import static org.junit.Assert.*; import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest { @Autowired
private CompactDisc cd; @Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
} }
CDPlayerTest 使用了Spring的SpringJUnit4ClassRunner ,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration 会告诉它需要在CDPlayerConfig 中加载配置。因为CDPlayerConfig 类中包含了@ComponentScan ,因此最终的应用上下文中应该包含CompactDisc bean。
测试代码中有一个CompactDisc 类型的属性,并且这个属性带有@Autowired 注解,以便于将CompactDisc bean注入到测试代码之中。
2.2 为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID。在前面的例子中,尽管我们没有明确地为SgtPeppers bean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers ,也就是将类名的第一个字母变为小写。
如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component 注解。
还有一种方式是使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named 注解来为bean设置ID。(不常用)
2.3 设置组件扫描的基础包
为了指定不同的基础包,你所需要做的就是在@ComponentScan 的value属性中指明包的名称:
要表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:
要设置多个基础包的话,只需要将basePackages 属性设置为要扫描包的一个数组即可:
除了将包设置为简单的String类型(缺点类型不安全【not type-safe】)之外,@ComponentScan 还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:
可以看到,basePackages 属性被替换成了basePackageClasses 。同时,我们不是再使用String 类型的名称来指定包,为basePackageClasses 属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包。
2.4 通过为bean添加注解实现自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired 注解。
如下,构造器上添加了@Autowired 注解,这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc 类型的bean。
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd; @Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
} public void play() {
cd.play();
}
}
@Autowired 注解不仅能够用在构造器上,还能用在属性的Setter方法上。
@Autowired
public setCompactDisc(CompactDisc cd) {
this.cd = cd;
}
@Autowired 注解可以用在类的任何方法上。
@Autowired
public insertCompactDisc(CompactDisc cd) {
this.cd = cd;
}
不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired 的required 属性设置为false :
@Autowired(required=false)
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
将required 属性设置为false 时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但是,把required 属性设置为false 时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException 。
@Autowired 是Spring特有的注解。如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以考虑将其替换为@Inject :
@Inject 注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named 注解。在自动装配中,Spring同时支持@Inject 和@Autowired 。尽管@Inject 和@Autowired 之间有着一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。
2.5 验证自动装配
package soundsystem; import static org.junit.Assert.*; import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest { //测试代码使用StandardOutputStreamLog 基于控制台的输出编写断言
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog(); //将CDPlayer bean注入到测试代码的player 成员变量之中(它是更为通用的MediaPlayer 类型)
@Autowired
private MediaPlayer player; @Autowired
private CompactDisc cd; @Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
} @Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\n",
log.getLog());
} }
3. 通过Java代码装配bean
在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。
同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。
3.1 创建配置类
package soundsystem;import org.springframework.context.annotation.Configuration; @Configuration
public class CDPlayerConfig {
}
创建JavaConfig类的关键在于为其添加@Configuration 注解,@Configuration 注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
3.2 声明简单的bean
要在JavaConfig中声明bean ,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean 注解。
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
默认情况下,bean的ID与带有@Bean 注解的方法名是一样的。当然也可通过name属性自定义。
@Bean(name="lonelyHeartsClubBand")
public CompactDisc compactDisc() {
return new SgtPeppers();
}
3.3 借助JavaConfig实现注入
可以使用上面的代码,通过调用方法来引用bean,但最常用的方法是下面这种:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
在这里,cdPlayer() 方法请求一个CompactDisc 作为参数。当Spring调用cdPlayer() 创建CDPlayer bean的时候,它会自动装配一个CompactDisc 到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer() 方法也能够将CompactDisc 注入到CDPlayer 的构造器中,而且不用明确引用CompactDisc 的@Bean 方法。
4. 通过xml装配bean
4.1 创建xml配置规范
创建新的配置规范。在使用JavaConfig的时候,这意味着要创建一个带有@Configuration 注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
4.2 声明一个简单的bean
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
4.3 借助构造器注入初始化bean
构造器注入,有两种基本的配置方案可供选择:
- <constructor-arg> 元素
- 使用Spring 3.0所引入的c-命名空间
4.3.1 构造器注入bean引用
<constructor-arg >使用ref 来引入其他bean
c-命名空间
4.3.2 将字面量注入到构造器中
<constructor-arg>使用value 属性表明给定的值要以字面量的形式注入到构造器之中。
c-命名空间
①引用构造器参数的名字
②通过参数索引装配相同的字面量值
4.3.3 装配集合
4.4 设置属性
引用bean
<property >通过ref引用bean
p-命名空间
将字面量注入到属性中
<property> 使用value 属性设置属性值
p-命名空间
不能使用p-命名空间来装配集合,装配集合可使用<util:list>
现在,我们能够像使用其他的bean那样,将磁道列表bean注入到BlankDisc bean的tracks 属性中
5. 导入和混合配置
在自动装配时,它并不在意要装配的bean来自哪里。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。
5.1 在JavaConfig中引用XML配置
假设BlankDisc 定义在名为cd-config.xml 的文件中,该文件位于根类路径下,那么可以修改SoundSystemConfig ,让它使用@ImportResource 注解,如下所示:
两个bean——配置在JavaConfig中的CDPlayer 以及配置在XML中BlankDisc ——都会被加载到Spring容器之中。因为CDPlayer 中带有@Bean 注解的方法接受一个CompactDisc 作为参数,因此BlankDisc 将会装配进来,此时与它是通过XML配置的没有任何关系。
5.2 在XML配置中引用JavaConfig
在XML中,我们可以使用<import >元素来拆分XML配置。
(二)《Spring实战》——Spring核心的更多相关文章
- spring实战 — spring数据库事务
欢迎加入程序员的世界,添物科技为您服务. 欢迎关注添物网的微信(微信号:tianwukeji),微博(weibo.com/91tianwu/),或下载添物APP,及时获取最新信息. 免费加入QQ群:5 ...
- (一)《Spring实战》——Spring核心
<Spring实战>(第4版) 第一章:Spring之旅 1. 简化Java开发 为了降低Java开发的复杂性,Spring采取了以下4种关键策略: 基于POJO的轻量级和最小侵入性编程: ...
- 【Spring实战】--1Spring的核心
最近面试总会涉及Spring的优点,SpringMVC与Struts2的比较,生活慢慢稳定下来,这些面试还是应了那句话“只顾盲目拉车,不会低头看路”,回过头来还是要好好研究一下Spring,如果仅仅是 ...
- Spring实战(二)Spring容器和bean的生命周期
引入问题: 在XML配置文件中配置bean后,这些文件又是如何被加载的?它们被加载到哪里去了? Spring容器——框架核心 1.什么是Spring容器?它的功能是什么? 在基于Spring的应用中, ...
- Spring实战——缓存
缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis-- 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓存技术的优势就是快,无需反复查询等. 当然 ...
- Spring实战——无需一行xml配置实现自动化注入
已经想不起来上一次买技术相关的书是什么时候了,一直以来都习惯性的下载一份电子档看看.显然,如果不是基于强烈的需求或强大的动力鞭策下,大部分的书籍也都只是蜻蜓点水,浮光掠影. 就像有位同事说的一样,有些 ...
- Spring Boot 最核心的 25 个注解,都是干货!
学习和应用 Spring Boot 有一些时间了,你们对 Spring Boot 注解了解有多少呢?今天栈长我给大家整理了 Spring Boot 最核心的 25 个注解,都是干货! 你所需具备的基础 ...
- Spring Boot 最核心的 3 个注解详解
最近面试一些 Java 开发者,他们其中有些在公司实际用过 Spring Boot, 有些是自己兴趣爱好在业余自己学习过.然而,当我问他们 Spring Boot 最核心的 3 个注解是什么,令我失望 ...
- 《Spring实战》学习笔记-第五章:构建Spring web应用
之前一直在看<Spring实战>第三版,看到第五章时发现很多东西已经过时被废弃了,于是现在开始读<Spring实战>第四版了,章节安排与之前不同了,里面应用的应该是最新的技术. ...
- spring框架 AOP核心详解
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...
随机推荐
- Redis 3.0 Windows 安装步骤
Redis 3.0 Windows 安装步骤 ----来自 https://www.aliyun.com/jiaocheng/872572.html 发布时间:2018-04-10 来源:网络 上传者 ...
- python函数 divmod
divmod(a,b)函数 中文说明: divmod(a,b)方法返回的是a//b(除法取整)以及a对b的余数 返回结果类型为tuple 参数: a,b可以为数字(包括复数) from 2. Add ...
- 测试用例设计——WEB通用测试用例(转)
http://qa.blog.163.com/blog/static/190147002201111332029404/
- 关于warning: Clock skew detected. Your build may be incomplete. 的解决方法
今天发现电脑的系统时间不对,因此将时钟进行了改动,回头编译Linux kernel的时候,提演示样例如以下的warning: warning: Clock skew detected. Your ...
- .NET Versioning and Multi-Targeting - .NET 4.5 is an in-place upgrade to .NET 4.0
Say what you will about the past ridiculousness of .NET Framework versioning, since the confusion of ...
- Caliburn.Micro对目录结构的要求
Caliburn.Micro对MVVM目录结构的要求判定规则是如下正则表达式: (?<nsbefore>([A-Za-z_]\w*\.)*)(?<subns>ViewModel ...
- [mark]如何删除地址栏的记录?
比如,我输入字母a ..因为经常访问.它首先会自动帮我补填 amazon.cn 第二行出现 ali213...第三行是 alipay... 这个很简单,没必要搞得很复杂很Geek.比如楼主的情况,首先 ...
- Memcached源码分析——连接状态变化分析(drive_machine)
这篇文章主要介绍Memcached中,基于libevent构造的主线程和worker线程所处理连接的状态互相转换的过程(不涉数据的存取等操作),也就是drive_machine的主要业务逻辑了.状态转 ...
- OpenSSL再曝CCS注入漏洞-心伤未愈又成筛子
太戏剧了,昨晚看了佳片有约,还不错,2012版的<完美回顾>,像我这样的人依旧选择用电视或者去影院看电影,在没有中间插播广告的时候,体验憋尿得过程中,总是能突然有非常多的想法,这是用电脑或 ...
- OpenShift 如何获取bearer Token以便进行各种API调用
Openshift 需要通过bearer token的方式和API进行调用,比如基于Postman就可以了解到,输入bearer token后 1.如何获取Bearer Token 但Bearer T ...