前言:
记录学习历程,在学习笔记中有描述不正确的地方,欢迎小伙伴们评论指正。
参考:
(转载)非常详细的SpringBoot-自动装配原理 - loveCode'shy - 博客园先看看SpringBoot的主配置类: 里面有一个main方法运行了一个run()方法,在run方法中必须要传入一个被@SpringBootApplication注解的类。 @SpringBootAphttps://www.cnblogs.com/hhcode520/p/9450933.html云道小破栈本站是大道七哥的技术分享博客。内容涵盖北漂程序员的生活故事、技术提升、Java后端技术、Spring Boot、 Spring Cloud、微服务架构、大数据演进、高可用架构、中间件使用、系统监控等相关的研究与知识分享。http://www.yund.tech/zdetail.html?type=1&id=2b2a032bd98dfeb5e4113ef6624722d2springboot启动类_仇人太多不方便透露姓名的博客-CSDN博客_springboot启动类我们开发任何一个Spring Boot项目,都会用到如下的启动类@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}从上面代...https://blog.csdn.net/weixin_41688792/article/details/100839204
既然从头梳理学习,那么就从最基础的启动配置类开始吧,看了N篇的文章后,整理一下学习笔记.
正文:
创建好的SpringBoot工程都会有一个启动配置类。如下:
package learn.basics.learnbasics;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LearnBasicsApplication {
public static void main(String[] args) {
SpringApplication.run(LearnBasicsApplication.class, args);
}
}
从上述代码中,首先比较惹人注意的就是启动配置类中的注解(Annotation)@SpringBootApplication和main方法中的SpringApplication.run()方法。
1、SpringApplication
SpringApplication是SpringBoot驱动Spring应用上下文的引导类,它的run()方法是启动Spring应用,实际上是为Spring应用创建并初始化Spring上下文。
2、SpringApplication#run
SpringApplication的静态run()方法中,首先创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法
点进run方法可以看到如下源码:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
其中primarySource就是我们的启动类LearnBasicsApplication。也就是说run()方法需要传入的其中第一个参数就是本身用@SpringBootApplication注解的启动配置类。然后调用第二个run()方法。
在第二个run()方法中,第一是通过构造函数创建SpringApplication对象;第二是调用其run()方法。
2.1、new SpringApplication(primarySources)
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
2.2、new SpringApplication(primarySources).run()
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
3、@SpringBootApplication
进入@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
3.1、@Target({ElementType.TYPE})
@Target:说明了注解(Annotation)所修饰的对象范围。作用与描述注解的使用范围。
取值ElementType有:
1)CONSTRUCTOR:(constructor)用于描述构造器。
2)FIELD:(field)用于描述域。
3)LOCAL_VARIABLE:(local_variable)用于描述局部变量。
4)METHOD:(method)用于描述方法。
5)PACKAGE:(package)用于描述包。
6)PARAMETER:(parameter)用于描述参数
7)TYPE:(type)用于描述类、接口(包括注解类型)或枚举enum声明。
3.2、@Retention(RetentionPolicy.RUNTIME)
@Retention:生命周期。
1)RetentionPolicy.SOURCE:注解只保留在源文件,当JAVA文件被编译成class文件时,注解被遗弃。
2)RetentionPolicy.CLASS:注解只保留到class文件,当JVM加载class文件时被遗弃,也是默认的生命周期。
3)RetentionPolicy.RUNTIME:注解不仅被保存在class文件中,当JVM加载class文件之后依然存在。
3.3、@Documented
@Documented:表明被javadoc工具记录,默认情况下,javadoc是不包含注解的,但如果声明注解时用了@Documented,则会被javadoc工具处理,故注解类型的信息也会被包含在生成的文档中,是一个标记注解,没有成员。
3.4、@Inherited
@Inherited:是一个标记,用来修饰注解。作用是如果一个类用了@Inherited,那么其子类也会集成这个注解。
注:接口(父类的方法)用了@Inherited,其实现类(子类)不会集成这个注解。
当用@Inherited修改的注解的@Retention是RetentionPolicy.RUNTIME,则增强继承性,在反射中可以获取得到。
3.5、@SpringBootConfiguration
进入@SpringBootConfiguration可以看到如下源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@Indexed可以为 Spring 的模式注解添加索引,以提升应用启动性能。
在源码中可以看到@SpringBootConfiguration其本质就是一个@Configuration
即:@SpringBootConfiguration = @Configuration + @EnableAutoConfiguration + @ComponentScan
3.5.1、@Configuration
@Configuration就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
3.5.2、@EnableAutoConfiguration
@EnableAutoConfiguration进去查看源码:
@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 {};
}
由上述代码可以看到,@EnableAutoConfiguration中的核心只有俩个注解@AutoConfigurationPackage(自动配置包)和@Import(导入自动配置的组件)。
@AutoConfigurationPackage进去后可以看到其本质是一个@Import。源码如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
由上述源码可以得到@AutoConfigurationPackage中用@Import给Spring容器中导入一个组件Registrar.class,通过Registrar.class的registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)方法来获取扫描的包路径。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
@Import({AutoConfigurationImportSelector.class})注解给Spring组件导入了一个组件选择器AutoConfigurationImportSelector。
AutoConfigurationImportSelector里面有一个selectImports方法,将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
3.5.3、@ComponentScan
@ComponentScan注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。