企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 4.10 类路径扫描和管理的组件 本章中的大多数示例都使用了 XML 来指定配置元数据在 Spring 的容器中生产每一个 BeanDefinition。之前的章节(4.9 节,“基于注解的容器配置”)表述了如何通过代码级 的注解来提供大量的配置信息。尽管在那些示例中,“基础的”bean 的定义都是在 XML 文 件中来明确定义的,而注解仅仅进行依赖注入。本节来说明另外一种通过扫描类路径的方式 来隐式检测候选组件。候选组件是匹配过滤条件的类库,并有在容器中注册的对应的 bean 的定义。这就可 以不用 XML 来执行 bea n 的注册了 ,那么你就 可以使用注 解(比如 @Component),AspectJ 风格的表达式,或者是你子定义的过滤条件来选择那些类会有在容 器中注册的 bean。 > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 从 Spring 3.0 开始,很多由 [Spring JavaConfig](http://www.springsource.org/javaconfig) [项目](http://www.springsource.org/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> ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 对类路径包的扫描需要存在类路径下对应的目录。当你使用 Ant 来构建 JAR 包时,要保 证你没有激活 JAR 目标中的 files-only 开关。 此 外 , 当 你 使 用 component-scan ( 组 件 - 扫 描 , 译 者 注 ) 时 , AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 二者都是隐式包含的。这就意味着两个组件 被自动检测之后就装配在一起了-而不需要在 XML 中提供其它任何 bean 的配置元数据。 > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 你 可 以 将 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> ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 你可以使用`<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 { // ... } ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 如果你不想使用默认的 bean 命名策略,你可以提供自定义的 bean 命名策略。首先, 实现 [BeanNameGenerator](http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/beans/factory/support/BeanNameGenerator.html) 接口,要保证包含默认的没有参数的构造方法。之后,在配置 扫描器时,要提供类的完全限定名。 ``` <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 { // ... } ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 为范围决策提供自定义策略而不是基于注解的方式,实现 [ScopeMetadataResolver](http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/context/annotation/ScopeMetadataResolver.html) 接口,并保证包含了默认的无参构造方法。之后,当配置扫描器时,要提供类的完全限定名: ``` <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 { // ... } ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 对于大多数基于注解的方式,要记得注解元数据会绑定到类定义本身中去,而使用 XML 就允许对多个相同类型的 bean 在它们的限定符元数据中提供变化,因为那些元数据是对于 每个实例而不是每个类提供的。