4.10 类路径扫描和管理的组件

本章中的大多数示例都使用了 XML 来指定配置元数据在 Spring 的容器中生产每一个 BeanDefinition。之前的章节(4.9 节,“基于注解的容器配置”)表述了如何通过代码级 的注解来提供大量的配置信息。尽管在那些示例中,“基础的”bean 的定义都是在 XML 文 件中来明确定义的,而注解仅仅进行依赖注入。本节来说明另外一种通过扫描类路径的方式 来隐式检测候选组件。候选组件是匹配过滤条件的类库,并有在容器中注册的对应的 bean 的定义。这就可 以不用 XML 来执行 bea n 的注册了 ,那么你就 可以使用注 解(比如 @Component),AspectJ 风格的表达式,或者是你子定义的过滤条件来选择那些类会有在容 器中注册的 bean。

注意

从 Spring 3.0 开始,很多由 Spring JavaConfig 项目提供的特性作为 Spring Framework 核心 的一部分了。这就允许你使用 Java 而不是传统的 XML 文件来定义 bean 了。看一看 @Configuration,@Bean,@Import 和@DependsOn 注解的例子来使用它们的新特性。

4.10.1 @Component 和更多典型注解

在 Spring 2.0 版之后,@Repository 注解是任意满足它的角色或典型(比如熟知的数 据访问对象,DAO)库的类的标记。在这个标记的使用中,就是在 14.2.2 节,“表达式翻译” 中描述的表达式自动翻译。

Spring 2. 5 引 入 了 更 多 的 注 解 : @Component , @Service 和 @Controller 。

@Component 是对 Spring 任意管理 组件的通用刻 板。@Repository ,@Service 和 @Controller 是对更多的特定用例@Component 的专业化,比如,在持久层,服务层和 表现层。因此,你可以使用@Component 注解你的组件类,但是使用@Repository, @Service 或@Controller 注解来替代的话,那么你的类更合适由工具来处理或和不同的方面相关联。比如,这些刻板注解使得理想化的目标称为切入点。而且@Repository, @Service 和@Controller 也可以在将来 Spring Framework 的发布中携带更多的语义。因此,如果对于服务层,你在@Component 或@Service 中间选择的话,那么@Service 无 疑是更好的选择。相似地,正如上面提到的,在持久层中,@Repository 已经作为自动异 常翻译的表示所被支持

4.10.2 自动检测类和 bean 的注册

Spring 可 以 自 动 检 测 刻 板 类 并 在 ApplicationContext 中 注 册 对 应 的BeanDefinition。比如,下面的两个类就是自动检测的例子:

@Service
public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    //为了清晰省略了实现
}

要 自 动检 测这 些类 并注 册对 应的 bean ,你 需要 包含 如下 的 XML 元素 ,其 中 的 base-package 元素通常是这两个类的的父包。(也就是说,你可以指定以逗号分隔的列表来 为每个类引入父包。)

<?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="org.example"/>
</beans>

注意

对类路径包的扫描需要存在类路径下对应的目录。当你使用 Ant 来构建 JAR 包时,要保 证你没有激活 JAR 目标中的 files-only 开关。

此 外 , 当 你 使 用 component-scan ( 组 件 - 扫 描 , 译 者 注 ) 时 , AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 二者都是隐式包含的。这就意味着两个组件 被自动检测之后就装配在一起了-而不需要在 XML 中提供其它任何 bean 的配置元数据。

注意

你 可 以 将 annotation-config 属 性 置 为 false 来 关 闭 AutowiredAnnotationBeanPostProcessor 的 CommonAnnotationBeanPostProcessor 注册。

4.10.3 使用过滤器来自定义扫描

默认情况下,使用@Component,@Repository,@Service,@Controller 注解 或使用了进行自定义的@Component 注解的类本身仅仅检测候选组件。你可以修改并扩展 这 种 行 为, 仅仅 应 用自 定 义的 过 滤器 就 可以 了。 在 component-scan 元素中添加 include-filter 或 exclude-filter 子元素就可以了。每个过滤器元素需要 type 和 expression 属性。下面的表格描述了过滤选项。

表 4.5 过滤器类型

| 过滤器类型 | 表达式示例 | 描述 | | annotation(注解) | org.example.SomeAnnotation | 在目标组件的类型层表示的注解 | | assignable(分配) | org.example.SomeClass | 目标组件分配去(扩展/实现)的类(接口) | | aspectj | org.example..Service+ | AspectJ 类型表达式来匹配目标组件 | | regex(正则表达式) | org.example.Default. | 正则表达式来匹配目标组件类的名称 | | custom(自定义) | org.example.MyTypeFilter | 自 定 义 org.springframework.core.type.TypeFilter 接口的实现类 |

下面的示例代码展示了 XML 配置忽略所有@Repository 注解并使用“sub”库来替代。

<beans>
    <context:component-scan base-package="org.example">
    <context:include-filter type="regex" expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
        expression="org.springframework.stereotype.Repository
        "/>
    </context:component-scan>
</beans>

注意

你可以使用<component-scan/>元素中的 use-default-filter="false"属性来关闭默认的过滤 器。这会导致关闭使用@Component,@Repository,@Service 或@Controller 注解 的类的自动检测。

4.10.4 使用组件定义 bean 的元数据

Spring 组件可以为容器提供 bea n 定义的元数据。你可以使用用于定义 bea n 元数据的@Configuration 注解的类的@Bean 注解。这里有一个示例:

@Component
public class FactoryMethodComponent {
    @Bean @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }
    public void doWork() {
        //忽略组件方法的实现
    }
}

这个类在 Spring 的组件中有包含在它的 doWork()方法中的特定应用代码。它也提供 了 bean 的定义并且有工厂方法来指向 publicInstance()方法。@Bean 注解标识了工厂 方法和其它 bean 定义的属性,比如通过@Qualifier 注解表示的限定符。其它方法级的注 解可以用于特定的是@Scope,@Lazy 和自定义限定符注解。自动装配字段和方法也是支持 的,这在之前讨论过,而且还有对自动装配@Bean 方法的支持:

@Component
public class FactoryMethodComponent {
    private static int i;
    @Bean @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }
    // 使用自定义标识符并自动装配方法参数
    @Bean
    protected TestBean protectedInstance(@Qualifier( "public") TestBean spouse,@Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1); tb.setSpouse(tb);
        tb.setCountry(country);
        return tb;
    }
    @Bean @Scope(BeanDefinition.SCOPE_SINGLETON)
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }
    @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

这个示例为另外一个名为 privateInstance 的 bea n 的 Age 属性自动装配了 String 方法参数 country。Spring 的表达式语言元素通过#{<expression>}表示定义了属性的 值。对于@Value 注解,当解析表达式文本时,表达式解析器会预先配置来查看 bean 的名 称。

Spring 组件中的@Bean 方法会被不同方式来处理,而不会像 Spring 中@Configuration 的类那样。不同的是@Component 类没有使用 CGLIB 来加强并拦截字段和方法的调用。CGLIB 代理是调用@Configuration 类和创建 bean 元数据引用协作对象的@Bean 方法调用的手 段。方法没有使用通常的 Java 语义来调用。相比之下,使用@Component 类@Bean 方法来 调用方法或字段有标准的 Java 语义。

4.10.5 命名自动检测组件

当 组 件 被 自 动 检 测 作 为 扫 描 进 程 的 一 部 分 时 , 它 的 bean 名 称 是 由 BeanNameGenerator 策略来生成并告知扫描器的。默认情况下, Spring 的刻板注解 (@Component,@Repository,@Service 和@Controller)包含 name 值从而提供对应 bean 定义的名称。 注意 JSR 330 的@Named 注解可以被用于检测组件和为它们提供名称的手段。如果在类路径 中有 JSR 330 的 JAR 包的话,这种行为会被自动开启。

如果注解包含 name 值或者对于其它任意被检测的组件(比如那些被自定义过滤器发现 的),默认的 bean 的名称生成器返回未大写的非限定符类名。比如,如果下面的两个组件被检测到了,那么名称可能是 myMovieLister 和 movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

注意

如果你不想使用默认的 bean 命名策略,你可以提供自定义的 bean 命名策略。首先, 实现 BeanNameGenerator 接口,要保证包含默认的没有参数的构造方法。之后,在配置 扫描器时,要提供类的完全限定名。

<beans>
    <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" />
</beans>

作为通用的规则,要考虑使用注解指定名称时,其它组件可能会有对它的明确的引用。 另一方面,当容器负责装配时,自动生成名称是可行的。

4.10.6 为自动检测组件提供范围

一般情况下,Spring 管理的组件,自动检测组件的默认和最多使用的范围是单例范围。 然而,有事你需要其它范围,Spring 2.5 提供了一个新的@Scope 注解。仅仅使用注解提供 范围的名称:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

注意

为范围决策提供自定义策略而不是基于注解的方式,实现 ScopeMetadataResolver 接口,并保证包含了默认的无参构造方法。之后,当配置扫描器时,要提供类的完全限定名:

<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver" />
</beans>

当使用特定反而非单例范围时,它可能必须要为有范围的对象生成代理。这个原因在 4.5.4.5 节,“各种范围的 bean 作为依赖”中描述过了。出于这样的目的,在 component-scan 元素中可以使用 scoped-proxy 属性。三种可能的值是:no(无),interface(接口)和 targetClass(目标类)。比如,下面的配置就会启动标准的 JDK 动态代理:

<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces" />
</beans>

4.10.7 使用注解提供限定符元数据

@Qualifier 注解在 4.9.3 节,“使用限定符来微调基于注解的自动装配”中讨论过了。 那部分中的示例说明了@Qualifier 注解的使用和当你需要处理自动装配候选者时,自定 义限定符注解来提供微调控制。因为那些示例是基于 XML 的 bean 定义的,限定符元数据在 候选者 bean 定义中提供,并使用了 XML 中的 bean 元素的 qualifier 和 meta 子元素。 当对自动检测组件使用基于类路径扫描时,你可以在候选者类中使用类型-级的注解提供限 定符元数据。下面的三个示例就展示了这个技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

注意

对于大多数基于注解的方式,要记得注解元数据会绑定到类定义本身中去,而使用 XML 就允许对多个相同类型的 bean 在它们的限定符元数据中提供变化,因为那些元数据是对于 每个实例而不是每个类提供的。