《Spring实战》学习笔记(2)——装配Bean
这次开始详细学习Spring装配Bean的相关知识。
本章知识点:
- 声明bean
- 构造器注入与setter方法注入
- 装配bean
- 控制bean的创建于销毁
一、装备Bean可选方案:
Spring容器负责创建应用程序中的bean并通过依赖注入(DI)来协调对象间的关系。Spring主要提供三种方式来进行装配:
- 隐藏的bean发现机制和自动装配
- 在XML中显式配置
- 在Java中显式配置
以上三种方案也可以相互搭配使用。建议尽可能多地使用自动装配,尽量少使用显式配置。
二、自动化装配bean
自动化装配需要两个角度实现:
- 组件扫描(component scaning):Spring会自动发现上下文中创建的bean
- 自动装配(autowiring):Spring会自动满足bean之间的依赖
1. 创建可被发现的bean
用以下几个类做例子:
+---java
| +---broadcast
| | Broadcast.java 广播
| |
| +---enemy
| | ArtilleryCorps.java 炮车兵
| | Enemy.java 敌人接口
| | MeleeCreeps.java 近战兵
| | RemoteSoldier.java 远程兵
| | EnemyConfig.java 敌人配置类
| |
| \---hero
| ADHero.java AD英雄
| APHero.java AP英雄
| Hero.java 英雄接口
| HeroConfig.java
|
\---resources
applicationContext.xml
首先定义Enemy接口,Enemy接口定义了Hero对一个Enemy实现所能进行的操作(英雄可以击杀敌人):
public interface Enemy {
void dead();
}
接下来创建一个Enemy的实现:
@Component
public class MeleeCreeps implements Enemy {
public void dead() {
System.out.println("近战兵被击杀");
}
}
@Component注解的意思是MeleeCreeps这个类会作为组件类并告诉Spring要为这个类创建bean。
但是组件扫描默认是不启用的,所以如果我们不配置Spring,告诉它去寻找有@Component注解的类是不行的,所以要加入一个配置类:
@Configuration
@ComponentScan
public class EnemyConfig {
}
@Configuration说明EnemyConfig是一个配置类。
@ComponentScan说明在Spring中启用组件扫描。
也可以通过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="Enemy"/>
</beans>
接下来写一个测试类来验证Spring是否开启了组件扫描并为我们创建了bean。我们在包Enemy下创建测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = EnemyConfig.class)
public class ComponentScanTest {
@Autowired
private MeleeCreeps meleeCreeps;
@Test
public void meleeCreepsNotNull() {
assertNotNull(meleeCreeps);
}
}
运行结果为绿色说明meleeCreeps已经被成功创建并被注入进来。

2. 为bean命名
Spring会根据类名为bean指定一个ID,ID为将类名的第一个首字母变为小写。如上个例子中的bean的ID为meleeCreeps。若想设置不同的ID,将值传给@Component即可。
@Component("customizeID")
public class MeleeCreeps implements Enemy {
public void dead() {
System.out.println("近战兵被击杀");
}
}
或用@Name代替@Component,但不常用。
3. 设置组件扫描基础包
@ComponentScan注解同样可以传入参数,它表示要扫描包是哪个。默认为配置类所在的包所谓基础包来扫描,这就是为什么之前的例子没有给@ComponentScan传入参数也好用的原因,因为EnemyConfig和MeleeCreeps类都在包enemy下。
传入参数的写法如下:
@Configuration
@ComponentScan("enemy") //或 @ComponentScan(basePackages = "enemy")
public class EnemyConfig {
}
basePackages表名可扫描的包不止一个,扫描多个就传入数组,写为@ComponentScan(basePackages = {"package1","package2"})这种形式。
上面这种方式,参数以String类型传入,虽然好用但类型不安全,如果重构代码,很可能就会出现错误。我们可以用第二种方式代替。
@Configuration
@ComponentScan(basePackageClasses = Enemy.class)
public class EnemyConfig {
}
将basePackages替换为basePackageClasses,参数指定为包中的类或接口。同样也可以传入多个参数,写为@ComponentScan(basePackageClasses = {C1.class,C2.class})这种形式。
4. 为bean添加注解实现自动装配
自动装配就是让Spring自动满足bean依赖的一种方法。我们使用@Autowired注解实现自动装配。
@Component
public class ADHero implements Hero {
private Enemy enemy;
@Autowired
public ADHero(Enemy enemy) {
this.enemy = enemy;
}
public void killEnemy() {
enemy.dead();
}
}
在ADHero的构造方法上加了@Autowired注解,当Spring创建ADHero bean的时候,会通过构造器传入一个可设置给Enemy类型的bean。这种方式叫构造器注入。在这个例子中,传入的bean为meleeCreeps。
我们也可以将@Autowired注解加载setter方法上:
@Autowired
public void setEnemy(Enemy enemy) {
this.enemy = enemy;
}
或任意方法上:
@Autowired
public void insertEnemy(Enemy enemy) {
this.enemy = enemy;
}
不论是那种方式,Spring都会尝试满足bean依赖。有三种情况:
- 如果只有一个bean匹配依赖需求,这个bean就会被装配。
- 若没有匹配到bean,Spring就会抛出异常。想避免异常可做如下配置,但要注意null检查,防止未匹配到bean时的空指针异常。
@Autowired(required = false)
public ADHero(Enemy enemy) {
this.enemy = enemy;
}
- 若有多个bean满足依赖关系,Spring也会抛出异常,说明没有指定要选择具体哪个bean进行装配。
@Autowired也可替换为@Inject。
5. 验证自动装配
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {EnemyConfig.class, HeroConfig.class})
public class AutowiredTest {
@Autowired
private Enemy enemy;
@Autowired
private ADHero adHero;
@Test
public void meleeCreepsNotNull() {
Assert.assertNotNull(enemy);
}
@Test
public void kill() {
adHero.killEnemy();
}
}

输出结果:
近战兵被击杀
三、通过Java代码装配bean
虽然自动装配是更为推荐的方式,但有时没办法使用这种方式。比如想要将第三方库中的组件装配到自己的应用中,这时我们没办法在第三方库中的类上添加@Component注解,所以就需要显示配置的方式。
1. 创建配置类
@Configuration
//@ComponentScan(basePackageClasses = Hero.class)
public class HeroConfig {
}
@Configuration注解表明HeroConfig是一个配置类,这里应包含Spring如何创建bean的细节。因为是显示配置,所以我们不用@ComponentScan这个注解,先将它注释掉。
2. 创建bean
@Configuration
//@ComponentScan(basePackageClasses = Hero.class)
public class HeroConfig {
@Bean
public Enemy remoteSoldier(){
return new RemoteSoldier();
}
}
@Bean注解表明此方法会返回一个对象,并且这个对象要注册为Spring上下文中的bean。bean的ID默认与方法名一致。可用@Bean(name = "customizeID")指定不同的ID。
3. 使用JavaConfig实现注入
在JavaConfig中,引用创建bean的方法就可以进行bean装配。
public class APHero implements Hero {
private Enemy enemy;
public APHero(Enemy enemy) {
this.enemy = enemy;
}
public void killEnemy() {
enemy.dead();
}
}
@Configuration
public class HeroConfig {
@Bean
public RemoteSoldier remoteSoldier(){
return new RemoteSoldier();
}
@Bean
public Hero apHero(){
return new APHero(remoteSoldier());
}
}
我们将RemoteSoldier注入到ApHero中。看起来貌似是通过调用remoteSoldier()方法,使得返回实例对象传入到APHero的构造方法中,但实际上,Spring将会拦截对所有添加@Bean注解方法的调用,并直接返回该方法所创建的bean,而不是每一次都进行实际方法调用。
4. 验证装配
@Configuration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HeroConfig.class)
public class HeroConfig {
@Bean
public RemoteSoldier remoteSoldier(){
return new RemoteSoldier();
}
@Bean
public Hero apHero(){
return new APHero(remoteSoldier());
}
@Test
public void remoteSoldierNotNull() {
Assert.assertNotNull(remoteSoldier());
}
@Test
public void killTest() {
apHero().killEnemy();
}
}

输出:
远程兵被击杀
四、通过XML装配bean
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">
</beans>
2. 声明bean
<bean id="artilleryCorps" class="enemy.ArtilleryCorps"/>
如果不指定id,那么这个bean的ID为enemy.ArtilleryCorps#0,如果还有另外一个ArtilleryCorps的bean也没有指定id,那么这个ID为enemy.ArtilleryCorps#1。
3. XML构造器注入
XML中主要有两种注入的配置方案:
元素 - c-命名空间
(1). 构造器注入bean
<constructor-arg>方式
我们新增加一个AssistHero(辅助英雄)类。
public class AssistHero implements Hero {
private Enemy enemy;
public AssistHero(Enemy enemy) {
this.enemy = enemy;
}
public void killEnemy() {
enemy.dead();
}
}
<bean id="assistHero" class="hero.AssistHero">
<constructor-arg ref="artilleryCorps"/>
</bean>
Spring遇到
c-命名空间方式
<bean id="assistHero" class="hero.AssistHero" c:enemy-ref="artilleryCorps" />
c:enemy-ref="artilleryCorps" 中:
c:c-命名空间前缀
enemy:构造器参数名
ref:注入bean引用
artilleryCorps:要注入bean的ID
可以用参数在整个参数列表中的位置信息来替代参数名
<bean id="assistHero" class="hero.AssistHero" c:_0-ref="artilleryCorps" />
因为构造器参数只有一个,所以可以不用标注位置信息
<bean id="assistHero" class="hero.AssistHero" c:_-ref="artilleryCorps" />
(2). 将字面量注入到构造器中
我们将AssistHero修改一下
public class AssistHero implements Hero {
private String heroName;
private String enemyHeroName;
public AssistHero(String heroName, String enemyHeroName) {
this.heroName = heroName;
this.enemyHeroName = enemyHeroName;
}
public void killEnemy() {
System.out.println("我方英雄" + heroName + "击杀了敌方英雄" + enemyHeroName);
}
}
<constructor-arg>方式
<bean id="assistHero" class="hero.AssistHero">
<constructor-arg value="锤石"></constructor-arg>
<constructor-arg value="EZ"></constructor-arg>
</bean>
这次我们将
c-命名空间方式
<bean id="assistHero" class="hero.AssistHero" c:heroName="锤石" c:enemyHeroName="EZ"/>
也可以改为通过参数索引的方式
<bean id="assistHero" class="hero.AssistHero" c:_0="锤石" c:_1="EZ"/>
(3). 装配集合
我们为辅助类再增加一个装备变量。
public class AssistHero implements Hero {
private String heroName;
private String enemyHeroName;
private List<String> equipList;
public AssistHero(String heroName, String enemyHeroName,List<String> equipList) {
this.heroName = heroName;
this.enemyHeroName = enemyHeroName;
this.equipList = equipList;
}
public void killEnemy() {
System.out.println("我方英雄" + heroName + "击杀了敌方英雄" + enemyHeroName);
}
public void LookequipList() {
equipList.stream().forEach(item -> System.out.println(item));
}
}
<constructor-arg>方式
<bean id="assistHero" class="hero.AssistHero">
<constructor-arg value="锤石"></constructor-arg>
<constructor-arg value="EZ"></constructor-arg>
<constructor-arg>
<list>
<value>明朗之靴</value>
<value>救赎</value>
<value>石像鬼板甲</value>
</list>
</constructor-arg>
</bean>
如果构造器参数类型是Set时,将改为
c-命名空间方式
目前,c-命名空间无法装配集合。
4. XML属性注入
XML中主要有两种属性注入的配置方案:
元素 - p-命名空间
(1). 属性注入bean
<property>方式
之前的例子都是使用构造器注入,接下来我们看一下如何使用XML实现属性注入。
我们将APHero修改一下:
public class APHero implements Hero {
private Enemy enemy;
public void setEnemy(Enemy enemy) {
this.enemy = enemy;
}
public void killEnemy() {
enemy.dead();
}
}
<bean id="artilleryCorps" class="enemy.ArtilleryCorps"/>
<bean id="apHero" class="hero.APHero">
<property name="enemy" ref="artilleryCorps"></property>
</bean>
与构造器注入类似,我们将
p-命名空间方式
还可以使用P-命名空间的方式装配。
<bean id="artilleryCorps" class="enemy.ArtilleryCorps"/>
<bean id="apHero" class="hero.APHero" p:enemy-ref="artilleryCorps"/>
p:enemy-ref="artilleryCorps" 中:
p:p-命名空间前缀
enemy:属性名
ref:注入bean引用
artilleryCorps:要注入bean的ID
(2). 字面量注入属性
修改辅助类的代码:
public class AssistHero implements Hero {
private String heroName;
private String enemyHeroName;
private List<String> equipList;
public void setHeroName(String heroName) {
this.heroName = heroName;
}
public void setEnemyHeroName(String enemyHeroName) {
this.enemyHeroName = enemyHeroName;
}
public void setEquipList(List<String> equipList) {
this.equipList = equipList;
}
public void killEnemy() {
System.out.println("我方英雄" + heroName + "击杀了敌方英雄" + enemyHeroName);
}
public void LookequipList() {
equipList.stream().forEach(item -> System.out.println(item));
}
}
<property>方式
<bean id="assistHero" class="hero.AssistHero">
<property name="heroName" value="锤石"></property>
<property name="enemyHeroName" value="EZ"></property>
<property name="equipList">
<list>
<value>明朗之靴</value>
<value>救赎</value>
<value>石像鬼板甲</value>
</list>
</property>
</bean>
p-命名空间方式
<bean id="assistHero" class="hero.AssistHero" p:heroName="锤石" p:enemyHeroName="EZ">
<property name="equipList">
<list>
<value>明朗之靴</value>
<value>救赎</value>
<value>石像鬼板甲</value>
</list>
</property>
</bean>
与c-命名空间同理,是装配bean还是装配字面量取决于属性是否带有-ref后缀。注意:p-命名空间也不能装配集合。但可以用util:list创建列表的bean。
<util:list id="equipList">
<value>明朗之靴</value>
<value>救赎</value>
<value>石像鬼板甲</value>
</util:list>
<bean id="assistHero" class="hero.AssistHero" p:heroName="锤石" p:enemyHeroName="EZ" p:equipList-ref="equipList"/>
五、导入和混合配置
1. JavaConfig中引用XML配置
在JavaConfig类中使用@Import(xxClassName.class)导入其他配置类。
使用@ImportResource("classpath:xxspringConfigName.xml")导入Spring的XML配置文件。
2. JavaConfig中引用XML配置
在XML中使用
使用

浙公网安备 33010602011771号