第二章:装配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会抛出一个异常。为了避免异常的出现,你可以将@Autowiredrequired 属性设置为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核心的更多相关文章

  1. spring实战 — spring数据库事务

    欢迎加入程序员的世界,添物科技为您服务. 欢迎关注添物网的微信(微信号:tianwukeji),微博(weibo.com/91tianwu/),或下载添物APP,及时获取最新信息. 免费加入QQ群:5 ...

  2. (一)《Spring实战》——Spring核心

    <Spring实战>(第4版) 第一章:Spring之旅 1. 简化Java开发 为了降低Java开发的复杂性,Spring采取了以下4种关键策略: 基于POJO的轻量级和最小侵入性编程: ...

  3. 【Spring实战】--1Spring的核心

    最近面试总会涉及Spring的优点,SpringMVC与Struts2的比较,生活慢慢稳定下来,这些面试还是应了那句话“只顾盲目拉车,不会低头看路”,回过头来还是要好好研究一下Spring,如果仅仅是 ...

  4. Spring实战(二)Spring容器和bean的生命周期

    引入问题: 在XML配置文件中配置bean后,这些文件又是如何被加载的?它们被加载到哪里去了? Spring容器——框架核心 1.什么是Spring容器?它的功能是什么? 在基于Spring的应用中, ...

  5. Spring实战——缓存

    缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis-- 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓存技术的优势就是快,无需反复查询等. 当然 ...

  6. Spring实战——无需一行xml配置实现自动化注入

    已经想不起来上一次买技术相关的书是什么时候了,一直以来都习惯性的下载一份电子档看看.显然,如果不是基于强烈的需求或强大的动力鞭策下,大部分的书籍也都只是蜻蜓点水,浮光掠影. 就像有位同事说的一样,有些 ...

  7. Spring Boot 最核心的 25 个注解,都是干货!

    学习和应用 Spring Boot 有一些时间了,你们对 Spring Boot 注解了解有多少呢?今天栈长我给大家整理了 Spring Boot 最核心的 25 个注解,都是干货! 你所需具备的基础 ...

  8. Spring Boot 最核心的 3 个注解详解

    最近面试一些 Java 开发者,他们其中有些在公司实际用过 Spring Boot, 有些是自己兴趣爱好在业余自己学习过.然而,当我问他们 Spring Boot 最核心的 3 个注解是什么,令我失望 ...

  9. 《Spring实战》学习笔记-第五章:构建Spring web应用

    之前一直在看<Spring实战>第三版,看到第五章时发现很多东西已经过时被废弃了,于是现在开始读<Spring实战>第四版了,章节安排与之前不同了,里面应用的应该是最新的技术. ...

  10. spring框架 AOP核心详解

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...

随机推荐

  1. April Fools Day Contest 2016 D. Rosetta Problem

    D. Rosetta Problem 题目连接: http://www.codeforces.com/contest/656/problem/D Description ++++++++[>+& ...

  2. Linux下添加静态路由表设置网关出现SIOCADDRT: Network is unreachable的问题分析

    场景: # route add default gw 192.168.4.1 route: SIOCADDRT: Network is unreachable 解释: 1.先ping一下网关,但是pi ...

  3. 一步一步实现listview加载的性能优化

    listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次: 0.最原始的加载 1.利用convertView 2.利用ViewHol ...

  4. C/C++之回调函数

    转自:http://www.cnblogs.com/chenyuming507950417/archive/2012/01/02/2310114.html 今天讨论下C/C++中的回调函数. 在理解“ ...

  5. java 中 BigDecimal 怎么与 0 比较

    标准做法int r=big_decimal.compareTo(BigDecimal.Zero); //和0,Zero比较if(r==0) //等于if(r==1) //大于if(r==-1) //小 ...

  6. Android面试题-OkHttp3源码分析

    本文配套视频: okhttp内核分析配套视频一 okhttp内核分析配套视频二 okhttp内核分析配套视频三 源码分析相关面试题 Volley源码分析 注解框架实现原理 基本使用 从使用方法出发,首 ...

  7. (转)找回vss超级管理员密码

    原文:http://www.cnblogs.com/446557021/archive/2011/01/05/1926213.html 如果忘记了VSS管理员密码,打开vss数据库所在的文件夹,打开d ...

  8. thymleaf th:if标签

    1.概念 <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOC ...

  9. django的动态url,url里含有参数,含有参数的url

    #!/usr/bin/env python # coding:utf- from django.conf.urls import url,include from django.contrib imp ...

  10. C语言指针加1问题以及字节对齐问题

    今天早上自己写了一段代码,然后测试的时候发现结果总是和预期的不一样,而且偏差的有点离谱,冥思苦想了将近五个小时,最后在我要开始怀疑人生的时候,发现原来是自己犯了一个极其低级但又容易被忽略的问题.好吧, ...