Spring实战2:装配bean—依赖注入的本质
主要内容
- Spring的配置方法概览
- 自动装配bean
- 基于Java配置文件装配bean
- 控制bean的创建和销毁
任何一个成功的应用都是由多个为了实现某个业务目标而相互协作的组件构成的,这些组件必须相互了解、能够相互协作完成工作。例如,在一个在线购物系统中,订单管理组件需要与产品管理组件以及信用卡认证组件协作;这些组件还需要跟数据库组件协作从而进行数据库读写操作。
在Spring应用中,对象无需自己负责查找或者创建与其关联的其他对象,由容器负责将创建各个对象,并创建各个对象之间的依赖关系。例如,一个订单管理组件需要使用信用卡认证组件,它不需要自己创建信用卡认证组件,只需要定义它需要使用信用卡认证组件即可,容器会创建信用卡认证组件然后将该组件的引用注入给订单管理组件。
创建各个对象之间协作关系的行为通常被称为装配(wiring),这就是依赖注入(DI)的本质。
2.1 Spring的配置方法概览
正如在Spring初探一文中提到的,Spring容器负责创建应用中的bean,并通过DI维护这些bean之间的协作关系。作为开发人员,你应该负责告诉Spring容器需要创建哪些bean以及如何将各个bean装配到一起。Spring提供三种装配bean的方式:
- 基于XML文件的显式装配
- 基于Java文件的显式装配
- 隐式bean发现机制和自动装配
绝大多数情况下,开发人员可以根据个人品味选择这三种装配方式中的一种。Spring也支持在同一个项目中混合使用不同的装配方式。
我的建议是:尽可能使用自动装配,越少写显式的配置文件越好;当你必须使用显式配置时(例如,你要配置一个bean,但是该bean的源码不是由你维护),尽可能使用类型安全、功能更强大的基于Java文件的装配方式;最后,在某些情况下只有XML文件中才又你需要使用的名字空间时,再选择使用基于XML文件的装配方式。
2.2 自动装配bean
Spring通过两个特性实现自动装配:
- Component scanning——Spring自动扫描和创建应用上下文中的beans;
- Autowiring——Spring自动建立bean之间的依赖关系;
这里用一个例子来说明:假设你需要实现一个音响系统,该系统中包含CDPlayer和CompactDisc两个组件,Spring将自动发现这两个bean,并将CompactDisc的引用注入到CDPlayer中。
2.2.1 创建可发现的beans
首先创建CD的概念——CompactDisc接口,如下所示:
package com.spring.sample.soundsystem;
public interface CompactDisc {
void play();
}
CompactDisc接口的作用是将CDPlayer与具体的CD实现解耦合,即面向接口编程。这里还需定义一个具体的CD实现,如下所示:
package com.spring.sample.soundsystem;
import org.springframework.stereotype.Component;
@Componentpublic class SgtPeppers implements CompactDisc {
private String title = "Sgt. Perppers' Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
这里最重要的是@Component注解,它告诉Spring需要创建SgtPeppers bean。除此之外,还需要启动自动扫描机制,有两种方法:基于XML配置文件;基于Java配置文件,代码如下(二选一):
- 创建soundsystem.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"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="com.spring.sample.soundsystem" /></beans>
在这个XML配置文件中,使用<context:component-scan>标签启动Component扫描功能,并可设置base-package属性。
- 创建Java配置文件
package com.spring.sample.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.spring.sample.soundsystem")
public class SoundSystemConfig {
}
在这个Java配置文件中有两个注解值得注意:@Configuration表示这个.java文件是一个配置文件;@ComponentScan表示开启Component扫描,并且可以设置basePackages属性——Spring将会设置该目录以及子目录下所有被@Component注解修饰的类。
- 自动配置的另一个关键注解是@Autowired,基于之前的两个类和一个Java配置文件,可以写个测试
package com.spring.sample.soundsystem;
import com.spring.sample.config.SoundSystemConfig;
import org.junit.Assert;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 = SoundSystemConfig.class)
public class SoundSystemTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
Assert.assertNotNull(cd);
}
}
运行测试,测试通过,说明@Autowired注解起作用了:自动将扫描机制创建的CompactDisc类型的bean注入到SoundSystemTest这个bean中。
2.2.2 给被扫描的bean命名
在Spring上下文中,每个bean都有自己的ID。在上一个小节的例子中并没有提到这一点,但Spring在扫描到SgtPeppers这个组件并创建对应的bean时,默认给它设置的ID为sgtPeppers——是的,这个ID就是将类名称的首字母小写。
如果你需要给某个类对应的bean一个特别的名字,则可以给@Component注解传入指定的参数,例如:
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
2.2.3 设置需要扫描的目标basepackage
在之前的例子中,我们通过给@Component注解传入字符串形式的包路径,来设置需要扫描指定目录下的类并为之创建bean。
可以看出,basePackages是复数,意味着你可以设置多个目标目录,例如:
@Configuration
@ComponentScan(basePackages = {"com.spring.sample.soundsystem", "com.spring.sample.video"})
public class SoundSystemConfig {
}
这种字符串形式的表示虽然可以,但是不具备“类型安全”,因此Spring也提供了更加类型安全的机制,即通过类或者接口来设置扫描机制的目标目录,例如:
@Configuration@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class SoundSystemConfig {
}
通过如上设置,会将CDPlayer和DVDPlayer各自所在的目录作为扫描机制的目标根目录。
如果应用中的对象是孤立的,并且互相之间没有依赖关系,例如SgtPeppersbean,那么这就够了。
2.2.4 自动装配bean
简单得说,自动装配的意思是让Spring从应用上下文中找到对应的bean的引用,并将它们注入到指定的bean。通过@Autowired注解可以完成自动装配。
例如,考虑下面代码中的CDPlayer类,它的构造函数被@Autowired修饰,表明当Spring创建CDPlayer的bean时,会给这个构造函数传入一个CompactDisc的bean对应的引用。
package com.spring.sample.soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
@Componentpublic class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowiredpublic CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
还有别的实现方法,例如将@Autowired注解作用在setCompactDisc()方法上:
@Autowiredpublic void setCd(CompactDisc cd) {
this.cd = cd;
}
或者是其他名字的方法上,例如:
@Autowiredpublic void insertCD(CompactDisc cd) {
this.cd = cd;
}
更简单的用法是,可以将@Autowired注解直接作用在成员变量之上,例如:
@Autowiredprivate CompactDisc cd;
只要对应类型的bean有且只有一个,则会自动装配到该属性上。如果没有找到对应的bean,应用会抛出对应的异常,如果想避免抛出这个异常,则需要设置@Autowired(required=false)。不过,在应用程序设计中,应该谨慎设置这个属性,因为这会使得你必须面对NullPointerException的问题。
如果存在多个同一类型的bean,则Spring会抛出异常,表示装配有歧义,解决办法有两个:(1)通过@Qualifier注解指定需要的bean的ID;(2)通过@Resource注解指定注入特定ID的bean;
2.2.5 验证自动配置
通过下列代码,可以验证:CompactDisc的bean已经注入到CDPlayer的bean中,同时在测试用例中是将CDPlayer的bean注入到当前测试用例。
package com.spring.sample.soundsystem;
import com.spring.sample.config.SoundSystemConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
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 = SoundSystemConfig.class)
public class CDPlayerTest {
public final Logger log = LoggerFactory.getLogger(CDPlayerTest.class);
@Autowiredprivate MediaPlayer player;
@Testpublic void playTest() {
player.play();
}
}
2.3 基于Java配置文件装配bean
Java配置文件不同于其他用于实现业务逻辑的Java代码,因此不能将Java配置文件业务逻辑代码混在一起。一般都会给Java配置文件新建一个单独的package。
2.3.1 创建配置类
实际上在之前的例子中我们已经实践过基于Java的配置文件,看如下代码:
@Configuration@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class SoundSystemConfig {
}
@Configuration注解表示这个类是配置类,之前我们是通过@ComponentScan注解实现bean的自动扫描和创建,这里我们重点是学习如何显式创建bean,因此首先将@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
这行代码去掉。
2.3.2 定义bean
通过@Bean注解创建一个Spring bean,该bean的默认ID和函数的方法名相同,即sgtPeppers。例如:
@Beanpublic CompactDisc sgtPeppers() {
return new SgtPeppers();
}
同样,可以指定bean的ID,例如:
@Bean(name = "lonelyHeartsClub")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
可以利用Java语言的表达能力,实现类似工厂模式的代码如下:
@Bean
public CompactDisc randomBeatlesCD() {
int choice = (int)Math.floor(Math.random() * 4);
if (choice == 0) {
return new SgtPeppers();
} else if (choice == 1) {
return new WhiteAlbum();
} else if (choice == 2) {
return new HardDaysNight();
} else if (choice == 3) {
return new Revolover();
}
}
2.3.3 JavaConfig中的属性注入
最简单的办法是将被引用的bean的生成函数传入到构造函数或者set函数中,例如:
@Beanpublic CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
看起来是函数调用,实际上不是:由于sgtPeppers()方法被@Bean注解修饰,所以Spring会拦截这个函数调用,并返回之前已经创建好的bean——确保该SgtPeppers bean为单例。
假如有下列代码:
@Beanpublic CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
@Beanpublic CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
如果把sgtPeppers()方法当作普通Java方法对待,则cdPlayerbean和anotherCDPlayerbean会持有不同的SgtPeppers实例——结合CDPlayer的业务场景看:就相当于将一片CD同时装入两个CD播放机中,显然这不可能。
默认情况下,Spring中所有的bean都是单例模式,因此cdPlayer和anotherCDPlayer这俩bean持有相同的SgtPeppers实例。
当然,还有一种更清楚的写法:
@Beanpublic CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
@Beanpublic CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
这种情况下,cdPlayer和anotherCDPlayer这俩bean持有相同的SgtPeppers实例,该实例的ID为lonelyHeartsClub。这种方法最值得使用,因为它不要求CompactDisc bean在同一个配置文件中定义——只要在应用上下文容器中即可(不管是基于自动扫描发现还是基于XML配置文件定义)。
2.4 基于XML配置文件装配bean
这种是Spring中最原始的定义方式,在此不再详述。
2.5 混合使用多种配置方法
通常,可能在一个Spring项目中同时使用自动配置和显式配置,而且,即使你更喜欢JavaConfig,也有很多场景下更适合使用XML配置。幸运的是,这些配置方法可以混合使用。
首先明确一点:对于自动配置,它从整个容器上下文中查找合适的bean,无论这个bean是来自JavaConfig还是XML配置。
2.5.1 在JavaConfig中解析XML配置
- 通过@Import注解导入其他的JavaConfig,并且支持同时导入多个配置文件;
@Configuration@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig {
} - 通过@ImportResource注解导入XML配置文件;
@Configuration@Import(CDPlayerConfig.class)
@ImportResource("classpath: cd-config.xml")
public class SoundSystemConfig {
}
2.5.2 在XML配置文件中应用JavaConfig
- 通过<import>标签引入其他的XML配置文件;
- 通过<bean>标签导入Java配置文件到XML配置文件,例如
<bean class="soundsystem.CDConfig" />
通常的做法是:无论使用JavaConfig或者XML装配,都要创建一个root configuration,即模块化配置定义;并且在这个配置文件中开启自动扫描机制:<context:component-scan>
或者@ComponentScan
。
2.6 总结
这一章中学习了Spring 装配bean的三种方式:自动装配、基于Java文件装配和基于XML文件装配。
由于自动装配几乎不需要手动定义bean,建议优先选择自动装配;如何必须使用显式配置,则优先选择基于Java文件装配这种方式,因为相比于XML文件,Java文件具备更多的能力、类型安全等特点;但是也有一种情况必须使用XML配置文件,即你需要使用某个名字空间(name space),该名字空间只在XML文件中可以使用。
参考资料
Spring实战2:装配bean—依赖注入的本质的更多相关文章
- Spring的自动装配与依赖注入
Spring的自动装配与依赖注入 装配 = 创建Bean + 注入Bean 创建Bean 自动发现 显式注册Bean 注入Bean 基于配置的注入 自动注入 Spring的装配分为显式装配和隐式装配, ...
- Spring实战 (第3版)——依赖注入
首先弄明白几个概念: 1.什么是POJO 2.JavaBean规范 3.EJB(Enterprise JavaBean) 体会Spring如何简化Java开发. 创建应用对象(组件)之间协作关系的行为 ...
- spring实战一:装配bean之注入Bean属性
内容参考自spring in action一书. 创建应用对象之间协作关系的行为通常称为装配,这也是依赖注入的本质. 1. 创建spring配置 spring是一个基于容器的框架.如果没有配置spri ...
- Spring实战之装配Bean
1.1Spring配置的可选方案 Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系.但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起.当 ...
- spring实战三装配bean之Bean的作用域以及初始化和销毁Bean
1.Bean的作用域 所有的spring bean默认都是单例.当容器分配一个Bean时,不论是通过装配还是调用容器的getBean()方法,它总是返回Bean的同一个实例.有时候需要每次请求时都获得 ...
- 《精通Spring4.X企业应用开发实战》读后感第五章(装配Bean,依赖注入)
- Spring bean依赖注入、bean的装配及相关注解
依赖注入 Spring主要提供以下两种方法用于依赖注入 基于属性Setter方法注入 基于构造方法注入 Setter方法注入 例子: public class Communication { priv ...
- 002-Spring4 快速入门-项目搭建、基于注解的开发bean,Bean创建和装配、基于注解的开发bean,Bean初始化销毁、Bean装配,注解、Bean依赖注入
一.项目搭建 1.项目创建 eclipse→project explorer→new→Project→Maven Project 默认配置即可创建项目 2.spring配置 <dependenc ...
- spring实战二之Bean的自动装配(非注解方式)
Bean的自动装配 自动装配(autowiring)有助于减少甚至消除配置<property>元素和<constructor-arg>元素,让Spring自动识别如何装配Bea ...
随机推荐
- 二叉搜索树(Binary Search Tree)
二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树. 二叉搜索树:一棵二叉树,可以为空:如果不为空,满足以下性质: 非空左子树的所有键值小于其根结点的键值: 非空右 ...
- 设置drawable图片
google官方建议在textView和imageView挨着的时候,建议使用drawable来显示图片 第一个方法:setCompoundDrawablesWithIntrinsicBounds(D ...
- Mysql Too Many Connections问题解决
MySQL的max_connections参数用来设置最大连接(用户)数.每个连接MySQL的用户均算作一个连接,max_connections的默认值为100.本文将讲解此参数的详细作用与性能影响. ...
- ANTLR3完全参考指南读书笔记[02]
前言 程序语言是什么? 用wiki上的描述,程序语言是一种人工设计的语言,用于通过指令与机器交互:程序语言是编程程序的标记,而程序是一种计算或算法的描述.详细介绍和背景信息参考: Programmin ...
- 209. Minimum Size Subarray Sum
Given an array of n positive integers and a positive integer s, find the minimal length of a subarra ...
- System.Web.UI.ScriptManager.RegisterStartupScript(语句末尾加分号,不然可能会造成语句不执行)
System.Web.UI.ScriptManager.RegisterStartupScript(this.Page, this.GetType(), "testKey", &q ...
- spark_updateStateByKey
java核心代码 JavaPairDStream<String, Integer> wordCounts = pair.updateStateByKey(new Function2< ...
- python 核心编程第5章(习题)
1.标准类型运算符. 写一段脚本,输入一个测验成绩,根据下面的标准,输出他的评分成绩(A-F). #coding:utf8 a = raw_input() a = int(a) if (a > ...
- ExtJS Panel主要配置列表
Ext.panel.Panel 属性 值 描述 animCollapse Boolean 设置面板折叠展开是否显示动画,Ext.Fx可用默认true,否则false applyTo Mixed 面板定 ...
- c++关于接口机制和不完全类型的小问题
都和typedef有关 一个是接口机制时用到的 就是所有用到接口的源文件只需包含简单的接口声明 接口的具体实现在其他源文件中实现 接口可以是 //interface.h typedef struct ...