Spring ComponentScan

1. 包扫描的路径

@ComponentScan 注解可以指定包扫描的路径(而且还可以声明不止一个),它的写法是使用 @ComponentScanvalue / basePackages 属性:

@Configuration
@ComponentScan("com.linkedbear.spring.annotation.e_basepackageclass.bean")
public class BasePackageClassConfiguration {
    
}

这种方式是最常用的,也是最推荐使用的。除此之外,还有一种声明方式basePackageClasses,它使用的是类的 Class 字节码:

@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

    /**
	 * Type-safe alternative to {@link #basePackages} for specifying the packages
	 * to scan for annotated components. The package of each class specified will be scanned.
	 * <p>Consider creating a special no-op marker class or interface in each package
	 * that serves no purpose other than being referenced by this attribute.
	 */
	Class<?>[] basePackageClasses() default {};
    // ...
}

它的这个 basePackageClasses 属性,可以传入一组 Class 进去,它代表的意思,是扫描传入的这些 Class 所在包及子包下的所有组件

@Configuration
@ComponentScan(basePackageClasses = DemoService.class)
public class BasePackageClassConfiguration {
    
}

这样就会以 DemoService 所在的包为基准扫描了。

2. 包扫描的过滤

在实际开发的场景中,我们用包扫描拿到的组件不一定全部都需要,也或者只有一部分需要,这个时候就需要用到包扫描的过滤了。

2.1 按注解过滤包含

@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class))
public class TypeFilterConfiguration {
    
}

注意:除了会把带有@Animal注解的组件注入进来,也会把带有@ComponentScan注解的组件注入进来。

因为,@ComponentScan 注解中还有一个属性:useDefaultFilters ,它代表的是“是否启用默认的过滤规则”。咱之前也讲过了,默认规则就是扫那些以 @Component 注解为基准的模式注解。

/**
 * Indicates whether automatic detection of classes annotated with {@code @Component}
 * {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
 */
boolean useDefaultFilters() default true;

2.2 按注解排除

@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class))
public class TypeFilterConfiguration {
    
}

排除型过滤器会排除掉其他过滤规则已经包含进来的 Bean 。

2.3 按类型过滤

@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Color.class)},
               excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class)})
public class TypeFilterConfiguration {
    
}

2.4 正则表达式过滤

除了按注解过滤、按类型过滤,它内置的模式还有两种表达式的过滤规则,分别是 “切入点表达式过滤” 和 “正则表达式过滤” 。

@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = {
                       @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.linkedbear.spring.annotation.f_typefilter.+Demo.+")
               },
               excludeFilters = {
                       @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class),
                       @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Color.class)
               })
public class TypeFilterConfiguration {
    
}

2.5 自定义过滤

如果预设的几种模式都不能满足要求,那就需要用编程式过滤方式了,也就是自定义过滤规则

2.5.1 TypeFilter接口

编程式自定义过滤,需要编写过滤策略,实现 TypeFilter 接口。这个接口只有一个 match 方法:

@FunctionalInterface
public interface TypeFilter {

	/**
	 * Determine whether this filter matches for the class described by
	 * the given metadata.
	 * @param metadataReader the metadata reader for the target class
	 * @param metadataReaderFactory a factory for obtaining metadata readers
	 * for other classes (such as superclasses and interfaces)
	 * @return whether this filter matches
	 * @throws IOException in case of I/O failure when reading metadata
	 */
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;

}

这个 match 方法有两个参数:

  • metadataReader :通过这个 Reader ,可以读取到正在扫描的类的信息(包括类的信息、类上标注的注解等)

  • metadataReaderFactory :借助这个 Factory ,可以获取到其他类的 Reader ,进而获取到那些类的信息。可以这样理解:借助 ReaderFactory 可以获取到 Reader ,借助 Reader 可以获取到指定类的信息。

2.5.2 编写自定义过滤规则

MetadataReader 中有一个 getClassMetadata 方法,可以拿到正在扫描的类的基本信息,咱可以由此取到全限定类名,进而与咱需求中的 Green 类做匹配:

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
         throws IOException {
    ClassMetadata classMetadata = metadataReader.getClassMetadata();
    return classMetadata.getClassName().equals(Green.class.getName());
}

返回 true ,则说明已经匹配上了。

2.5.3 添加过滤规则声明

TypeFilter 写完了,不要忘记加在 @ComponentScan 上:

@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = {
                       @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.linkedbear.spring.annotation.f_typefilter.+Demo.+")
               },
               excludeFilters = {
                       @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class),
                       @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Color.class),
                       @ComponentScan.Filter(type = FilterType.CUSTOM, value = GreenTypeFilter.class)
               })
public class TypeFilterConfiguration {
    
}

重新运行启动类的 main 方法,可以发现 Green 也没了,自定义 TypeFilter 生效。

2.5.4 metadata的概念

讲到这里了,咱先不着急往下走,停一停,咱讲讲 metadata 的概念。

回想一下 JavaSE 的反射,它是不是可以根据咱写好的类,获取到类的全限定名、属性、方法等信息呀。好,咱现在就建立起这么一个概念:咱定义的类,它叫什么名,它有哪些属性,哪些方法,这些信息,统统叫做元信息元信息会描述它的目标的属性和特征

在 SpringFramework 中,元信息大量出现在框架的底层设计中,不只是 metadata ,前面咱屡次见到的 definition ,也是元信息的体现。后面到了 IOC 高级部分,咱会整体的学习 SpringFramework 中的元信息、元定义设计,以及 BeanDefinition 的全解析。

3. 包扫描的其他特性

3.1 包扫描可以组合使用(ComponentScans

/**
 * Container annotation that aggregates several {@link ComponentScan} annotations.
 *
 * <p>Can be used natively, declaring several nested {@link ComponentScan} annotations.
 * Can also be used in conjunction with Java 8's support for repeatable annotations,
 * where {@link ComponentScan} can simply be declared several times on the same method,
 * implicitly generating this container annotation.
 *
 * @author Juergen Hoeller
 * @since 4.3
 * @see ComponentScan
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {

	ComponentScan[] value();

}

其实它就是一次性组合了一堆 @ComponentScan 注解而已了,没啥好说的。

3.2 包扫描的组件名称生成

咱在前面刚学习注解驱动时,就知道默认情况下生成的 bean 的名称是类名的首字母小写形式( Person → person ),可是它为啥就有这个规则呢?这个问题,也可以从 @ComponentScan 注解中找到。

@ComponentScan 注解的属性中,有一个 nameGenerator ,它的默认值是 BeanNameGenerator 。不过这个 BeanNameGenerator 是一个接口,从文档注释中不难找到实现类是 AnnotationBeanNameGenerator

/**
	 * The {@link BeanNameGenerator} class to be used for naming detected components
	 * within the Spring container.
	 * <p>The default value of the {@link BeanNameGenerator} interface itself indicates
	 * that the scanner used to process this {@code @ComponentScan} annotation should
	 * use its inherited bean name generator, e.g. the default
	 * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
	 * application context at bootstrap time.
	 * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
	 * @see AnnotationBeanNameGenerator
	 * @see FullyQualifiedAnnotationBeanNameGenerator
	 */
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

3.2.1 BeanNameGenerator

从名称上就知道它是 Bean 的名字生成器了,它只有一个 generateBeanName 方法:

/**
 * Strategy interface for generating bean names for bean definitions.
 *
 * @author Juergen Hoeller
 * @since 2.0.3
 */
public interface BeanNameGenerator {

	/**
	 * Generate a bean name for the given bean definition.
	 * @param definition the bean definition to generate a name for
	 * @param registry the bean definition registry that the given definition
	 * is supposed to be registered with
	 * @return the generated bean name
	 */
	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}

又出现 BeanDefinitionBeanDefinitionRegistry 了,可见元信息、元定义在底层真的太常见了!

3.2.2 AnnotationBeanNameGenerator的实现

找到 AnnotationBeanNameGenerator 的实现:

public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    if (definition instanceof AnnotatedBeanDefinition) {
        String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
        if (StringUtils.hasText(beanName)) {
            // Explicit bean name found.
            return beanName;
        }
    }
    // Fallback: generate a unique default bean name.
    return buildDefaultBeanName(definition, registry);
}

看这段源码的实现,整体的逻辑还是非常容易理解的:

  1. 只有注解扫描注册进来的 Bean 才会被处理

  2. 既然是注解扫描进来的,那我就要看看有木有在注解中声明好了,这种声明方式就是 @Component("person")

  3. 注解中找不到名,那好吧,我给你构造一个,不过这个名是按照我默认规则来的,你就别挑挑拣拣咯

上面从注解中获取的部分咱留到后面再看,这里咱只看 buildDefaultBeanName 的实现。

3.2.3 buildDefaultBeanName的实现

protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    return buildDefaultBeanName(definition);
}

protected String buildDefaultBeanName(BeanDefinition definition) {
    String beanClassName = definition.getBeanClassName();
    Assert.state(beanClassName != null, "No bean class name set");
    String shortClassName = ClassUtils.getShortName(beanClassName);
    return Introspector.decapitalize(shortClassName);
}

一路走到最底下的方法中,它会根据组件类的全限定名,截取出短类名(如 com.linkedbear.PersonPerson ),最后用一个叫 Introspector 的类,去生成 bean 的名称。那想必这个 Introspector.decapitalize 方法肯定就可以把类名的首字母转为小写咯,点进去发现确实如此:

/**
     * Utility method to take a string and convert it to normal Java variable
     * name capitalization.  This normally means converting the first
     * character from upper case to lower case, but in the (unusual) special
     * case when there is more than one character and both the first and
     * second characters are upper case, we leave it alone.
     * <p>
     * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
     * as "URL".
     *
     * @param  name The string to be decapitalized.
     * @return  The decapitalized version of the string.
     */
    public static String decapitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                        Character.isUpperCase(name.charAt(0))){
            return name;
        }
        char chars[] = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }

原理实现看完了,小伙伴们肯定有一个疑惑:Introspector 是个什么鬼哦??

3.2.4 Java的内省机制

说到这个内省,或许好多小伙伴都没听说过。其实它是 JavaSE 中就有的,对 JavaBean 中属性的默认处理规则

回想一下咱写的所有模型类,包括 vo 类,是不是都是写好了属性,之后借助 IDE 生成 gettersetter ,或者借助 Lombok 的注解生成 gettersetter ?其实这个生成规则,就是利用了 Java 的内省机制。

**Java 的内省默认规定,所有属性的获取方法以 get 开头( boolean 类型以 is 开头),属性的设置方法以 set 开头。**根据这个规则,才有的默认的 getter 和 setter 方法。

Introspector 类是 Java 内省机制中最核心的类之一,它可以进行很多默认规则的处理(包括获取类属性的 get / set 方法,添加方法描述等),当然它也可以处理这种类名转 beanName 的操作。SpringFramework 深知这个设计之妙,就直接利用过来了。

最后更新于