重读Spring系列-@SpringBootApplication注解的详细解析
Posted _微风轻起
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重读Spring系列-@SpringBootApplication注解的详细解析相关的知识,希望对你有一定的参考价值。
这一篇我们来梳理下@SpringBootApplication
这个注解它注入了那些内容。
一、结构分析
1、main方法使用
我们知道@SpringBootApplication
是用在Main方法上面的,例如:
@SpringBootApplication
public class SpringBootSimpleDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSimpleDemoApplication.class, args);
}
}
通过怎样,其实就能通过入参SpringBootSimpleDemoApplication.class
,来获取到@SpringBootApplication
。
2、@SpringBootApplication结构
@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 {};
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
我们进入这个方法里面,可以看到这个方法里面又引入了几个其他的注解。我们先看@ComponentScan
,我们知道这个是用来自动扫描包路径的组件的,同时这里又引入了两个排除FilterType
:TypeExcludeFilter
、AutoConfigurationExcludeFilter
。
再之后是SpringBootConfiguration
,这个注解其实没有什么特殊的内容,主要是用来标记这个是SpringBoot
配置,就是引入了@Configuration
注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
然后是@EnableAutoConfiguration
,这种就是@Enablexxx
,用来自动注入的,我们之后的源码也 就是主要分析这类的逻辑。我们之后也可以自己实现一个自定义的@Enablexxx
。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
二、关于阅读下面流程来拓展Spring的扫描组件注解
这一项是我们来拓展Spring的源码
1、拓展一个与@Component同样的注入组件@BeanClass
我们一般主动注入一个Bean可以是下面的样式,这里以主动注入:
@Configuration
public class MyConfiguration {
@Bean
public Book book()
{
return new Book();
}
}
当然我们也可以直接使用@Service
(也就是@Component
)注解,下面我们就来独立定义一个主键注解,当然不是不是想@Service
本身主要是为了引入@Component
:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
我们是与@Component
同级别的。
1)、demo1
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BeanClass {
}
这个注解我们将其命名为@BeanClass
,然后也是使用在类上面的。
再定义一个TypeFilter
,如果一个类上面有这个注解,我们就返回true
。
public class BeanClassTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
if (metadataReader.getAnnotationMetadata().isAnnotated(BeanClass.class.getName()))
{
return true;
}
return false;
}
}
然后再使用@ComponentScan
引入BeanClassTypeFilter
,并指定扫描的包名。
@Configuration
@ComponentScan(basePackages = "com.fev.springboot.simple.demo",includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes = BeanClassTypeFilter.class)})
public class MyConfiguration {
}
最后启动SpringBoot
项目测试,就能获取到了,感觉这个demo其实可以放在上一篇文章。
这里我们启动测试类,就能注入Book
了。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootSimpleDemoApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test1()
{
Book contextBean = applicationContext.getBean(Book.class);
System.out.println("............" + contextBean);
}
}
............com.fev.springboot.simple.demo.Book@33eb6758
当然这个目前可能不知道为什么,但看到后面源码部分我们就知道了。
三、@ComponentScan在@SpringBootApplication中的源码分析
我们在上一篇是有分析过@ComponentScan
的整体逻辑的,其的解析是在ConfigurationClassPostProcessor
类中的。这里我们直接去doProcessConfigurationClass
方法。
1、doProcessConfigurationClass (ConfigurationClassPostProcessor)
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
...........
// Process any @ComponentScan annotations
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) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
..........
// No superclass -> processing is complete
return null;
}
首先这里是我们会扫描到这个注解的信息,包括两个排除FilterexcludeFilters
。
然后便是this.conditionEvaluator.shouldSkip
,这里目前是会false
,这个ConditionEvaluator
我们本篇之后会具体讲。再就是this.componentScanParser.parse
解析获取这个@ComponentScan
要扫描的包下面的组件了。当前componentScanParser
是ComponentScanAnnotationParser
2、this.componentScanParser.parse (ComponentScanAnnotationParser)
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
.......
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);
}
}
.........
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, 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));
}
上面这个就是构建ClassPathBeanDefinitionScanner scanner
,并且讲@ComponentSans
的注入信息添加到scanner
中,例如IncludeFilter
、ExcludeFilter
这些。然后就是一个扫描包的路径问题,如果你有指定要扫描的包名basePackages
,其就会是指定的包名,如果没有指定basePackages.isEmpty()
,其就会是declaringClass
,也就是被@ComponentScan
注解的类的包名。之后就是具体的scanner.doScan(StringUtils.toStringArray(basePackages))
了。
同时这里我们可以注意到这里还另外加了一个ExcludeFilter
即scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false)
。
3、scanner.doScan (ClassPathBeanDefinitionScanner)
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);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
这里首先是通过findCandidateComponents
获取到这个包下面所有的.class
的Resource
文件。
1)、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);
}
.........
}
....
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
..........
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}以上是关于重读Spring系列-@SpringBootApplication注解的详细解析的主要内容,如果未能解决你的问题,请参考以下文章
重读Spring系列-Conditional注入Bean的判断