Spring IOC之Classpath扫描和管理的组件

在前面的大部分例子我们使用XML去指明配置数据去定义在Spring容器中的每一个BeanDefinition。上一节我们展示了如何在
代码层注解的方式来提供大量的配置信息。即使在这些例子中,但是,基础的Bean定义显示定义在XML文件中,与此同时注解
只是驱动了依赖注入。这节提供了一个选择去通过扫描classpath的方式来发现候选的组件Bean。候选的组件匹配了过滤器条
件的那些类并且有一个一致的Bean定义注册在容器中。这个就可以免除了使用XML去展示Bean注册,取而代之的是你可以使用
注解,Aspect类型表达式或者你自定义的过滤器条件去选择注册在容器中的有Bean定义的类。

注意:从Spring3.0开始,许多通过Spring JavaConfig工厂提供的功能是核心Spring框架的部分。这个允许你去定义Bean
使用java而不是使用传统的XML文件。来看下  @Configuration,  @Bean,  @Import,和  @DependsOn注解的例子,以及如
何使用这些功能。

@Component 和 更多的被沿用的注解

@Repository注解标记那些满足持久层(DAO)的角色的类。Spring 提供了更多的沿用的注解:@Component, @Service, 和
@Controller。@Component是一个对所有的Spring管理的组件的泛型的沿用。 @Repository, @Service,和 @Controller是@Component
的具体化。例如,相对应的在持久层,service层和表示层相。因此,你可以注解你的的组件类使用@Component注解,但是通过
@Repository, @Service, 或者@Controller 你的类在通过共建或者和切面相联系时或更加适配些。@Repository, @Service,和
@Controller 也可能携带额外的寓意在将来的Spring框架的发布版本中。因此,如果你在为你的service层的@Componentor或者 @Service
之间选择时,@Service是一个更好的选择。类似的是,正如上面所说的那样, @Repository已经支持作为一个标记在你持久层是一个
原子一事务。

元注解(Meta-annotatioins)

许多通过Spring提供的注解能够在你的代码中用作元注解。一个元注解就是一个简单地 注解,只不过它可以用在另外的一个注解上面。
例如,上面提到@Service注解就是一个一个拥有@Component主机的元注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interfaceService {
// ....
}

元注解能够组合在一起来创建组合注解。例如,Spring MVC的@RestController注解就是@Controller和@ResponseBody组合。

带有要给value(),元注解类型可以重新声明属性允许用户自定义。这个在想只是暴露源注解属性的一个子集时是很有用的。例如,下面就是
一个定义了session作用的自定义@Scope注解,但是它仍然允许自定义proxyMode。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope("session")
public @interfaceSessionScope {
ScopedProxyMode proxyMode() defaultScopedProxyMode.DEFAULT
}

自动发现类并且注册Bean定义

Spring 能够自动发现模式化的类并且使用ApplicationContext中注册相关的BeanDefination。例如,下面的类就能够被自动发现:

    @Service
public classSimpleMovieLister {
privateMovieFinder movieFinder;
@Autowired
publicSimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public classJpaMovieFinder implementsMovieFinder {
// implementation elided for clarity
}

为了自动发现这些类,并且注册相应的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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
建议:<context:component-scan>的使用就隐式的使用了<context:annotation-config>的功能。所以在使用
<context:component-scan>的时候必要使用<context:annotation-config>。

注意:classpath包的扫描需要相一致的目录路径在classpath中。当你使用ANT构建 JARS时,确保你使用文件时不只
是选择 JAR 任务的方式。

还有,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor在你使用组件扫描元素的时候就被包括进来了。这
意味着这两个组件自动发现和装配在一起,而不用任何XML中的Bean的配置数据信息。

注意:你可以不适用CommonAnnotationBeanPostProcessorare和AutowiredAnnotationBeanPostProcessor的注册通过
annotation-config的属性值FALSE。

使用过滤器自定义扫描

默认情况下,使用@Component, @Repository, @Service, @Controller或者一个自定义的注解标注的类只发现候选的组件。
但是,你可以修改和扩展这个行为通过应用自定义的过滤器。每一个过滤去元素需要type和expression属性。下面的表格表示
了可选的过滤器:

过滤器类型 例子表达式 描述
annotation(默认情况下) org.example.SomeAnnotation 在目标遵纪按在类型级别使用注解
assignable 一个目标组件可以分配的类
aspectj org.example..*Service+ 一个通过目标组件匹配的AspectJ类型的表达式
regex org\.example\.Default.* 一个通过目标组件类名匹配的正则表达式
custom org.example.MyTypeFilter 实现了org.springframework.core.type .TypeFilter接口的自定义实现

下面的例子表明了基于XML的配置忽略了所有的@Repository注解并且使用stub repositories代替。

<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>
注意:你可以通过提供  use-default-filters="false"作为<component-scan/>元素的属性来是默认
的过滤器失效。这个讲影响自动发现注解类@Component, @Repository, @Service, 或者 @Controller。

在组件内部定义Bean 元数据

Spring 组件也可以像容器提供Bean定义元数据。你可以通过用在@Configuration 主机的类的定义Bean元数据
的@Bean注解来做这件事。下面是一个简单例子:

@Component
public classFactoryMethodComponent {
@Bean
@Qualifier("public")
publicTestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}

这个类是一个在他的doWork()方法中含有应用代码的Spring组件。但是,它也有助于有一个执行方法publicInstance()
的工厂方法的Bean定义。@Bean注解标明了这个工厂方法和其他Bean定义属性,例如,通过@Qulifier注解的限定符的值。
其他的方法级别的注解能够给通过@Scope @Lazy和自定义的限定符注解指明。
注意:除了它的组件初始化的角色,@Lazy注解还可以 放置标记了在@Autowiredor @Inject的注入点。在这个上下文
中,它会产生一个lazy-resolution代理的注入。

自动装配的域和方法通过额外支持正对自动装配的@Bean方法是支持的:

@Component
public classFactoryMethodComponent {
private static inti;
@Bean
@Qualifier("public")
publicTestBean publicInstance() {
return newTestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protectedTestBean protectedInstance(
@Qualifier("public")TestBean spouse,
@Value("#{privateInstance.age}")String country) {
TestBean tb = newTestBean("protectedInstance", 1);
tb.setSpouse(tb);
tb.setCountry(country);
returntb;
}
@Bean
@Scope(BeanDefinition.SCOPE_SINGLETON)
privateTestBean privateInstance() {
return newTestBean("privateInstance", i++);
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode =ScopedProxyMode.TARGET_CLASS)
publicTestBean requestScopedInstance() {
return newTestBean("requestScopedInstance", 3);
}
}

这个例子自动装配了String的方法参数 country到名字为privateInstance的其他Bean的属性Age中。一个Spring表达式语言元素
定义了这个属性的值通过符号 #{ }。对于@Value注解,在解析表达式文本的时候一个表达式解析器被提前配置
去查看Bean的名字。

在Spring组件中@Bean方法相比在Spring @Configuration类中的他们副本处理时不一样的。不同之处在于@Component类没有使用CGLIB
增强去拦截方法和域的调用。CGLIB代理通过调用在@Configuration类的@Bean方法来调用方法和域的来创建Bean元数据引用到相关的对象;
这样的方法不会被一般的Java所调用。行成对比的是,在一个@Component类的内部的@Bean方法中调用一个方法或者域是有标准的Java语法的。

命名自动发现的组件

当一个组件在扫描过程中被自动发现了,它的Bean名字是通过扫描器的BeanNameGenerator策略来生成的。默认情况下,任何模式化的注解(
@Component, @Repository, @Service, 和 @Controller)都可以包含一个name的值,这个值将提供给相应的Bean定义的。如果这样的注解
没有包含了一个name的值,默认的Bean 名字生成器返回了不是大写的非限定的类名。例如,下面的两个组件被发现了,他们名字僵尸myMovieLister和
movieFinderImpl:

@Service("myMovieLister")
public classSimpleMovieLister {
// ...
}
@Repository
public classMovieFinderImpl implementsMovieFinder {
// ...
}
注意:如果你不想依赖默认的Bean 名字策略,你可以提供ige自定义的Bean-name策略。首先你需要实现 BeanNameGenerator接口,并且确保
包括一个默认的空的构造函数。然后在配置扫描器的时候提供完整的限定符的类名:
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator"/>
</beans>

作为一般的规则,考虑指明注解的名字因为在任何时候其他的组件可能显示的引用它。在另一方面,自动生成的名字是足够用的在任何时候容器负责装配。

为自动发现的组件提供一个作用域

一般来讲对于Spring管理的组件,默认的和大部分的作用域对于需要自动发现的组件都是单例的。但是,有时候你需要其他的作用域,这个在Spring2.5中
提供一个新的@Scope注解。简单的在注解的内部提供了作用域的名字:

@Scope("prototype")
@Repository
public classMovieFinderImpl implementsMovieFinder {
// ...
}
注意:为了提供一个自动以的作用域策略而不是依赖基于注解的方式,需要实现ScopeMetadataResolver接口,并且确信包括一个空的构造函数。然后
在配置扫描器的时候提供一个完整的限定符的类名:

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

当使用确定的非单例的作用域时,为作用域的对象生成代理可能是必要的。为了这个目的,一个scoped-proxy属性就需要了在组件扫描元素中。三种可能的值
是:no,inferfaces 和targetClass.例如,下面的配置将导致标准的JDK动态代理:

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

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

@Qualifier注解是“使用限定符的细粒度的机遇注解的自动装配”部分讲过了。里面有一个例子展示了 @Qualifier注解的使用,并且在解析自动装配候选的时候
自定义限定符注解去提供细粒度的控制。因为这些例子是在基于XML的Bean定义中的,限定符元数据通过使用在XML中的
的子元素qualifier或者meta提供在候选的Bean定义中。当使用classpath扫描来自动发现组件,你可以再候选的类中提供限定符元数据给类型级别的注解。下面就
是这个知识点的上那个例子:

  @Component
@Qualifier("Action")
public classActionMovieCatalog implementsMovieCatalog {
// ...
}
@Component
@Genre("Action")
public classActionMovieCatalog implementsMovieCatalog {
// ...
}
  @Component
@Offline
public classCachingMovieCatalog implementsMovieCatalog {}
注意:对于大部分的基于注解的可选项,记住注解元数据是和类定义自己绑定的,当XML的使用允许多个相同类型的Bean去igong在他们限定符元数据中的差异,
因为那样的元数据是按实力来提供而不是按类来提供的。
posted on 2015-01-31 16:39  叼烟斗的纤夫  阅读(551)  评论(0编辑  收藏  举报