@SpringBootApplication注解-从源码分析

Posted by JimWang on 2021-03-05

@SpringBootApplication注解-从源码分析

1. 启动类的写法

1
2
3
4
5
6
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}

启动类中包含两个重要部分:

  • @SpringBootApplication注解
  • main方法中SpringApplication.run()方法

2. @SpringBootApplication的注解

进入SpringBootApplication注解,其头部注解如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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 {
// ...
}

我们一项一项来讲。

2.1 @Target

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}

该接口用于指定注解可以用于什么地方,并且用了一个枚举类数组来表示。四个元注解之一。

关于为什么注解的属性后面有括号

注解是java以接口为基础实现的。不同的是把方法在内部作为属性处理了。所以属性以方法的方式来进行定义,必须有括号。

再来看一下这个枚举类,到底是哪些地方可以用注解

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
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, // 类、接口(包括注解类型)或枚举声明

/** Field declaration (includes enum constants) */
FIELD, // 说明字段,包括枚举常量

/** Method declaration */
METHOD, // 方法声明

/** Formal parameter declaration */
PARAMETER, // 方法参数声明

/** Constructor declaration */
CONSTRUCTOR, // 构造方法

/** Local variable declaration */
LOCAL_VARIABLE, //局部变量

/** Annotation type declaration */
ANNOTATION_TYPE, // 注解类型

/** Package declaration */
PACKAGE, // 包

/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER, // 类型参数

/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE // 类型
}

2.2 @Retention

1
2
3
4
5
6
7
8
9
10
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}

Indicates how long annotations with the annotated type are to be retained. 根据介绍来看,Retention是用来表示注解的生命周期的,同样的也是通过一个枚举类来指定。四个元注解之一。

再来看一下RetentionPolicy枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE, // 注解在编译阶段就会被丢弃

/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS, // 被编译在类文件中,运行时不会被JVM保留。

/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME // 被编译在类文件中,并且在JVM中也会保留,可以通过反射获取到
}

2.3 @Documented

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

这个注解没有方法。该注解是为了指明被注解的类型可以被javadoc文本话。四个元注解之一。

2.4 @Inherited

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

Indicates that an annotation type is automatically inherited.

该注解表示父类的注解可以被子类继承。说明被@SpringBootApplication注解的类的子类也与它有相同的注解。四个元注解之一。

2.5 @SpringBootConfiguration

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

@AliasFor在这里表示继承注解。可以发现SpringBootConfiguration继承了@Configuration,在@AliasFor中并没有指明value,因此默认为Configuration注解中同名属性。

再来看@Configuration是怎么样的。

1
2
3
4
5
6
7
8
9
10
11
12
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";

boolean proxyBeanMethods() default true;
}

还是有一个@AliasFor注解,和一个会被SpringBootConfiguration继承的proxyBeanMethods属性。

先来讲一下@Configuration的用处。Indicates that a class declares one or more Beans methods and may be processed by Spring container to generate bean definitions and service requests for those beans at runtime.

意思就是说当@Configuration被注解在一个类上时,表明该类的内部有一个或多个生成Bean的方法,它们将被Spring容器用于生产Bean和响应运行时的服务请求。

proxyBeanMethods()方法,Specify whether {@code @Bean} methods should get proxied in order to enforce bean lifecycle behavior。意思是指定@Bean标注的方法是否使用代理模式,直接从IOC容器中获取对象。如果不需要将Bean交给spring管理,把这项设为false,那么每次获取的都是一个新的对象。

@Configuration中还包含了一个继承下来的方法,涉及到了@Component

1
2
3
4
5
6
7
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}

Indicates that an annotated class is a “component”.Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.

被注解为Component的类会被扫描到。其中的value方法是为component指定一个名字

2.6 @EnableAutoConfiguration

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
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; // 自动装载配置

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {}; // 根据类,排除这些类,不让它们自动配置

/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {}; // 根据类名,排除这些类,不让它们自动配置

}

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. 该注解是用来自动配置的,SpringBoot会通过上文环境以及你在classpath中存放的包和你定义的bean来猜测需要哪些配置。

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

@Import(AutoConfigurationPackages.Registrar.class)引入了Registrar类,这个类是AutoConfigurationPackages内部的静态类,只有用到的时候才会被初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}

}

通过register(registry,new PackageImports(metadata).getPackageNames().toArray(new String[0]));}将主程序类包下所有的类都在spring中注册了。注意只有当前包下,不包括子包

image-20210305202401765

使用registerBeanDefinition方法注册了Bean

这里我们重点关注一下@Import(AutoConfigurationImportSelector.class),当import的是importSelector类时,会把该类selectImports()方法返回的Class都定义为bean

1
2
3
4
5
6
7
8
9
10
11
12
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// ...
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

通过AnnotationMetadata对象,该方法可以获取到标注@Import类的各种信息。通过getAutoConfigurationEntry(annotationMetadata)方法就可以获取到需要自动配置的类了。

整理一下

@EnableAutoConfiguration注解首先通过@AutoConfigurationPackage将有该注解的类的包下的全部类都注入spring,然后通过@Import(AutoConfigurationImportSelector.class)判断需要符合条件的配置类,将它们也都注入到Spring中。

2.7 @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
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

// 与basePackages是同一个东西,只是有别名。
// 指定需要扫描的package
@AliasFor("basePackages")
String[] value() default {};

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

// 指定包中的类,这些类会被扫描
Class<?>[] basePackageClasses() default {};

// Bean名字生成器,默认根据类名生成
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

// 探测component的范围
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

// 表示是否要对探测到的component使用代理
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

// 控制有资格被component扫描的类文件
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

// 决定是否之探测component,还是repository、service和controller都探测
boolean useDefaultFilters() default true;

// 指定可以被探测到的类型
Filter[] includeFilters() default {};

// 指定不能被探测到的类型
Filter[] excludeFilters() default {};

// 指定被探测到的bean是否是懒加载
boolean lazyInit() default false;

// 过滤器,用于includeFilters和excludeFilters
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {

// filter的类型
FilterType type() default FilterType.ANNOTATION;

// 跟classes是同一个
// 用来描述哪些类被filter
@AliasFor("classes")
Class<?>[] value() default {};

@AliasFor("value")
Class<?>[] classes() default {};

// filter的模式
String[] pattern() default {};
}

其中@Repeatable表示这个注解可重复执行。这个注解会让被注解类的包及其子包中的类注册当spring中。这是与@EnableAutoConfiguration的不同

image-20210305203929402

SpringBootApplication中它的用法是

1
2
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

这里它指定了几个不可能探测的类。

TypeExcludeFilter指定的是那些已经被BeanFactory和被SpringBootApplication自动扫描的类。

AutoConfigurationExcludeFilter指定排除那些在AutoConfiguration中被注册到spring中的类。

3. @SpringBootApplication的方法

讲完了@SpringBootApplication的方法,现在再来讲一下其内部方法

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
@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 {};

// bean名字生成器
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

// 指定是否使用代理模式
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;

}

看了这么多,可以发现@SpringBootApplication的所有方法都是作用于它的子注解。

4. 总结

通过@SpringBootApplication注解,我们来看一下这个注解可以在项目启动时做哪些事。

  • 通过@SpringBootConfiguration将主程序标记为@Configuration@Configuration内部又有@Component,所以会被扫描到,并且其生成的Bean也会被加入到容器中。
  • 通过@EnableAutoConfiguration先通过@AutoConfigurationPackage将有该注解的类的包下的全部类都注入spring(不包括子包),然后通过@Import(AutoConfigurationImportSelector.class)判断需要自动配置的类,将他们也都注入到Spring中。
  • 通过@ComponentScan,将被标记类的包及其子包下所有的类注册到spring容器(通过exclude排除那些已经被注册过的类)。

@EnableAutoConfiguration@ComponentScan的不同

  • @EnableAutoConfiguration只会注册当前包下的类,而@ComponentScan是当前包及其子包下的类。
  • @EnableAutoConfiguration是通过registerBeanDefinition方法注册bean,而@ComponentScan是在ConfigurationClassParser类中,通过this.componentScanParser.parse注册bean

学习笔记,如有错误欢迎指出,立即改正。