Spring ComponentScan
1. 包扫描的路径
@ComponentScan
注解可以指定包扫描的路径(而且还可以声明不止一个),它的写法是使用 @ComponentScan
的 value
/ 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
)
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);
}
又出现 BeanDefinition
和 BeanDefinitionRegistry
了,可见元信息、元定义在底层真的太常见了!
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);
}
看这段源码的实现,整体的逻辑还是非常容易理解的:
只有注解扫描注册进来的 Bean 才会被处理
既然是注解扫描进来的,那我就要看看有木有在注解中声明好了,这种声明方式就是
@Component("person")
注解中找不到名,那好吧,我给你构造一个,不过这个名是按照我默认规则来的,你就别挑挑拣拣咯
上面从注解中获取的部分咱留到后面再看,这里咱只看 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.Person
→ Person
),最后用一个叫 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 生成 getter
和 setter
,或者借助 Lombok
的注解生成 getter
和 setter
?其实这个生成规则,就是利用了 Java 的内省机制。
**Java 的内省默认规定,所有属性的获取方法以 get 开头( boolean 类型以 is 开头),属性的设置方法以 set 开头。**根据这个规则,才有的默认的 getter 和 setter 方法。
Introspector
类是 Java 内省机制中最核心的类之一,它可以进行很多默认规则的处理(包括获取类属性的 get / set 方法,添加方法描述等),当然它也可以处理这种类名转 beanName 的操作。SpringFramework 深知这个设计之妙,就直接利用过来了。
最后更新于
这有帮助吗?