Classpath扫描和管理组件

用注解让Spring自动扫描包内的配置。

@Component和类似的注解:@Repository用于Dao层,@Component用于Spring管理的通用的组件。
@Repository@Service,和@Controller都是特别版的@Component,用于特定用途。

比如:@Service注解是由@Component注解和元注解定义出来的:

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

    // ...
}

@RestController是由@Controller@ResponseBody组合成的。

自动探测类和注册bean:

@Configuration修饰的类添加@ComponentScan注解。
有一个basePackages属性,可以设置扫描的包范围,一般是类的父包。
也可以使用逗号或者分号指定扫描多个包。

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

在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>,
所以不需要添加<context:annotation-config>。
AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor也被隐式包含。

使用过滤器定制扫描行为
(MyBatis的拦截器也会用到类似的)

使用@ComponentScan注解的includeFiltersexcludeFilters属性添加过滤器,
过滤器有两个属性,type和pattern。

过滤器类型:

Filter TypeExampleDescription
FilterType.ANNOTATIONorg.example.SomeAnnotation匹配被注解的组件
FilterType.ASSIGNABLE_TYPEorg.example.SomeClass类和它extends和implements的类
FilterType.ASPECTJorg.example..*Service+匹配这个切面
FilterType.REGEXorg\.example\.Default.*匹配正则表达式
FilterType.CUSTOMorg.example.MyTypeFilter实现org.springframework.core.type.TypeFilter的类

使用例子:

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

可以关闭默认的过滤器以禁止自动探测
@Component@Repository@Service@Controller@RestController@Configuration
修饰的类:

@ComponentScan(useDefaultFilters=false)或:
<component-scan use-default-filters=”false”/>

在组件里定义bean

定义一个类,用
@Component@Repository@Service@Controller@RestController@Configuration
修饰它,然后在它里面用@Bean修饰返回对象的方法,即可自动将对象注册成bean:

@Component
public class FactoryMethodComponent {

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

    public void doWork() {
        // 组件内的方法会被忽略
    }
}

还可以将@Bean@Qualifier@Scope@Lazy等修饰bean的注解组合使用。
这种生成bean的方法也称作工厂方法

从Spring4.3版本开始,可以将InjectionPoint或者它的更详细的子类DependencyDescriptor类型作为工厂方法的参数。用于访问创建当前bean的注入点,而不是注入现有的bean。用于生成prototype的bean。
对于别的作用域的bean,只在创建当前bean的时候可以看见注入点。

用法:

@Component
public class FactoryMethodComponent {

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

@Component内的@bean方法和在@Configuration里的不太一样。
@Component类没有用CGLIB代理拦截方法调用或者域(变量)。

CGLIB是调用@Configuration 类中@Bean方法内的方法或变量的一种方式,
是创建bean元数据引用给其协作对象的途径。
这些方法不是通过普通的Java语法调用,而是通过容器这一途径,用以提供生命周期管理和Bean代理,
就算主动调用别的@Bean方法也是一样。

作为对比,@Component类中的@Bean方法或变量调用就是标准的Java语法。

声明静态@Bean方法可以在不创建它们的容器类的情况下被调用,比如定义各种
BeanFactoryPostProcessorBeanPostProcessor

调用静态@Bean方法不会被容器拦截,这是因为技术限制:CGLIB子类仅可以覆盖非静态方法。

作为结果,在这种静态@Bean方法内调用别的@Bean方法是标准的Java语法,
将导致直接返回独立的bean实例。

Java语言的可见性并不会在Spring容器内与bean定义结果相冲突。
可以在@Configuration类内随便定义@Bean工厂方法。
但是只能在@Configuration类内定义可被覆盖的@Bean工厂方法,不可以是private或者final

Spring也会在@Component@Configuration类的基类或实现的接口的默认方法里检测@Bean方法。

单一的一个类可能含有多个bean工厂方法返回同一类型的bean,方法之间的区别在于需要的参数不一样。
最多参数被匹配的方法胜出。

给被检测到的组件类命名

@Component@Repository@Service@Controller@RestController@Configuration
注解有一个value属性,将这个类视为一个bean,就是这个bean的名称。

如果不提供value属性,默认返回小写的非全限定类名:

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

SimpleMovieLister类被发现时的名称为myMovieLister。

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

MovieFinderImpl类被发现时的名称为movieFinderImpl。

如果要定制一个bean命名策略,可以实现BeanNameGenerator接口,必需要有无参数构造器。
然后在@ComponentScan里提供实现类的全限定类名:

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

或者xml里面:

<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

最好不要自定义名称生成,用注解给定就够了。

组件类的作用域

默认作用域是singleton。可以通过@Scope注解声明:

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

作用域只用于组件类bean,不会被类里面的bean工厂方法继承。

如果要定制一个作用域解析策略,可以实现ScopeMetadataResolver接口,必需要有无参数构造器。
然后在@ComponentScan里提供实现类的全限定类名:

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

在xml配置中:

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

当使用非singleton作用域时,可能要为被作用域修饰的对象生成代理。
通过@ComponentScan注解的scopedProxy属性设置:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}

xml配置:

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

interfaces指使用标准JDK动态代理。

三个可选的值为:nointerfaces,和targetClass

用于容器类的@Qualifier

和用于自动bean的@Qualifier一样,可以将这个注解用于类:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}

只绑定类定义,不会继承给内部定义的bean。

生成候选组件索引

在编译时生成一个静态的候选列表对于大型程序可以提升启动速度。
现存的@ComponentScan<context:component-scan/>不可以改动。
容器检测到有索引存在就会直接使用,不会再扫描classpath。

生成索引需要添加依赖,比如在Maven中添加:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.7</version>
        <optional>true</optional>
    </dependency>
</dependencies>

在项目打包成jar时会在META-INF/目录下生成spring.components文件。

spring-context-indexer必需被注册成注解处理器才能确保当组件更新时索引也同步更新。

可以通过在SpringProperties里设置spring.index.ignore为true以切换回扫描classpath模式。

JSR 330标准注解

都用Spring了,Spring是它的超集,用Spring就好了。

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注