3.12. 对受管组件的Classpath扫描

到目前为止本章中的大多数例子都使用XML来指定配置元数据, 这些元数据会生成Spring容器中的每个BeanDefinition。 上一节(第 3.11 节 “基于注解(Annotation-based)的配置”)演示了如何使用代码级注解来提供大量配置元数据。 然而,即使是在那些例子中,“基础”bean定义还是显式地定义在XML文件中,注解只是用来驱动依赖注入的。 本节中会介绍一种方法,通过扫描classpath并匹配过滤器来隐式地检测候选组件 (candidate components)。

3.12.1. @Component和更多典型化注解

从Spring 2.0开始,引入了@Repository注解, 用它来标记充当储存库(又称 Data Access Object或DAO)角色或典型的类。利用这个标记可以做很多事, 其中之一就是对第 12.6.4 节 “异常转化”中描述的异常进行自动转换。

Spring 2.5引入了更多典型化注解(stereotype annotations): @Component@Service@Controller@Component是所有受Spring管理组件的通用形式; 而@Repository@Service@Controller则是@Component的细化, 用来表示更具体的用例(例如,分别对应了持久化层、服务层和表现层)。也就是说, 你能用@Component来注解你的组件类, 但如果用@Repository@Service@Controller来注解它们,你的类也许能更好地被工具处理,或与切面进行关联。 例如,这些典型化注解可以成为理想的切入点目标。当然,在Spring Framework以后的版本中, @Repository@Service@Controller也许还能携带更多语义。如此一来,如果你正在考虑服务层中是该用 @Component还是@Service, 那@Service显然是更好的选择。同样的,就像前面说的那样, @Repository已经能在持久化层中进行异常转换时被作为标记使用了。

3.12.2. 自动检测组件

Spring可以自动检测“被典型化”(stereotyped)的类,在ApplicationContext 中注册相应的BeanDefinition。例如,下面的这两个类就满足这种自动检测的要求:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

要检测这些类并注册相应的bean,需要在XML中包含以下元素,其中'basePackage'是两个类的公共父包 (或者可以用逗号分隔的列表来分别指定包含各个类的包)。

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

此外,在使用组件扫描元素时,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor会隐式地被包括进来。 也就是说,连个组件都会被自动检测织入 - 所有这一切都不需要在XML中提供任何bean配置元数据。

注意

通过加入值为'false'的annotation-config属性可以禁止注册这些后置处理器。

3.12.3. 使用过滤器自定义扫描

默认情况下,用@Component@Repository@Service@Controller (或本身使用了@Component注解的自定义注解) 注解的类是唯一会被检测到的候选组件。但是可以很方便地通过自定义过滤器来改变并扩展这一行为。 可以用'component-scan'的include-filterexclude-filter子元素来进行添加。 每个过滤器元素都要求有'type'和'expression'属性。 下面给出了四个已有的可选过滤器。

表 3.7. 过滤器类型

过滤器类型 表达式范例
annotation

org.example.SomeAnnotation

assignable

org.example.SomeClass

regex

org\.example\.Default.*

aspectj

org.example..*Service+


下面这个XML配置会忽略所有的@Repository注解并用“stub”储存库代替。

<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-filters="false" 属性来禁用默认的过滤器。这会关闭对使用了@Component@Repository@Service@Controller的类的自动检测。

3.12.4. 自动检测组件的命名

当一个组件在某个扫描过程中被自动检测到时,会根据那个扫描器的BeanNameGenerator 策略生成它的bean名称。默认情况下,任何包含name值的Spring“典型”注解 (@Component@Repository@Service@Controller) 会把那个名字提供给相关的bean定义。如果这个注解不包含name值或是其他检测到的组件 (比如被自定义过滤器发现的),默认bean名称生成器会返回小写开头的非限定(non-qualified)类名。 例如,如果发现了下面这两个组件,它们的名字会是'myMovieLister'和'movieFinderImpl':

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

注意

如果你不想使用默认bean命名策略,可以提供一个自定义的命名策略。首先实现 BeanNameGenerator 接口,确认包含了一个默认的无参数构造方法。然后在配置扫描器时提供一个全限定(fully-qualified)类名:

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

</beans>

作为一条常规,当其他组件可能会显式地引用一个组件时可以考虑用注解来指定名称。 另一方面,当容器负责织入时,自动生成的名称就足够了。

3.12.5. 为自动检测的组件提供一个作用域

通常受Spring管理的组件,默认或者最常用的作用域是“singleton”。然而,有时也会需要其他的作用域。 因此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>

当使用某些非singleton的作用域时,可能需要为这些作用域中的对象生成代理。 原因在标题为第 3.4.4.5 节 “作用域bean与依赖”的章节中已经说过了。 为了这个目的,'component-scan'元素有一个scoped-proxy属性。 三个可能值是:'no'、'interfaces'和'targetClass'。比方说,下面的配置会产生标准的JDK动态代理:

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

3.12.6. 用注解提供限定符元数据

在名为第 3.11.2 节 “基于注解的自动连接微调”的章节里引入了@Qualifier注解。 那节的例子中演示了@Qualifier注解的用法,以及如何用自定义限定符注解在自动织入解析时提供精细控制。 那些例子是基于XML bean定义的,所以限定符元数据是在XML中由'bean'元素的 'qualifier'或'meta'子元素提供。使用classpath扫描来自动检测组件时, 限定符元数据可以由候选类上的类别级(type-level)注解来提供。下面的三个例子就演示了这个技术。

@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根据限定符元数据提供多种选择, 因为元数据是由每个实例提供的,而不是每个类。