【翻译计划】-Spring Framework Documentation(十)

416 阅读6分钟

原文链接:docs.spring.io/spring-fram…

1.10. 基于Classpath 扫描和管理bean

上述中大多数示例使用XML指定每个BeanDefinition的配置元数据。上一节(Annotation-based Container Configuration)演示了如何通过注解提供配置元数据。然而,即使在这些示例中,基本的bean定义也是在XML文件中显式定义的,而注解仅配置依赖项注入。本节描述通过扫描classpath的方式隐式的检测bean。

从Spring 3.0开始,Spring JavaConfig提供的许多特性都是Spring核心框架的一部分。这允许您使用Java注解而非传统的XML文件定义bean。您可以查看@Configuration、@Bean、@Import和@DependsOn注解,了解如何使用这些新特性。

1.10.1. @Component 和其衍生注解

@Component注解注明该类被Spring管理。Spring提供了它的衍生注解:@Controller、@Service和@Repository(分别在控制层、服务层和持久层中使用)。因此,你可以用@Component来标注你的组件类,但是,通过用@Repository、@Service或@Controller来标注变得更言简意赅。@Controller、@Service和@Repository也可以在Spring框架的未来版本中携带额外的语,因此,如果要在服务层使用@Component还是@Service之间进行选择,@Service显然是更好的选择。

1.10.2 使用元注解和复合注解

Spring提供的许多注解都可以在您自己的代码中用作元注解。元注解是可以应用于另一个注解的注解。例如,前面提到的@Service注解用到了@Component元注解,如下例所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ...
}

@Component致使@Service被以与@Component相同的方式对待

您还可以组合元注解来创建“复合注解”。例如,来自Spring MVC的@RestController注解由@Controller和@ResponseBody组成。

1.10.3 自动检测和注册Bean

Spring可以自动检测构造型类并向ApplicationContext注册相应的BeanDefinition实例。例如,以下两个类符合这种自动检测条件:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    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  {
    // ...
}

为简洁起见,上面的示例basePackages属性可以省略(即@ComponentScan("org.example"))。

效果与下例中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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>

使用context:component-scan隐式地启用了context:annotation-config的标签。

同样隐式启用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor处理器,通过包含值为false的注释配置属性,可以禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。

1.10.4 配置扫描过滤器

默认情况下,使用@Component、@Repository、@Service、@Controller、@Configuration以及使用@Component标注的自定义标注是Spring唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为:将类添加为@ComponentScan注解的includeFilters或excludeFilters属性(或在XML配置中作为context:component-scan元素的<context:include-filter />或<context:exclude-filter />子元素)中。每个过滤器元素都需要type和expression属性。过滤选项如下表所示:

Filter TypeExample ExpressionDescription
annotation (default)org.example.SomeAnnotationAn annotation to be present or meta-present at the type level in target components.
assignableorg.example.SomeClassA class (or interface) that the target components are assignable to (extend or implement).
aspectjorg.example..*Service+An AspectJ type expression to be matched by the target components.
regexorg\.example\.Default.*A regex expression to be matched by the target components' class names.
customorg.example.MyTypeFilterA custom implementation of the org.springframework.core.type.TypeFilter interface.

下面的示例表示仅扫描包含stub命名的repository类,其余用@Repository的类不被扫描:

@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、@Repository、@Service、@Controller、@RestController或@Configuration注解或衍生注解的自动检测。

1.10.5 在组件类中定义bean的元数据

Spring组件类还可以向容器提供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
    }
}

FactoryMethodComponent类是一个使用@Component修饰的bean,它的doWork()方法中包含特定于应用程序的代码。但是,它还提供了一个包含bean定义的publicInstance()的工厂方法(使用@Bean注解修饰)。publicInstance方法通过@Qualifier注解标识一个限定符值,其他可以指定的方法级注解有@Scope、@Lazy和自定义注解。

如前所述,@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);
}

}

该示例将privateInstance对象的age属性的值赋给country。 常规Spring组件中的@Bean方法的处理方式与@Configuration类中的@Bean方法存在不同:不同之处在于@Component类对@Bean注解修饰的方法没有使用CGLIB代理增强。 在普通的@Component类中调用@Bean方法中的方法具有标准的Java语义,不需要应用特殊的CGLIB处理或其他约束。

可以将@Bean方法声明为静态方法,这样就可以在包含它们的配置类没有创建为实例的情况下调用它们。这在定义后处理器(例如BeanFactoryPostProcessor或BeanPostProcessor)时特别有意义,因为这样的bean在容器生命周期的早期被初始化,应该避免在那个时候触发配置的其他部分。 对静态@Bean方法的调用从来不会被容器拦截,甚至在@Configuration类中也不会(如本节前面所述),这是由于技术限制:CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法具有标准的Java语义,导致直接从工厂方法本身返回独立的实例。

1.10.6. 自定义组件命名器

当一个bean被自动检测时,它的名称将由该扫描程序已知的BeanNameGenerator类生成。默认情况下,Spring注解(@Component、@Repository、@Service和@Controller)通过value属性提供bean名称, 如果注解中不包含任何名称值,BeanNameGenerator将返回首字母小写的非限定类名。例如,如果检测到以下bean,则名称为myMovieLister和movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}

@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果不想使用默认的BeanNameGenerator,可以提供自定义的BeanNameGenerator。首先,实现BeanNameGenerator接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描程序时提供完全限定类名,如下示例所示:

如果由于多个自动检测的组件具有相同的非限定类名(即具有相同名称但驻留在不同包中的类)而导致命名冲突,则可能需要配置一个BeanNameGenerator,默认为生成的bean名的完全限定类名。从Spring Framework 5.2.3开始,位于package org.springframework.context.annotation中的fulllyqualifiedannotationbeannamegenerator就可以用于这些目的。

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

1.10.7. 设置作用域

组件类的默认作用域是单例的。您可以使用@Scope注解指定的不同作用域。如下面的示例所示:

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