《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依赖。有三种情况:

  1. 如果只有一个bean匹配依赖需求,这个bean就会被装配
  2. 若没有匹配到bean,Spring就会抛出异常。想避免异常可做如下配置,但要注意null检查,防止未匹配到bean时的空指针异常。
    @Autowired(required = false)
    public ADHero(Enemy enemy) {
        this.enemy = enemy;
    }
  1. 若有多个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

元素类似于JavaConfig中的@Bean注解,我们用如下方式声明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遇到时会创建一个对应的实例。会将ID为ref指定的bean传递到构造器中。

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>

这次我们将元素中的ref属性替换为value,表明将值以字面量的形式注入到构造其中。

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中使用导入JavaConfig类。

使用引入其他XML配置。

posted @ 2020-08-16 23:02  当代艺术家  阅读(206)  评论(2)    收藏  举报