1.1 元注解
能声明在其他注解上的注解,典型的例如Spring中的@Compoment,它在@Service,@Repository等注解上都有标注。值得注意的是,元注解并非只限定在Spring的使用场景中,像@Documented,@Inhreited这些Java标准注解,也属于元注解。
1.2 Spring模式注解
Spring模式注解即@Component派生注解,由于Java语法规定,Annotation不允许继承,因此Spring采用元注解的方式实现注解间的"派生"。
简单的说,Spring模式注解就被@Component注解标注的注解,例如@Service,@Compository,@Controller
1.2.1 @Component注解的派生性
@Component作为Spring容器托管的通用模式组件,任何被@Component标注的组件均为组件扫描的候选对象。类似的,凡是被@Component元标注(meta-annotated)的注解,如@Service,当任何组件标注它时,也被视为组件扫描的候选对象。这种性质就是@Component注解的派生性。
1.2.2 @Component派生性原理
在进行源码分析前,先说明,我并不会对每一行源码都详细的说明,毕竟本本章的重点是说明@Component的派生性原理,而非Spring源码解析,如果对贴出的每一行源码都进行说明,容易迷失重点,所以只会对其中与本章相关的点进行说明
先从Spring传统的xml配置方式入手,Spring通过context:component-scan/标签配置扫描@Component组件。根据/META-INF/sping.handlers的配置,我们能定位到对应的处理器ContextNameSpaceHandler(具体原理可以参考spring自定义xml配置,这里不多做描述)。
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
依照源码,Spring框架在解析context:component-scan/时,会生成ComponentScanBeanDefinitionParser类,并调用它的parse()方法。
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
这里我们重点看下面这两行代码,这两行代码的的逻辑是生成一个ClassPathBeanDefinitionScanner对象 并扫描basePackges下的类,为满足条件的类生成对应的BeanDefinitionHolder对象,以便接下来注册到Spring容器中。
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
首先我们深入到ClassPathBeanDefinitionScanner#doScan的方法中
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
...
return beanDefinitions;
}
从代码里看到,doScan方法遍历了basePackages,并调用findCandidateComponents方法获取了候选组件。从该方法一直深入下去,我们能看到,
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
...
for (Resource resource : resources) {
...
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
...
candidates.add(sbd);
}
...
}
...
}
...
}
...
}
}
...
return candidates;
}
scanCandidateComponents方法首先处理backagePgae的占位符,将${…}的内容替换为实际的配置值,并将Java pagekage路径中的“.”替换为“/”。假设basePackage是“thinking.in.spring.boot”,那么处理后的packageSearchPath便是“classpath*:thingking/in/spring/boot/**/*.class”,随后以此为参数,获得类资源集合。之后,便会迭代resources,并根据resource获取MetadataReader对象,MetadataReader中包含了类和注解的源信息读取方法,是后续isCandidateComponent方法的判断依据。
最后,类资源是否满足候选条件就依据isCandidateComponent()方法判断。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
候选条件的判断具体由includeFilters和excludeFilters字段决定。那么,includeFilters是在何时初始化的呢?让我们回头到最初的ComponentScanBeanDefinitionParser#parse方法中的。
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
深入该行代码,我们能看到,这里会调用ClassPathBeanDefinitionScanner的构造方法
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
并最终会调用registerDefaultFilters()方法,在该方法中,会给includeFilters字段增添一个包含@Component类型信息的AnnotationTypeFilter实例。
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
因此,ClassPathBeanDefinitionScanner类会扫描标注所有@Compontent以及@Compontent派生注解标注的类为Spring候选组件。
在SpringBoot或者高版本的Spring中,支持通过注解@ComponentScan的方式标注组件扫描。原理和xml配置是一样的。在ApplicationContext的AbstractApplicationContext#refresh()方法中,会调用postProcessBeanFactory()方法,
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
...
}
}
在AbstractApplicationContext的子类中,该方法会重载,已AnnotationConfigServletWebApplicationContext为例
public AnnotationConfigServletWebApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
if (!ObjectUtils.isEmpty(this.basePackages)) {
this.scanner.scan(this.basePackages);
}
if (!this.annotatedClasses.isEmpty()) {
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
}
}
可以看到,最终还是调用了ClassPathBeanDefinitionScanner#scan方法
1.2.3 @Component多层次派生性质
Spring4.0.0之后的版本,@Component派生性开始具备多层次,多层次的表现在于,@A标注了@B,@Component标注了@A,那么@B同样拥有@Component的性质。在4.0.0之前的版本,@Component只能支持单层次或者两层的派生性,这其中的差别主要是由于metadataReader的内部的实现差异造成的。
前文有所提到,MetadataReader中包含了类和注解的源信息读取方法,在老版本中,MetadataReader内部实现只查找一层或两层元注解信息。而从4.0.0版本开始,则采用了递归的方式获取所有的上层元注解。这时,Spring才开始真正支持@Component多层次派生性。具体在这里不在赘述,大家感兴趣可以自己翻看源码。
1.3 组合注解
所谓的组合注解,指的是某个注解被"元标注"了一个或多个其他注解,其目的是将这些注解行为合并为单个自定义注解。简单举例来说,@TransactionalService标注了@Transctional和@Service注解,那么,@TransactionalService就同时具备了这两个注解语义。
1.4 Spring注解属性别名和覆盖
1.4.1.隐式覆盖
较低层次的注解属性会覆盖同名的高层次注解属性,如何理解较低层次呢?已@Service为例,@Compontant元标注了@Service,@Service就是较低的层次,@Compontent就是高层次,所以@Service的注解属性会覆盖同名的@Compoent注解属性。
1.4.2.显示覆盖
有隐式覆盖就有显式覆盖,显示覆盖就是@AliasFor提供的属性覆盖能力。
简单介绍下@AliasFor标签有几种使用方式。
- 在同一个注解内显示使用
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
//...
}
示例代码中,value和path就是互为别名,注意互为别名的属性属性值类型,默认值,都是相同的,互为别名的注解必须成对出现,比如value属性添加了@AliasFor(“path”),那么path属性就必须添加@AliasFor(“value”),互为别名的属性必须定义默认值。
- 显示的覆盖元注解中的属性
...
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
)
...
public @interface SpringBootApplication {
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
...
}
已SpringBootApplication为例,@ComponentScan是@SpringBootApplication的源注解,
@SpringBootApplication分别指定了scanBasePackages和scanBasePackageClasses为注解源ComponentScan的basePackages和basePackageClasses的别名
- 在一个注解中隐式声明别名
@ContextConfiguration
public @interface MyTestConfig {
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] value() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] groovyScripts() default {};
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xmlFiles() default {};
}
可以看到,在MyTestConfig注解中,为value,groovyScripts,xmlFiles都定义了别名@AliasFor(annotation = ContextConfiguration.class, attribute = “locations”),所以,其实在这个注解中,value、groovyScripts和xmlFiles也互为别名,这个就是所谓的在统一注解中的隐式别名方式;
- 别名的传递
简单理解,如果A是B的别名,并且B是C的别名,那么A是C的别名;