EchoDemo's Blogs

装配Bean之自动化装配Bean

Spring从两个角度来实现自动化装配:

(1)组件扫描(component scanning):Spring会自动发现应用上下文中所创建的 bean。

(2)自动装配(autowiring):Spring自动满足 bean 之间的依赖。

1、创建可被发现的Bean

(1)创建一个接口CompactDisc:

package soundsystem;

public interface CompactDisc{
    void play();
}

(2)创建带有@Component注解的CompactDisc接口的一个实现类:

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);
    }
}
注:
@Component注解表明,该类会作为组件类,并告知Spring要为这个类创建bean。不过,组件的扫描默认是不启动的,需要显式地对Spring进行配置,从而命令它去寻找带有@Component注解的类,并为其创建bean。

(3)创建配置类:

package soundsystem;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig{
    ...
}
注:
@ComponentScan注解能够在Spring当中启动组件扫描,它会默认扫描与配置类相同的包以及这个包下的所有子包,查找带有@Component注解的类。如此一来,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。

(4)当然我们也可以使用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-4.2.xsd">

    <context:component-scan base-package="soundsystem"/>

</beans>

(5)Junit测试:

package soundsystem;

import org.junit.Test;
import static org.junit.Assert.*;
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 cdShuldNotBeNull(){
        assertNotNull(cd);
    }
}
注:
a、SpringJUnit4ClassRunner用于在测试开始的时候自动创建Spring的应用上下文。
b、@ContextConfiguration注解会告诉它需要在CDPlayerConfig中加载配置。由于CDPlayerConfig类当中包含了@ComponentScan注解、SgtPeppers当中包含了@Component注解,因此最终的应用上下文就会包含CompactDisc的bean。
c、@Autowired注解用于将CompactDisc的bean注入到测试代码当中。

2、为组件扫描的bean命名

(1)Spring上下文中所有的bean都会给定一个ID,若直接使用 @Component 注解来声明一个 bean,其bean 的名字默认为类名首字母小写。例如,如上 SgtPeppers 类的默认 bean 名称为 sgtPeppers。当然我们可以在 @Component 注解中说明此 bean 的名称。

@Component("lonelyHeartClub")
public class SgtPeppers implements CompactDisc{
    ...
}

也可以使用另外一种为bean命名的方式:

@Named("lonelyHeartClub")
public class SgtPeppers implements CompactDisc{
    ...
}
注:
@Named是Java依赖注入规范(Java Dependency Injection)中所提供的为bean设置ID的注解。Spring支持将@Named作为@Component注解的替代方案,两者之间有一些细微的差异,但在大多数场景下,它们是可以相互替换的。然由于@Named其本身并无法表明它是干什么的,因此人们常常会选择使用@Component注解来命名。

3、设置组件扫描的基础包

在之前的案列中,我们没有为 @ComponentScan注解设置任何属性,这意味着,按照默认规则,它会以配置类所在包作为基础包( base package )来扫描组件。但是有个原因会促使我们要明确的设置基础包,那就是我们想要将配置类放在单独的包中,使其与其他的应用代码区分开来。

(1)为了指定不同的包,只需要在 @ComponentScan的value属性中指明包的名称:

@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig{
    ...
}

(2)如果你想更加明确的表名你所设置的是基础包,那么你可以通过basePackages属性进行配置:

@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig{
    ...
}

(3)如果你想扫描多个包,只需要将basePackages属性的值设置为要扫描包的一个数组即可:

@Configuration
@ComponentScan(basePackages={"soundsystem","video"})
public class CDPlayerConfig{
    ...
}

(4)在上面的例子当中,basePackages属性的值是一个String类型的数组,这样配置没有问题,但却是类型不安全的,如果要重构代码的话,这些包的名字可能会被修改,从而导致包扫描出现错误,除了将包设置为简单的String类型之外,@ComponentScan 还提供了另一种方法,那就是将其指定为要扫描包中所含的类或接口。

@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig{
    ...
}
注:
你可以在包中设置一个专门用来进行包扫描的空标记接口,这样,可以避免对任何实际应用的代码进行重构后,包扫描出现错误。

4、通过为bean添加注解实现自动装配

在你的应用程序当中,如果所有的对象都是独立的,彼此之间没有任何的依赖,就像ShtPeppers的bean一样,那么你所需要的可能就是组件的扫描而已。但是很多对象会依赖其他的对象才能完成任务。如此一来,我们就需要有一种方法将组件扫描得到的bean和它们的依赖装配在一起。这就涉及到Spring的自动装配。自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean,这里借助@Autowired注解来声明自动装配。

(1)以下示例的构造器上添加了@Autowired注解,这表明当Spring创建CDPlayer bean的时候,会通过这个构造器进行实例化并传入一个CompactDisc类型的 bean。@Autowired 注解可以用在类的任何方法上。

@Component
public class CDPlayer implements MediaPlayer{
    private CompactDisc cd;

    @Autowired
    public CDPlayer(CompacrDisc cd){
        this.cd = cd;
    }

    public void play(){
        cd.play();
    }
}

(2)不管是构造器、Setter 方法还是其他方法,Spring 都会尝试满足方法参数上所声明的依赖,假如有且仅有一个bean依赖需求的话,那么这个bean就会被装填进来。如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false:

@Autowired(required=false)
public CDPlayer(CompactDisc cd){
    this.cd = cd;
}
注:
但是,把required属性设置为false时,需要谨慎对待。如果你的代码中没有进行null检查的话,这个处于未装配状态的属性可能会出现NullPointerException。

(3)如果有多个bean都能满足依赖关系的话,Spring也将会抛出一个异常,表明没有明确指定要选择那个bean进行装配。

🐶 您的支持将鼓励我继续创作 🐶
-------------本文结束感谢您的阅读-------------