@ComponentScan
注解是 Spring
中很常用的注解,用于扫描并加载指定类路径下的 Bean
,而 Spring Boot
为了便捷使用 @SpringBootApplication
组合注解集成了 @ComponentScan
的能力。也许你听说过使用后者会覆盖前者中关于包扫描的设置,但你是否质疑过这个“不合常理”的结论?是否好奇过为什么它们不像其他注解在嵌套使用时可以同时生效?又是否好奇过 @SpringBootApplication
可以间接设置 @ComponentScan
属性的原因?本文从源码角度分析 @ComponentScan
的工作原理,揭示它独特的检索算法和注解层次结构中的属性覆盖机制。
入口 对于标注了 @ComponentScan
注解的配置类,处理过程如下:
获取 @ComponentScan
的注解属性
遍历注解属性集合,依次根据其中的信息进行扫描,获取 Bean
定义
如果获取到的 Bean
定义中有任何其他配置类,将递归解析(处理配置类)
这里和处理 @Import
的过程很像,都出现了递归解析新获得的配置类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 protected final SourceClass doProcessConfigurationClass (ConfigurationClass configClass, SourceClass sourceClass) throws IOException { Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this .conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { Set<BeanDefinitionHolder> scannedBeanDefinitions = this .componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); for (BeanDefinitionHolder holder : scannedBeanDefinitions) { if (ConfigurationClassUtils.checkConfigurationClassCandidate( holder.getBeanDefinition(), this .metadataReaderFactory)) { parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } } } } }
扫描获取 Bean 定义 我们先跳过“获取 @ComponentScan
的注解属性”的过程,来看“扫描获取 Bean
定义”的过程。扫描是通过 ComponentScanAnnotationParser
的 parse
方法完成的,这个方法很长,但逻辑并不复杂,主要是为 ClassPathBeanDefinitionScanner
设置一些来自 @ComponentScan
的注解属性值,最终执行扫描。ClassPathBeanDefinitionScanner
顾名思义是基于类路径的 Bean
定义扫描器,真正的扫描工作全部委托给了它。在这些设置过程中,我们需要关注 basePackages
的设置:
使用 Set 存储合并结果,用于去重
获取设置的 basePackages
值并添加
获取设置的 basePackageClasses
值,转换为它们所在的包名并添加
如果结果集现在还是空的,获取被标注的配置类所在的包名并添加
最后一条规则就是“默认情况下扫描配置类所在的包”的说法由来,并且根据代码可知,如果主动设置了值,这条规则就不起作用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public Set<BeanDefinitionHolder> parse (AnnotationAttributes componentScan, final String declaringClass) { Assert.state(this .environment != null , "Environment must not be null" ); Assert.state(this .resourceLoader != null , "ResourceLoader must not be null" ); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner (this .registry, componentScan.getBoolean("useDefaultFilters" ), this .environment, this .resourceLoader); Class<? extends BeanNameGenerator > generatorClass = componentScan.getClass("nameGenerator" ); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this .beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy" ); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver > resolverClass = componentScan.getClass("scopeResolver" ); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } scanner.setResourcePattern(componentScan.getString("resourcePattern" )); for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters" )) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters" )) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } boolean lazyInit = componentScan.getBoolean("lazyInit" ); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true ); } Set<String> basePackages = new LinkedHashSet <String>(); String[] basePackagesArray = componentScan.getStringArray("basePackages" ); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this .environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); basePackages.addAll(Arrays.asList(tokenized)); } for (Class<?> clazz : componentScan.getClassArray("basePackageClasses" )) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter (false , false ) { @Override protected boolean matchClassName (String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
parse
方法与其说是解析,不如说是封装了一些设置并最终调用 ClassPathBeanDefinitionScanner
,而设置的属性值来源于 @ComponentScan
的注解属性。关于获取 @ComponentScan
的注解属性的方法 AnnotationConfigUtils.attributesForRepeatable
在分析 @PropertySource
时也曾经遇到过,顾名思义我们知道它应该是用于获取可重复的注解的属性。可是它和直接获取注解对象有什么区别呢?
我们知道 @SpringBootApplication
拥有和 @ComponentScan
具备相似的功能,并且可以使用 scanBasePackages
和 scanBasePackageClasses
这两个属性设置扫描的包。也许你还知道 @SpringBootApplication
之所以如此是因为它被标注了 @ComponentScan
,scanBasePackages
和 scanBasePackageClasses
分别是它的元注解 @ComponentScan
中 basePackages
和 basePackageClasses
的别名。你甚至可能知道如果在配置类上使用 @ComponentScan
设置包扫描后会导致 @SpringBootApplication
设置的包扫描失效 。可是为什么呢? 在 Spring
中我们会看到从指定类上直接获取目标注解的代码,我们还会看到递归地从元注解上获取目标注解的代码,我们使用 @ComponentScan
的经验告诉我们可重复注解不是覆盖彼此而是共同生效,那么为什么 @SpringBootApplication
上的 @ComponentScan
就被覆盖了呢?想当然的认为 @SpringBootApplication
上标注了 @ComponentScan
是一切的原因是不够的 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
获取注解属性 attributesForRepeatable
方法有两个重载方法,最终调用的版本如下。先后处理了 @ComponentScan
和 @ComponentScans
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static Set<AnnotationAttributes> attributesForRepeatable (AnnotationMetadata metadata, String containerClassName, String annotationClassName) { Set<AnnotationAttributes> result = new LinkedHashSet <AnnotationAttributes>(); addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false )); Map<String, Object> container = metadata.getAnnotationAttributes(containerClassName, false ); if (container != null && container.containsKey("value" )) { for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value" )) { addAttributesIfNotNull(result, containedAttributes); } } return Collections.unmodifiableSet(result); }
检索注解的规则 根据注释,getAnnotationAttributes
方法检索给定类型的注解的属性,检索的目标可以是直接注解也可以是元注解 ,同时考虑组合注解上的属性覆盖 。
元注解指的是标注在其他注解上的注解,用于对被标注的注解进行说明,比如 @SpringBootApplication
上的 @ComponentScan
就被称为元注解,此时 @SpringBootApplication
被称为组合注解
组合注解中存在属性覆盖现象
其实这两点分别对应了我们想要探究的两个问题:@ComponentScan
究竟是如何被检索的?注解属性比如 basePackages
又是如何被覆盖的?
1 2 3 4 5 public Map<String, Object> getAnnotationAttributes (String annotationName, boolean classValuesAsString) { return (this .annotations.length > 0 ? AnnotatedElementUtils.getMergedAnnotationAttributes( getIntrospectedClass(), annotationName, classValuesAsString, this .nestedAnnotationsAsMap) : null ); }
根据注释,getMergedAnnotationAttributes
方法获取所提供元素上方的注解层次结构中指定的 annotationName
的第一个注解,并将该注解的属性与注解层次结构较低级别中的注解中的匹配属性合并。注解层次结构中较低级别的属性会覆盖较高级别中的同名属性,并且完全支持单个注解中或是注解层次结构中的 @AliasFor
语义。与 getAllAnnotationAttributes
方法相反,一旦找到指定 annotationName
的第一个注解,此方法使用的搜索算法将停止搜索注解层次结构。因此,指定的 annotationName
的附加注解将被忽略。
这注释有点太抽象了,理解代码后再来回味吧。
1 2 3 4 5 6 7 8 9 public static AnnotationAttributes getMergedAnnotationAttributes (AnnotatedElement element, String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { AnnotationAttributes attributes = searchWithGetSemantics(element, null , annotationName, new MergedAnnotationAttributesProcessor (classValuesAsString, nestedAnnotationsAsMap)); AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); return attributes; }
searchWithGetSemantics
方法有多个重载方法,最终调用的版本如下:
先获取 element
上的所有注解(包括重复的,不包括继承的),这意味着可重复注解 @ComponentScan
标注了多个就会有多个实例
在注解中搜索
如果没找到,就从继承的注解中继续搜索
本方法是一个会被递归调用的方法,在第一次调用时 element
是配置类,之后就是注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private static <T> T searchWithGetSemantics (AnnotatedElement element, @Nullable Class<? extends Annotation> annotationType, @Nullable String annotationName, @Nullable Class<? extends Annotation> containerType, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) { if (visited.add(element)) { try { List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations()); T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations, annotationType, annotationName, containerType, processor, visited, metaDepth); if (result != null ) { return result; } if (element instanceof Class) { List<Annotation> inheritedAnnotations = new ArrayList <>(); for (Annotation annotation : element.getAnnotations()) { if (!declaredAnnotations.contains(annotation)) { inheritedAnnotations.add(annotation); } } result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations, annotationType, annotationName, containerType, processor, visited, metaDepth); if (result != null ) { return result; } } } catch (Throwable ex) { AnnotationUtils.handleIntrospectionFailure(element, ex); } } return null ; }
遍历注解进行搜索。
先在注解中搜索,这意味着如果配置类标注了 @ComponentScan
,直接就找到了
如果没找到再在元注解中搜索,如果配置类只标注了 @SpringBootApplication
,就是在这部分找到元注解 @ComponentScan
严格意义上说,并不是直接标注的 @ComponentScan
会覆盖 @SpringBootApplication
上间接标注的 @ComponentScan
,而是搜索在找到第一个注解后终止没有继续查找。这解答了我们的第一个疑问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 private static <T> T searchWithGetSemanticsInAnnotations (@Nullable AnnotatedElement element, List<Annotation> annotations, @Nullable Class<? extends Annotation> annotationType, @Nullable String annotationName, @Nullable Class<? extends Annotation> containerType, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) { for (Annotation annotation : annotations) { Class<? extends Annotation > currentAnnotationType = annotation.annotationType(); if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { if (currentAnnotationType == annotationType || currentAnnotationType.getName().equals(annotationName) || processor.alwaysProcesses()) { T result = processor.process(element, annotation, metaDepth); if (result != null ) { if (processor.aggregates() && metaDepth == 0 ) { processor.getAggregatedResults().add(result); } else { return result; } } } else if (currentAnnotationType == containerType) { for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) { T result = processor.process(element, contained, metaDepth); if (result != null ) { processor.getAggregatedResults().add(result); } } } } } for (Annotation annotation : annotations) { Class<? extends Annotation > currentAnnotationType = annotation.annotationType(); if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { T result = searchWithGetSemantics(currentAnnotationType, annotationType, annotationName, containerType, processor, visited, metaDepth + 1 ); if (result != null ) { processor.postProcess(element, annotation, result); if (processor.aggregates() && metaDepth == 0 ) { processor.getAggregatedResults().add(result); } else { return result; } } } } return null ; }
处理 @ComponentScan
获得 AnnotationAttributes
。
1 2 3 4 public AnnotationAttributes process (@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { return AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation, this .classValuesAsString, this .nestedAnnotationsAsMap); }
以 AnnotationAttributes
映射的形式检索给定注解的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 static AnnotationAttributes retrieveAnnotationAttributes (@Nullable Object annotatedElement, Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { Class<? extends Annotation > annotationType = annotation.annotationType(); AnnotationAttributes attributes = new AnnotationAttributes (annotationType); for (Method method : getAttributeMethods(annotationType)) { try { Object attributeValue = method.invoke(annotation); Object defaultValue = method.getDefaultValue(); if (defaultValue != null && ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) { attributeValue = new DefaultValueHolder (defaultValue); } attributes.put(method.getName(), adaptValue(annotatedElement, attributeValue, classValuesAsString, nestedAnnotationsAsMap)); } catch (Throwable ex) { if (ex instanceof InvocationTargetException) { Throwable targetException = ((InvocationTargetException) ex).getTargetException(); rethrowAnnotationConfigurationException(targetException); } throw new IllegalStateException ("Could not obtain annotation attribute value for " + method, ex); } } return attributes; } static List<Method> getAttributeMethods (Class<? extends Annotation> annotationType) { List<Method> methods = attributeMethodsCache.get(annotationType); if (methods != null ) { return methods; } methods = new ArrayList <>(); for (Method method : annotationType.getDeclaredMethods()) { if (isAttributeMethod(method)) { ReflectionUtils.makeAccessible(method); methods.add(method); } } attributeMethodsCache.put(annotationType, methods); return methods; } static boolean isAttributeMethod (@Nullable Method method) { return (method != null && method.getParameterCount() == 0 && method.getReturnType() != void .class); }
组合注解的属性覆盖 在获得注解属性后还要进行后处理,使用注解层次结构中较低级别的属性覆盖较高级别中的同名(包括 @AliasFor
指定的)属性。比如使用 @SpringBootApplication
中的 scanBasePackages
的值覆盖 @ComponentScan
中的 basePackages
的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public void postProcess (@Nullable AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) { annotation = AnnotationUtils.synthesizeAnnotation(annotation, element); Class<? extends Annotation > targetAnnotationType = attributes.annotationType(); Set<String> valuesAlreadyReplaced = new HashSet <>(); for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) { String attributeName = attributeMethod.getName(); String attributeOverrideName = AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType); if (attributeOverrideName != null ) { if (valuesAlreadyReplaced.contains(attributeOverrideName)) { continue ; } List<String> targetAttributeNames = new ArrayList <>(); targetAttributeNames.add(attributeOverrideName); valuesAlreadyReplaced.add(attributeOverrideName); List<String> aliases = AnnotationUtils.getAttributeAliasMap(targetAnnotationType).get(attributeOverrideName); if (aliases != null ) { for (String alias : aliases) { if (!valuesAlreadyReplaced.contains(alias)) { targetAttributeNames.add(alias); valuesAlreadyReplaced.add(alias); } } } overrideAttributes(element, annotation, attributes, attributeName, targetAttributeNames); } else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) { overrideAttribute(element, annotation, attributes, attributeName, attributeName); } } } static String getAttributeOverrideName (Method attribute, @Nullable Class<? extends Annotation> metaAnnotationType) { AliasDescriptor descriptor = AliasDescriptor.from(attribute); return (descriptor != null && metaAnnotationType != null ? descriptor.getAttributeOverrideName(metaAnnotationType) : null ); } static Map<String, List<String>> getAttributeAliasMap (@Nullable Class<? extends Annotation> annotationType) { if (annotationType == null ) { return Collections.emptyMap(); } Map<String, List<String>> map = attributeAliasesCache.get(annotationType); if (map != null ) { return map; } map = new LinkedHashMap <>(); for (Method attribute : getAttributeMethods(annotationType)) { List<String> aliasNames = getAttributeAliasNames(attribute); if (!aliasNames.isEmpty()) { map.put(attribute.getName(), aliasNames); } } attributeAliasesCache.put(annotationType, map); return map; } static List<String> getAttributeAliasNames (Method attribute) { AliasDescriptor descriptor = AliasDescriptor.from(attribute); return (descriptor != null ? descriptor.getAttributeAliasNames() : Collections.<String> emptyList()); } private void overrideAttributes (@Nullable AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes, String sourceAttributeName, List<String> targetAttributeNames) { Object adaptedValue = getAdaptedValue(element, annotation, sourceAttributeName); for (String targetAttributeName : targetAttributeNames) { attributes.put(targetAttributeName, adaptedValue); } }
在代码的注释中我们留下过一个疑问,如果找到了第一个注解就立即返回,那么标注了多个 @ComponentScan
呢?当你 Debug
时,你会发现并没有走出现直接标注了 @ComponentScan
的处理,其实看到反编译后的代码你就知道了,多个 @ComponentScan
被合成了一个 @ComponentScans
,甚至此时设置的三个 basePackages
都是生效的。在 JDK 8
引入的重复注解机制,并非一个语言层面上的改动,而是编译器层面的改动。在编译后,多个可重复注解 @ComponentScan
会被合并到一个容器注解 @ComponentScans
中。
因此,“@ComponentScan
的配置会覆盖 @SpringBootApplication
关于包扫描的配置”这句话既对又不对,它在一个常见的个例上表现出的现象是对的,在更普遍的情况中以及本质上是错误的。你也许可以再根据一些情况罗列出类似的“@ComponentScan
使用规则”,但是如果你不明白背后的本质,那么这些只是一些死记硬背的陈述,甚至会带给你错误的认知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringBootApplication( scanBasePackages = {"com.example"} ) @ComponentScans({@ComponentScan( basePackages = {"com.example.demo"} ), @ComponentScan({"com"})}) public class DemoApplication { public DemoApplication () { } public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
注解内的别名属性 postProcess
方法完成了组合注解的属性覆盖,可是对于 @ComponentScan
注解而言,它没有被 postProcess
方法处理,它又是如何做到设置 basePackages
等于设置 value
呢?其实这发生在后处理注解属性方法中,该方法会对注解中标注了 @AliasFor
的属性强制执行别名语义。通俗地讲,就是统一 或校验 互为别名的属性值,要么只设置了其中一个属性的值,其他别名属性会被赋值为相同的值,要么设置为相同的值,否则会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public static AnnotationAttributes getMergedAnnotationAttributes (AnnotatedElement element, String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { AnnotationAttributes attributes = searchWithGetSemantics(element, null , annotationName, new MergedAnnotationAttributesProcessor (classValuesAsString, nestedAnnotationsAsMap)); AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); return attributes; } static void postProcessAnnotationAttributes (@Nullable Object annotatedElement, @Nullable AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { if (attributes == null ) { return ; } Class<? extends Annotation > annotationType = attributes.annotationType(); Set<String> valuesAlreadyReplaced = new HashSet <>(); if (!attributes.validated) { Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType); for (String attributeName : aliasMap.keySet()) { if (valuesAlreadyReplaced.contains(attributeName)) { continue ; } Object value = attributes.get(attributeName); boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder)); for (String aliasedAttributeName : aliasMap.get(attributeName)) { if (valuesAlreadyReplaced.contains(aliasedAttributeName)) { continue ; } Object aliasedValue = attributes.get(aliasedAttributeName); boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder)); if (valuePresent || aliasPresent) { if (valuePresent && aliasPresent) { if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) { String elementAsString = (annotatedElement != null ? annotatedElement.toString() : "unknown element" ); throw new AnnotationConfigurationException (String.format( "In AnnotationAttributes for annotation [%s] declared on %s, " + "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " + "but only one is permitted." , attributes.displayName, elementAsString, attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue))); } } else if (aliasPresent) { attributes.put(attributeName, adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(attributeName); } else { attributes.put(aliasedAttributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(aliasedAttributeName); } } } } attributes.validated = true ; } for (String attributeName : attributes.keySet()) { if (valuesAlreadyReplaced.contains(attributeName)) { continue ; } Object value = attributes.get(attributeName); if (value instanceof DefaultValueHolder) { value = ((DefaultValueHolder) value).defaultValue; attributes.put(attributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); } } }
总结
又是一篇在写之前自认心里有数,以为可以很快总结完,却不知不觉写了很久,也收获了很多的文章。在刚开始,我只是想接续分析 @Configuration
的思路补充关于 @ComponentScan
的内容,但是渐渐地我又想要回应心里的疑问,@ComponentScan
和 @SpringBootApplication
一起使用的问题的本质原因是什么?Spring
框架真的很好用,好用到你不用太关心背后的原理,好用到你有时候用一个本质上不太正确的结论“走遍天下却几乎不会遇到问题”。说实话,研究完也有点索然无味,尤其是花了这么多时间看自己很讨厌的关于解析的代码,只能说解开了一个卡点也算疏通了一口气,但是时间成本好大啊,得多看点能“面试”的技术啊!!!
综上分析,@SpringBootApplication
的包扫描功能本质上还是 @ComponentScan
提供的,但是和常见的嵌套注解不同,检索 @ComponentScan
有一套独特的算法,导致 @SpringBootApplication
和 @ComponentScan
并非简单的叠加效果。
Spring
会先获取 @ComponentScan
的注解属性再获取 @ComponentScans
的注解属性
以 @ComponentScan
为例,只获取给定配置类上的注解层次结构中的第一个 @ComponentScan
先从直接标注的注解开始,再递归地搜索元注解,这一点决定了 @ComponentScan
优先级高于 @SpringBootApplication
使用注解层次结构中较低级别的属性覆盖较高级别的同名(支持 @AliasFor
)属性,这一点决定了 @SpringBootApplication
可以设置扫描路径
多个 @ComponentScan
在编译后隐式生成 @ComponentScans
,这一点决定多个 @ComponentScan
彼此之间以及和 @SpringBootApplication
互不冲突