2.2.1 创建可被发现的 bean

在这个 MP3 和流式媒体音乐的时代,CD(compact disc)显得有点典雅甚至陈旧。它不像卡带机、八轨磁带、塑胶唱片那么普遍,随着以物理载体进行音乐交付的方式越来越少,CD 也变得越来越稀少了。

尽管如此,CD 为我们阐述 DI 如何运行提供了一个很好的样例。如果你不将 CD 插入(注入)到 CD 播放器中,那么 CD 播放器其实是没有太大用处的。所以,可以这样说,CD 播放器依赖于 CD 才能完成它的使 命。

为了在 Spring 中阐述这个例子,让我们首先在 Java 中建立 CD 的概念。以下展现了 CompactDisc,它是定义 CD 的一个接口:

CompactDisc.java
package soundsystem;

public interface CompactDisc {
  void play();
}

CompactDisc 的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了 CD 播放器对一盘 CD 所能进行的操作。它将 CD 播放器的任意实现与 CD 本身的耦合降低到了最小的程度。

我们还需要一个 CompactDisc 的实现,实际上,我们可以有 CompactDisc 接口的多个实现。在本例中,我们首先会创建其中的一个实现,带有 @Component 注解的 CompactDisc 实现类 SgtPeppers:

SgtPeppers.java
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。没有必要显式配置 SgtPeppersbean,因为这个类使用了 @Component 注解,所以 Spring 会为你把事情处理妥当。

不过,组件扫描默认是不启用的。我们还需要显式配置一下 Spring, 从而命令它去寻找带有 @Component 注解的类,并为其创建 bean。以下的配置类展现了完成这项任务的最简洁配置,@ComponentScan 注解启用了组件扫描:

CDPlayerConfig.java
package soundsystem;

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

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}

类 CDPlayerConfig 通过 Java 代码定义了 Spring 的装配规则。在 2.3 节中,我们还会更为详细地介绍基于 Java 的 Spring 配置。不过,现在我们只需观察一下 CDPlayerConfig 类并没有显式地声明任何 bean,只不过它使用了 @ComponentScan 注解,这个注解能够在 Spring 中启用组件扫描。

如果没有其他配置的话,@ComponentScan 默认会扫描与配置类相同的包。因为 CDPlayerConfig 类位于 soundsystem 包中,因此 Spring 将会扫描这个包以及这个包下的所有子包,查找带有 @Component 注解的类。这样的话,就能发现 CompactDisc,并且会在 Spring 中自动为其创建一个 bean。

如果你更倾向于使用 XML 来启用组件扫描的话,那么可以使用 Spring context 命名空间的元素。以下展示了启用组件扫描的最简洁 XML 配置:

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"
  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>

尽管我们可以通过 XML 的方案来启用组件扫描,但是在后面的讨论中,我更多的还是会使用基于Java 的配置。如果你更喜欢 XML 的话,元素会有与 @ComponentScan 注解相对应的属性和子元素。

可能有点让人难以置信,我们只创建了两个类,就能对功能进行一番尝试了。为了测试组件扫描的功能,我们创建一个简单的 JUnit 测试,它会创建 Spring 上下文,并判断 CompactDisc 是不是真的创建出来 了。CDPlayerTest 就是用来完成这项任务的,测试组件扫描能够发现 CompactDisc:

CDPlayerTest.java
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 {

  @Autowired
  private CompactDisc cd;
  
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd);
  }
  
}

CDPlayerTest 使用了 Spring 的 SpringJUnit4ClassRunner,以便在测试开始的时候自动创建 Spring 的应用上下文。注解 @ContextConfiguration 会告诉它需要在 CDPlayerConfig 中加载配置。因为 CDPlayerConfig 类中包含了 @ComponentScan,因此最终的应用上下文中应该包含 CompactDiscbean。

为了证明这一点,在测试代码中有一个 CompactDisc 类型的属性,并且这个属性带有 @Autowired 注解,以便于将 CompactDiscbean 注入到测试代码之中(稍后,我会讨论 @Autowired)。最后,会有一个简单的测试方法断言 cd 属性不为 null。如果它不为 null 的话,就意味着 Spring 能够发现 CompactDisc 类,自动在 Spring 上下文中将其创建为 bean 并将其注入到测试代码之中。

这个代码应该能够通过测试,并以测试成功的颜色显示(在你的测试运行器中,或许会希望出现绿色)。你第一个简单的组件扫描练习就成功了!尽管我们只用它创建了一个 bean,但同样是这么少的配置能够用来发现和创建任意数量的 bean。在 soundsystem 包及其子包中,所有带有 @Component 注解的类都会创建为 bean。只添加一行 @ComponentScan 注解就能自动创建无数个 bean,这种权衡还是很划算的。

现在,我们会更加深入地探讨 @ComponentScan 和 @Component, 看一下使用组件扫描还能做些什么。

Last updated