IoC容器10—— Classpath扫描和组件管理

Classpath扫描和组件管理

本文介绍经过扫描类路径隐式检测候选组件的方法。候选组件是符合过滤条件的类而且在容器中有一个相应的bean定义。这移除了经过XML表示bean注册的须要;做为替代,可使用注解(例如@Component)、AspectJ 类型表达式、或者自定义的过滤条件来选择什么类能够在容器中注册bean定义。正则表达式

从Spring 3.0开始,Spring JavaConfig工程提供的许多功能成为Spring Framework核心的一部分。这容许使用Java定义bean而不是使用传统的XML文件。查看@Configuration、@Bean、@Import和@DependsOn注解来获取如何使用新功能的例子。算法

1 @Component和更多原型注解

@Respository注解是任何知足角色或者原型为repository(也被横位数据接入对象或DAO)类的标记。此标记的用途之一是自动翻译异常。spring

Spring提供更多的原型注解:@Component、@Service和@Controller。@Component是一个范型的原型,对任意Spring管理的组件都适用。@Repository、@Service和@Controller是@Component的特殊化,用于更具体的状况,例如,各自应用于在持久层、服务层和表现层。所以,可使用@Component注释组件类,可是使用@Respository、@Service和@Controller替代@Component注释它们,使class更适合于工具处理或者与面相关联。例如,这些原型注解是切入点的理想目标。同时,在Spring将来的发行版中@Repository、@Service和@Controller也许会携带更多的语义。所以,若是在业务层选择使用@Component仍是@Service,@Service是更好的选择。与上文所述类似,@Repository已经被支持为持久层的自动翻译异常的标记。express

2 元注解

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 @interface Service {

    // ....
}

元注解也能够组合起来建立组合注解。例如Spring MVC的@RestController注解是由@Controller和@ResponseBody组成。安全

此外,组合的注解能够从新声明元注解的属性用于用户自定义。这当你想要仅仅暴露元注解的一部分属性时十分有用。例如,Spring的@SessionScope注解硬编码做用域名为session可是仍然容许自定义proxyMode。session

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

而后可使用@SessionScope但不声明proxyMode:ide

@Service
@SessionScope
public class SessionScopedService {
    // ...
}

或者覆盖proxyMode的值以下:函数

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

3 自动检测类和注册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 {
    // implementation elided for clarity
}

为了自动检测这些类而且注册相关的bean,须要添加@ComponentScan到注释了@Configuration的类,其中basePackages属性是上面两个类共同的父包。(另外一种方法是,指定逗号/分号/空格分隔的列表包含每一个类的父包名。)

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

为了简洁,上面的例子可使用注解的value属性,即@ComponentScan("org.example")

除了将包设置为简单的String类型以外,@ComponentScan还提供了另一种方法,即将其指定为包中所包含的类或接口。

@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
	
}

basePackages属性被替换成了basePackageClasses。同时再也不使用String类型的名称指定包,使用String方法是类型不安全的。

下面的例子是使用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"
    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元素。

类路径包的扫描须要在类路径中存在相应的目录条目。当使用Ant编译JARs时,保证没有激活JAR认为的仅文件切换。(???)同时,在一些环境中基于安全策略,类路径文件夹也许不会被暴露,例如JDK 1.7.0_45或更高版本的独立应用(这须要在清单中设置“信任的库”)。

此外,但使用组件扫描元素时AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor被隐式的包含。这意味着这两个组件被自动检测并装配,全部这些都不须要在XML中提供任何bean配置元数据。

能够经过包含值为false的annotation-config属性(context:component-scan元素有,但@ComponentScan注解没有这个属性)来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。

4 使用过滤器自定义扫描

默认的,注释了@Component、@Repository、@Service、@Controller或者用户自定义的使用了@Componnet的注解的类是被检测的候选组件。然而,能够经过应用用户定义的过滤器修改和扩展这个行为。将它们添加到@ComponentScan注解的includeFilters或excludeFilters参数中(或者做为component-scan元素的include-filter或者excludeFilters子元素)。每一个过滤器元素须要type和expression属性。下面的表格描述过滤选项。

过滤器类型 表达式示例 描述
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 接口的实现类

下面的例子展现了忽略全部@Repository注解而且使用“stub”repositories做为替代:

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

等价的XML配置:

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

能够在注解上设置useDefaultFilters=false或提供use-default-filters="false"属性给<component-scan/>元素来关闭默认过滤器。这实际上会关闭对@Component、@Repository、@Service、@Contoller或@Configuration的自动检测。

5 组件中定义bean的元数据

Spring组件也能够向容器提供bean定义元数据。可使用@Bean来执行此操做,它与@Configuration注释类中用来定义bean元数据的@Bean相同。下面是一个例子:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }

}

这个类是一个Spring组件,其中doWork()方法包含面向应用的代码。然而,它也提供了一个bean定义,它具备引用publicInstance()方法的一个工厂方法。@Bean注解指定了工厂方法和其余bean定义属性,例如经过@Qualifier指定一个限定值。能够指定的其它方法级的注解有@Scope、@Lazy和自定义限定符注解。

除了初始化组件的做用,@Lazy注解也能够放置在标有@Autowired或@Inject的注入点。在这种状况下,它会致使注入一个延迟解析代理。

自动装配的字段和方法如前所述,还有对@Bean方法的自动装配的额外支持:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }

}

这个例子自动装配String类型的方法参数country为另外一个名为privateInstance的bean的Age属性。一个Spring表达式语言元素经过符号#{<expression>}定义了属性的值。对于@Value注解,预配置的表达式解析起在解析表达式文本时查找bean的名字。

在Spring Framework 4.3中,能够定义一个类型为InjectionPoint(或者它的更具体的子类DependencyDescriptor)工厂方法参数用于访问触发建立当前bean的请求注入点。须要注意的是它仅会应用于实际建立bean的实例时,而不是注入已存在的bean时。所以,该功能对于做用域为prototype的bean是最有意义的。对于其它做用域,工厂方法只会看到在给定范围内出发建立新bean实例的注入点:例如,依赖出发了一个懒加载singleton bean的建立。在这种状况下,使用提供的注入点元数据进行语义关注(???)。

@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

常规Spring组件中的@Bean方法的处理方式与Spring @Configuration类的方法不一样。不一样之处子在于,不会使用CGLIB加强@Component类来拦截方法和字段的调用。CGLIB代理是调用@Configuration 类中的@Bean方法中的方法或字段来建立b引用协做对象的ean元数据的手段;这样的方法不是用正常的Java语义来调用的,而是经过容器来提供Spring bean的生命周期和代理,即便经过对@Bean方法的编程调用来引用bean也同样。相反,在普通的@Component类的@Bean方法中调用一个方法或字段有用普通Java语义,没有特殊的CGLIB处理或其它约数。

能够将@Bean方法声明为static,容许在不建立包含它的容器配置类实例的状况下调用它们。这在定义后处理器bean(例如BeanFactoryPostProcessor或BeanPostProcessor)时十分有用,由于这样的bean将在容器生命周期的早期初始化而且应避免在此时出发配置的其它部分。

请注意,静态@Bean方法的调用永远不会被容器拦截,甚至是@Configuration类。这是因为技术限制:CGLIB字累仅能覆盖非静态方法。所以,直接调用另外一个@Bean方法会使用标准Java语义,致使直接从工厂方法返回一个独立的实例。

@Bean方法的Java语言可见性对于Spring容器中的bean定义没有直接的影响。能够随意的声明非@Configuration类的工厂方法和任何地方的静态方法。可是@Configuration类中的常规@Bean方法必须是可覆盖的,即不能将其声明为私有或fianl。

在给定的组件或配置类的基类中的@Bean方法也将会发现,同时Java 8中组件或配置类实现的接口中的@Bean方法也会被发现。这在组合复杂的配置布局方面具备很大的灵活性,甚至经过Spring4.2支持的Java 8默认方法能够实现多重继承。

最后要注意的是,一个类能够持有同一个bean的多个@Bean方法,做为根据运行时可用依赖关系使用的多个工厂方法的排列。这一个在其它场景中选择最“贪婪”的构造函数和工厂方法的算法相同:在构造时选择具备最多依赖关系的方法,相似于容器在多个@Autowired构造函数之间的选择。

6 命名自动检测组件

当组件被扫描程序自动发现,它的bean名字由扫描器已知的BeanNameGenerator策略生成。默认值,任意Spring远行注解(@Component、@Repository、@Service和@Controller)包含name值从而将这个名字提供给相应的bean定义。

若是注解不包含name值(例如由自定义过滤器发现的注解),默认的bean名字生成器返回没有限定值的首字母小写的类名。例以下面两个组件被发现,名字应为myMovieLister和movieFinderImpl:

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

若是不想使用默认的bean命名策略,能够提供自定义的bean命名策略。首先,实现BeanNameGenerator接口,而且保证有一个默认的无参数构造函数。而后配置扫描器时提供全限定类名:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

做为通常规则,只要其它组件可能对其进行显示引用,请考虑使用注解指定它的名字。另外一方面,只要容器负责自动装配,自动生成的名字就足够了。

还有另一种为bean命名的方式,这种方式不使用@Component注解,而是使用Java依赖注入规范中提供的@Named注解。Spring支持将@Named做为@Component的替代方案。在“使用JSR 330 标准注解”一章介绍。

7 为自动检测的组件提供做用域

与通常的Spring管理的组件同样,自动检测组件的默认和最多见的做用域时singleton。然而,有时须要不一样的做用域,可使用@Scope注解指定。在注解中提供做用域的名称便可:

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

为了提供自定义做用域方案能够而不是依赖基于注解的方法,实现ScopeMetadataResolver接口,而且确保它有默认无参数的构造方法。而后在配置扫描器时提供全限定类名:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
            scope-resolver="org.example.MyScopeResolver" />
</beans>

当使用某个非singleton做用域,可能须要为做用域对象生成代理(因为不一样做用域之间的依赖致使的问题)。为了这个目的,组件扫描元素有一个scoped-proxy属性。有三个有效的值:no、interface和targetClass。例如,下面的配置将使用标准JDK动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        scoped-proxy="interfaces" />
</beans>

8 使用注解提供限定值元数据

在XML配置中使用bean元素的quialifier或meta子元素提供候选bean定义的限定值元数据。当使用类路径扫描来自动检测组件,使用类型级别的注解来为候选类提供限定值元数据。下面的三个例子展现这个技术:

@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定义的多个bean,这样能够提供不一样的限定值元数据,由于元数据是每一个实例一份,而不是每一个类一份。

相关文章
相关标签/搜索