SpringFramework核心技术一(IOC:类路径扫描和组件管理)

本章中的大多数示例都使用XML来指定BeanDefinition在Spring容器中生成每个配置的元数据。上一节(基于注释的容器配置)演示了如何通过源代码级注释提供大量配置元数据。但是,即使在这些示例中,“基本”bean定义在XML文件中显式定义,而注释仅驱动依赖注入。
本节介绍用于隐式检测候选组件的选项通过扫描类路径。候选组件是与过滤条件相匹配的类,并具有在容器中注册的相应的bean定义。这消除了使用XML来执行bean注册的需要; 相反,您可以使用注释(例如@Component),AspectJ类型表达式或您自己的自定义过滤条件来选择哪些类将具有注册到容器的bean定义。

从Spring 3.0开始,Spring JavaConfig项目提供的许多功能都是核心Spring框架的一部分。这使您可以使用Java定义bean,而不是使用传统的XML文件。看看的@Configuration,@Bean, @Import,和@DependsOn注释有关如何使用这些新功能的例子。


一、@Component和更多的典型注解

@Repository注释是用于满足所述角色或任何类的标记 构造型的存储库(也被称为数据访问对象或DAO)的。
Spring提供进一步典型化注解:@Component@Service,和 @Controller@Component是任何Spring管理组件的通用原型。
@Repository@Service@Controller@Component更具体的用例的专门化,例如,分别在持久性,服务和表示层中。
因此,你可以用你的注解组件类 @Component,但如果用注解它们@Repository@Service或者@Controller ,你的类能更好地被工具处理,或与切面进行关联。
例如,这些刻板印象注解是切入点的理想目标。这也有可能是@Repository@Service@Controller可能会在Spring Framework的未来版本中增加额外的语义。因此,如果您选择使用@Component@Service为您的服务层,@Service显然是更好的选择。同样,如上所述,@Repository已经被支持作为持久层中自动异常转换的标记。

二、Meta-annotations(元注释)

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 {

    // ....
}

元注释也可以组合起来创建组合注释。例如,@RestController从Spring MVC的注解@Controller和 @ResponseBody两个注释组合成的。

另外,组合的注释可以可选地重新声明来自元注释的属性以允许用户定制。当您只想暴露元注释属性的子集时,这可能特别有用。例如,Spring的 @SessionScope注解将范围名称硬编码,session但仍允许自定义proxyMode。

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

    /**
     * Alias for {@link Scope#proxyMode}.
     * 

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

@SessionScope可以在没有声明的情况下使用proxyMode,如下所示:

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

或者proxyMode如下所示的重写值:

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

三、自动检测类并注册bean定义

Spring可以自动检测刻板类,并使用它注册相应的 BeanDefinitions ApplicationContext。例如,以下两个类有资格进行这种自动检测:

@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”)

以下是使用XML的替代方法


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

隐式使用的使能功能 使用时通常不需要包含元素
在JDK 9的模块路径(Jigsaw)中,Spring的类路径扫描一般按预期工作。但是,请确保您的组件类在您的module-info 描述符中导出; 如果你希望Spring调用你的类的非公共成员,确保它们是’打开’的(即使用opens声明而不是描述符中的exports 声明module-info)。

此外,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor都隐含当您使用组件扫描元件包括在内。这意味着这两个组件都是自动检测和连接在一起的 - 所有这些都没有以XML提供的任何bean配置元数据。

您可以禁用注册,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor通过包含值为的注释配置属性false。

四、使用过滤器来自定义扫描

默认情况下,类注有@Component@Repository@Service@Controller,或者本身都标注有一个自定义的注释@Component是唯一检测到的候选组件。但是,只需应用自定义过滤器即可修改和扩展此行为。将它们添加为注释的includeFilters或excludeFilters 参数@ComponentScan(或者包含元素的include-filter或exclude-filter子component-scan元素)。每个过滤器元素都需要type 和expression属性。下表介绍了过滤选项。

  • 过滤器类型
过滤器类型 示例表达式 描述
annotation (default)(注释) 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注释并使用“存根”存储库的配置。

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

使用XML的等价物


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

您还可以通过设置useDefaultFilters=false注释或提供元素use-default-filters=”false”的属性来禁用默认过滤器。这将在关闭对使用注解的类自动检测@Component,@Repository, @Service,@Controller,或@Configuration。

五、在组件中定义bean元数据

Spring组件也可以将bean定义元数据提供给容器。您可以使用与@Bean用于在@Configuration 注释类中定义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注释还可以放置在标有@Autowiredor的注入点上@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方法参数自动装入 另一个名为bean country的age属性的值privateInstance。Spring表达式语言元素通过符号定义属性的值#{ }。对于@Value 注释,表达式解析器预先配置为在解析表达式文本时查找bean名称。

从Spring Framework 4.3开始,您还可以声明类型InjectionPoint(或其更具体的子类DependencyDescriptor)的工厂方法参数, 以便访问触发创建当前bean的请求注入点。请注意,这只适用于实际创建的bean实例,而不适用于注入现有实例。因此,对于原型范围的bean来说,这个特性最有意义。对于其他作用域,factory方法只会看到触发在给定范围内创建新bean实例的注入点:例如,触发创建惰性单例bean的依赖关系。在这种情况下使用提供的注入点元数据和语义保护。

@Component
public class FactoryMethodComponent {

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

将@Bean在普通的Spring组件方法比在Spring里的同行处理方式不同@Configuration类。不同之处在于@Component CGLIB没有增强类来拦截方法和字段的调用。CGLIB代理是通过@Bean在@Configuration类中的方法中调用方法或字段来创建对协作对象的bean元数据引用的手段; 这种方法不是用普通的Java语义来调用的,而是通过容器来提供Spring bean的通常的生命周期管理和代理,即使通过对@Bean方法的编程调用来引用其他bean也是如此。相反,@Bean在普通@Component 类中的方法中调用方法或字段具有 标准的Java语义,没有特殊的CGLIB处理或其他限制条件。

您可以将@Bean方法声明为static允许在不创建其包含的配置类作为实例的情况下调用它们。定义当后处理器bean类,例如类型的这使得特定意义BeanFactoryPostProcessor或 BeanPostProcessor,因为这种bean将获得在容器生命周期的早期初始化,并且应避免在该点触发的配置的其他部分。
请注意,对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也是如此(参见上文)。这是由于技术限制:CGLIB子类只能覆盖非静态方法。因此,直接调用另一个@Bean方法将具有标准的Java语义,导致独立的实例从工厂方法本身直接返回。
方法的Java语言可见性@Bean不会立即影响Spring容器中的结果bean定义。你可以自由地声明你的工厂方法,因为你认为它适用于非@Configuration类,也适用于任何地方的静态方法。但是,类中的常规@Bean方法@Configuration需要被覆盖,即不能将其声明为private或final。
@Bean方法也将在给定组件或配置类的基类上以及组件或配置类实现的接口中声明的Java 8默认方法上发现。这为构建复杂的配置安排提供了很大的灵活性,从Spring 4.2起,通过Java 8默认方法甚至可以实现多重继承。
最后,请注意,单个类可能@Bean为同一个bean 保存多个方法,因为要根据运行时可用的依赖关系来使用多个工厂方法。这与在其他配置方案中选择“最贪婪的”构造函数或工厂方法的算法相同:在构建时将选择具有最大可满足依赖项数的变体,类似于容器如何在多个@Autowired构造函数之间进行选择。

六、命名自动检测的组件

当一个组件作为扫描进程的一部分被自动检测时,它的bean名称是由该扫描程序BeanNameGenerator已知的策略生成的。默认情况下,任何Spring刻板印象注释(@Component,@Repository,@Service,和 @Controller包含)的名字 value将因此提供的名字相应的bean定义。
如果这样的注释不包含任何名称 value或其他任何检测到的组件(例如自定义过滤器发现的组件),那么默认的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>

作为一般规则,当其他组件可能对其进行明确引用时,请考虑在注释中指定名称。另一方面,只要容器负责布线,自动生成的名称就足够了。

七、提供自动检测组件的范围

与一般的Spring管理组件一样,自动检测组件的默认和最常见的作用域是singleton。但是,有时您需要可以通过@Scope注释指定的不同范围。只需在注释中提供范围的名称即可:

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

@Scope注释仅在具体Bean类(用于注释组件)或工厂方法(用于@Bean方法)上内省。与XML bean定义相比,没有bean定义继承的概念,并且类级别的继承层次结构与元数据目的无关。

有关Web特定范围的详细信息,例如Spring请求中的“请求”/“会话”,请参阅请求,会话,应用程序和WebSocket范围。与这些范围的预构建注释一样,您也可以使用Spring的元注释方法编写自己的范围注释:例如,用元注释的自定义注释@Scope(“prototype”),也可能声明自定义范围代理模式。

要为范围解析提供自定义策略,而不是依赖基于注释的方法,请实现 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>

在使用某些非单例作用域时,可能需要为作用域对象生成代理。原因在范围bean中描述为依赖关系。为此,组件扫描元素上提供了scoped-proxy属性。三个可能的值是:no,interfaces和targetClass。例如,以下配置将导致标准的JDK动态代理:

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

八、为注释提供限定符元数据

在@Qualifier注释中讨论微调基于注解的自动连接与预选赛。该部分中的示例演示了@Qualifier在解析自动导向候选时使用注释和自定义限定符注释来提供细粒度控制。因为这些示例基于XML bean定义,所以使用XML中 元素的子元素qualifier或meta元素在候选Bean定义上提供限定符元数据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 提供其限定符元数据的变体,因为元数据是按 - 而不是每班。

九、生成候选组件的索引

虽然类路径扫描速度非常快,但通过在编译时创建候选静态列表,可以提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用这种机制,因为当 ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。
要生成索引,只需向包含组件扫描指令目标组件的每个模块添加附加依赖项即可:
Maven:

<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-context-indexerartifactId>
        <version>5.0.6.RELEASEversion>
        <optional>trueoptional>
    dependency>
dependencies>

或者,使用Gradle:

dependencies {
    compileOnly("org.springframework:spring-context-indexer:5.0.6.RELEASE")
}

该过程将生成一个META-INF/spring.components将被包含在jar中的文件。

在IDE中使用此模式时,spring-context-indexer必须将其注册为注释处理器,以确保在更新候选组件时索引是最新的。
当META-INF/spring.components在类路径上找到a时,索引会自动启用。如果索引对于某些库(或用例)部分可用,但不能为整个应用程序构建,则可以通过设置spring.index.ignore为 后退到常规的类路径安排(即根本没有索引)true,或者作为系统属性或位于spring.properties类路径根目录的文件中。

好啦,基于类路径扫描和组件管理的讲解就到这里结束啦!

你可能感兴趣的:(Spring框架)