威海市建设局官方网站,外国网站的风格,wordpress 添加水印,免费做名片的网站SpringBoot原理分析
作为一个javaer#xff0c;和boot打交道是很常见的吧。熟悉boot的人都会知道#xff0c;启动一个springboot应用#xff0c;就是用鼠标点一下启动main方法#xff0c;然后等着就行了。我们来看看这个main里面。
SpringBootApplication
public class E…SpringBoot原理分析
作为一个javaer和boot打交道是很常见的吧。熟悉boot的人都会知道启动一个springboot应用就是用鼠标点一下启动main方法然后等着就行了。我们来看看这个main里面。
SpringBootApplication
public class ExampleApplication {public static void main(String[] args) {SpringApplication.run(ExampleApplication.class, args);}
}这个类很简单吧有三个注意点。
ExampleApplication就是类名为了规范起见这个类名一般都是xxxApplication。
有一个注解SpringBootApplication。
然后就一个main方法里面使用了SpringApplication的静态方法run()传进去了俩参数。
这就神奇地启动起来了为什么呢
分析的版本SpringBoot 2.7.18
需要结合以前用xml文件来配置Spring容器的形式来对比Boot用注解的形式
1.注解自动配置
目前只知道注解的作用即可至于这些注解是怎么起作用的见后续…
SpringBootApplication
这一节来详细聊一聊这个注解。SpringBootApplication 是 Spring Boot 框架中的一个核心注解用于简化 Spring Boot 应用的配置和启动
它的作用
SpringBootApplication 标注在应用的主类上用于启动 Spring Boot 应用。它启用了 Spring Boot 的自动配置机制根据项目的依赖自动配置 Spring 应用。组件扫描它启用了组件扫描自动发现并注册带有 Component、Service、Repository、Controller 等注解的类。配置类它标记该类为 Spring 的配置类相当于 Configuration 注解。
点进去看看该注解的结构如下
Target(ElementType.TYPE)
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited // 四个元注解
SpringBootConfiguration
EnableAutoConfiguration //启用SpringBoot的自动配置机制。SpringBoot会根据类路径中的依赖自动配置应用。例如如果类路径中有spring-boot-starter-webSpringBoot会自动配置Tomcat和SpringMVC。ComponentScan(excludeFilters { Filter(type FilterType.CUSTOM, classes TypeExcludeFilter.class),Filter(type FilterType.CUSTOM, classes AutoConfigurationExcludeFilter.class) }) // 启用组件扫描并排除一些特定的过滤器。默认是注解标注的类所在包及其子包。实际上是不是就是主启动类所在包及其子包
public interface SpringBootApplication {
..............
}// 发现这是一个组合注解SpringBootConfiguration 注解主要作用是标记一个类为 Spring Boot 的配置类与 Configuration 类似但它专门用于 Spring Boot 应用。这个注解和ComponentScan注解结合到一起达成了如下效果。左边是xml文件配置形式右边是SpringBoot注解的形式这俩注解在一起就是扫描主启动类所在包及其子包下的被Controller、Service、…标注的类将他们归到Spring容器里面去。 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 {};}发现Import注解。这个注解的作用是干啥的这里给出GPT的回答Import 是 Spring 框架提供的一个注解用于将一个或多个配置类Configuration 类或组件类导入到当前的 Spring 应用上下文中。它可以用来显式地引入其他配置类或 Bean 定义从而实现模块化配置和代码复用。
这里有一个非常重要的东西那就是当前的Spring上下文也就是主启动类所在包及其子包Import(AutoConfigurationImportSelector.class)这句话的意思是将SpringBoot官方写的AutoConfigurationImportSelector类导入到当前Spring上下文中在当前Spring上下文注册这样一个bean。
AutoConfigurationImportSelector探究
public interface ImportSelector {String[] selectImports(AnnotationMetadata importingClassMetadata);
}
// 实现了DeferredImportSelector接口DeferredImportSelector实现了ImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {...........AutoConfigurationEntry autoConfigurationEntry getAutoConfigurationEntry(annotationMetadata); // 1.进去return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}// 2.这个方法protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes getAttributes(annotationMetadata);// 3.进入这里ListString configurations getCandidateConfigurations(annotationMetadata, attributes);...........}// 4. 3调用的这个方法protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {ListString configurations new ArrayList(// 5. 继续往下SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);Assert.notEmpty(configurations,No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.);return configurations;}// 6. 5处调用的方法public static ListString loadFactoryNames(Class? factoryType, Nullable ClassLoader classLoader) {...... // loadSpringFactoriesreturn loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}// 7// 最终可以在loadSpringFactories方法里面看到这样一行代码//EnumerationURL urls classLoader.getResources(FACTORIES_RESOURCE_LOCATION);// public static final String FACTORIES_RESOURCE_LOCATION META-INF/spring.factories;// 结合官方给的解释The location to look for factories. Can be present in multiple JAR files. 寻找工厂的位置。可以存在于多个 JAR 文件中。
}结合idea打断点调试我们发现getCandidateConfigurations扫描到了引入的所有jar包的META-INF/spring.factories共有156个xxxAutoConfigure类。但是这些都会用到吗别忘了SpringBoot有个非常强大的特点那就是导入场景、对应的场景才会生效
继续往下走请看下图发现只有50个了例如上图里面的amqp由于项目中没有用到故在这里就没有了经过了filter过滤
以RabbitMq自动配置为例
AutoConfiguration
ConditionalOnClass({ RabbitTemplate.class, Channel.class }) // 这两个类存在时才生效项目里面没导入相关jar包故RabbitAutoConfiguration不会被导入Spring容器生效下面就来说这个注解
EnableConfigurationProperties(RabbitProperties.class)
Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class })
public class RabbitAutoConfiguration {................
}ConditionalOnClass
先看看其源码
Target({ ElementType.TYPE, ElementType.METHOD })
Retention(RetentionPolicy.RUNTIME)
Documented
Conditional(OnClassCondition.class) // 这里***
public interface ConditionalOnClass {Class?[] value() default {};String[] name() default {};
}ConditionalOnClass注解的作用是当项目中存在某个类时才会使标有该注解的类或方法生效
先看我们的主类**【1. 此时没有引入test.demo.test.list.Student所在的maven依赖】**
/* 下面是这俩类
public class Cat {private Integer id;private String name;
}
public class Dog {private Integer id;private String name;
}*/SpringBootApplication
MapperScan(com.feng.tackle.dao)
public class DateApplication {public static void main(String[] args) {ConfigurableApplicationContext context SpringApplication.run(DateApplication.class, args);ConfigurableListableBeanFactory beanFactory context.getBeanFactory();IteratorString iterator beanFactory.getBeanNamesIterator();while (iterator.hasNext()) {String name iterator.next();if ( name.equals(dog01) || name.equals(cat01) ) System.out.println(name); // 看看有没有这两个名字的bean}}
}
// 最后的输出结果
/*
cat01
*/在配置类中我们这样做
Configuration
public class ConditionTestConfig {ConditionalOnClass(name test.demo.test.list.Student) // 类路径有这个类就往容器中放入该beanBeanpublic Dog dog01() {return new Dog(2, 汪汪汪);}ConditionalOnMissingClass(test.demo.test.list.Student) // 类路径没有这个类就往容器中放入该beanBeanpublic Cat cat01() {return new Cat(1, 喵喵喵);}
}main最后的输出结果是cat01。test.demo.test.list.Student是另一个maven项目里面的类。
【2. 项目中引入Student所在的maven】
dependencygroupIdcom.feng.test/groupIdartifactIdtest-demo/artifactIdversion1.0-SNAPSHOT/version
/dependency再次启动main输出结果dog01
通过这个例子我们可以知道SpringBoot自动配置导入具体starter对应的场景才会生效。【由于没有导入MQ的starter故此配置类不会生效】
总结
**自动配置 **
以spring-boot-starter-web为例子引入该启动器他的父级pom里面有spring-boot-starterspring-boot-starter的父级pom有spring-boot-autoconfigure。
spring-boot-autoconfigure里面有Spring官方定义的所有场景。 EnableAutoConfiguration 注解这是自动配置的入口通常由 SpringBootApplication 注解组合引入。spring.factories 文件Spring Boot 通过 META-INF/spring.factories 文件加载自动配置类。条件化配置通过 Conditional 系列注解如 ConditionalOnClass、ConditionalOnMissingBean 等实现按需加载配置。
自动配置的流程
加载自动配置类
SpringBootApplication 注解该注解是一个组合注解包含了 EnableAutoConfiguration用于启用自动配置。EnableAutoConfiguration 的作用该注解会通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件中定义的自动配置类。
条件化配置
自动配置类通常使用 Conditional 系列注解来控制是否生效
Configuration(proxyBeanMethods false)
ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
ConditionalOnMissingBean(type io.r2dbc.spi.ConnectionFactory)
EnableConfigurationProperties(DataSourceProperties.class)
Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {// 自动配置逻辑
}
//如果类路径中存在 DataSource 和 EmbeddedDatabaseType且容器中没有 ConnectionFactory Bean则自动配置 DataSource。加载配置属性
EnableConfigurationProperties自动配置类通常会通过该注解加载配置属性。application.properties 或 application.ymlSpring Boot 会读取这些配置文件中的属性并将其绑定到对应的配置类中。
ConfigurationProperties(prefix spring.datasource)
public class DataSourceProperties {private String url;private String username;private String password;// getters and setters
}注册bean
自动配置类通过 Bean 方法向容器中注册 Bean。这些 Bean 通常是框架的核心组件如 DataSource、DispatcherServlet、SecurityFilterChain 等。
2. 启动过程
SpringApplication.run(ExampleApplication.class, args);这一行代码做了啥这才是重点
从run开始看一层一层往下面点进去
// 1.
public static ConfigurableApplicationContext run(Class? primarySource, String... args) { // 会返回一个正在运行的Spring容器return run(new Class?[] { primarySource }, args); // 调用重载的run方法
}
// 2.
public static ConfigurableApplicationContext run(Class?[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args); // 先调用该构造方法,然后再调用run方法将程序参数传进去
}
// 3.
public SpringApplication(Class?... primarySources) {this(null, primarySources); //primarySource 是传入的主配置类通常带有 SpringBootApplication 注解的类Spring Boot 会将其作为配置源。
}// 4. [构造方法] 构造方法
public SpringApplication(ResourceLoader resourceLoader, Class?... primarySources) {........ // 见下一节构造方法
}先调用该构造方法,然后再调用run方法
①构造方法
public SpringApplication(ResourceLoader resourceLoader, Class?... primarySources) {this.resourceLoader resourceLoader; // null的Assert.notNull(primarySources, PrimarySources must not be null);this.primarySources new LinkedHashSet(Arrays.asList(primarySources)); // 一、设置主配置类this.webApplicationType WebApplicationType.deduceFromClasspath(); // 二、推断应用类型this.bootstrapRegistryInitializers new ArrayList( // 三、getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 四、setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 五、this.mainApplicationClass deduceMainApplicationClass(); // 六、通过堆栈信息推断出主类即 main 方法所在的类。
}1.推断应用类型
WebApplicationType.deduceFromClasspath() 方法会根据类路径中是否存在特定的类来推断应用类型。可能的类型包括 WebApplicationType.SERVLET基于 Servlet 的 Web 应用如 Spring MVC。WebApplicationType.REACTIVE基于 Reactive 的 Web 应用如 Spring WebFlux。WebApplicationType.NONE非 Web 应用。
2.加载并初始化 BootstrapRegistryInitializer 实例
分析一下核心方法getSpringFactoriesInstances下面两个都会调用这个。
private T CollectionT getSpringFactoriesInstances(ClassT type, Class?[] parameterTypes, Object... args) {ClassLoader classLoader getClassLoader();// Use names and ensure unique to protect against duplicatesSetString names new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 下一步ListT instances createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}// SpringFactoriesLoader.loadFactoryNames(type, classLoader) 到这儿来了
public static ListString loadFactoryNames(Class? factoryType, Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse classLoader;if (classLoaderToUse null) {classLoaderToUse SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName factoryType.getName();return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}//loadSpringFactories(classLoaderToUse)
private static MapString, ListString loadSpringFactories(ClassLoader classLoader) {// static final MapClassLoader, MapString, ListString cache new ConcurrentReferenceHashMap();// 从缓存map中拿到对应classLoader的map如果该classLoader已经存在了就不用走下面的了MapString, ListString result cache.get(classLoader);if (result ! null) {return result;}result new HashMap();try {/*public static final String FACTORIES_RESOURCE_LOCATION META-INF/spring.factories;结合官方给的解释The location to look for factories. Can be present in multiple JAR files. 寻找工厂的位置。可以存在于多个 JAR 文件中。*/EnumerationURL urls classLoader.getResources(FACTORIES_RESOURCE_LOCATION);..............return result;
}可以看到这里在第一章《注解自动配置》也出现了这个如今在SpringApplication的构造方法底层也调用到了这里。二者有什么区别或者联系呢
SpringFactoriesLoader.loadFactoryNames是Spring Boot 提供的工具方法用于从 META-INF/spring.factories 文件中加载指定类型的配置类。
两者都依赖于 META-INF/spring.factories 文件该文件是 Spring Boot 自动配置和扩展机制的核心配置文件。两者都通过类路径扫描加载所有 META-INF/spring.factories 文件并解析出指定类型的配置类。
1调用位置
AutoConfigurationImportSelector 位于 org.springframework.boot.autoconfigure 包中。是 Spring Boot 自动配置的核心组件之一负责加载自动配置类。在 Spring 容器的配置类解析阶段被调用具体是在 EnableAutoConfiguration 注解的处理过程中。 SpringApplication 位于 org.springframework.boot 包中。是 Spring Boot 应用的启动类负责初始化应用上下文、加载配置等。在应用启动时被调用。
2加载的配置类型
AutoConfigurationImportSelector 主要加载 META-INF/spring.factories 文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 键下的自动配置类。这些配置类用于实现 Spring Boot 的自动配置功能如 DataSourceAutoConfiguration、WebMvcAutoConfiguration 等。 SpringApplication 加载多种类型的配置类包括 ApplicationContextInitializerApplicationListenerBootstrapRegistryInitializer 这些配置类用于初始化应用上下文、监听应用事件等。
3调用时机
AutoConfigurationImportSelector 在 Spring 容器解析配置类时调用具体是在 ConfigurationClassPostProcessor 处理 Configuration 类时。属于 Spring 容器初始化的早期阶段。 SpringApplication 在应用启动时调用具体是在 SpringApplication 的构造方法或 run 方法中。属于应用启动的早期阶段。
4返回值的使用
AutoConfigurationImportSelector 返回的自动配置类会被 Spring 容器加载并处理最终生成相应的 Bean 定义。这些配置类通常包含 Configuration 注解和 Conditional 注解用于按需加载 Bean。 SpringApplication 返回的配置类会被直接实例化并注册到应用中。例如ApplicationContextInitializer 会被调用以初始化应用上下文ApplicationListener 会被注册以监听应用事件。
3.加载并设置 ApplicationContextInitializer 实例
同2
4.加载并设置 ApplicationListener 实例
同2
总结
BootstrapRegistryInitializer用于应用启动的最早阶段初始化引导阶段的组件。ApplicationContextInitializer用于在 ApplicationContext 创建之后、刷新之前对上下文进行自定义初始化。ApplicationListener用于监听应用生命周期中的事件并在特定阶段执行逻辑。
这三者共同扩展了 Spring Boot 应用的启动过程提供了灵活的扩展点可以满足不同场景下的需求。
5.三者的实现
分别编写实现类
public class MyBootstrapInit implements BootstrapRegistryInitializer {Overridepublic void initialize(BootstrapRegistry registry) {System.out.println(【MyBootstrapInit】--------------方法执行了);}
}public class MyApplicationContextInit implements ApplicationContextInitializerConfigurableApplicationContext {Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println(【MyApplicationContextInit】------------ applicationContext.getApplicationName());}
}public class MyContextListener implements ApplicationListenerApplicationStartedEvent { // 该监听器对此事件感兴趣Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {System.out.println(【MyContextListener】--监听器-- event.getSource());}
}然后在resource目录下面创建META-INF在其中的spring.factories中
org.springframework.boot.BootstrapRegistryInitializercom.feng.tackle.config.source.MyBootstrapInit
org.springframework.context.ApplicationContextInitializercom.feng.tackle.config.source.MyApplicationContextInit
org.springframework.context.ApplicationListenercom.feng.tackle.config.source.MyContextListener启动应用
疑问 我可以在这三者的实现类上面加Configuration注解不要spring.factories可以实现类似效果吗
答案是只有ApplicationListener可以。为什么请看下面的run方法里面
②run
public ConfigurableApplicationContext run(String... args) {// 记录应用启动的开始时间用于后续计算启动耗时。long startTime System.nanoTime(); //创建一个 BootstrapContext用于在应用启动的早期阶段提供一些基础设施支持如环境配置、属性源等DefaultBootstrapContext bootstrapContext createBootstrapContext();ConfigurableApplicationContext context null;// 设置系统属性 java.awt.headless确保应用在没有图形界面环境如服务器中也能正常运行configureHeadlessProperty();/*获取所有 SpringApplicationRunListener 实例用于监听应用启动的各个阶段。调用 listeners.starting()通知监听器应用正在启动。*/SpringApplicationRunListeners listeners getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {ApplicationArguments applicationArguments new DefaultApplicationArguments(args);// 加载和配置应用的环境Environment包括配置文件如 application.properties 或 application.yml、命令行参数等。ConfigurableEnvironment environment prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);// 打印 Spring Boot 的启动 Banner默认或自定义。Banner printedBanner printBanner(environment);// 根据应用类型Servlet、Reactive 等创建相应的 ApplicationContext。context createApplicationContext();context.setApplicationStartup(this.applicationStartup);// 准备应用上下文prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 调用 AbstractApplicationContext.refresh() 方法完成 Bean 的实例化、依赖注入和初始化。refreshContext(context);afterRefresh(context, applicationArguments);// 计算启动耗时并记录日志。Duration timeTakenToStartup Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}listeners.started(context, timeTakenToStartup);// 执行所有 ApplicationRunner 和 CommandLineRunner 的实现类用于在应用启动后执行自定义逻辑。callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {// 计算应用启动到就绪的总耗时。Duration timeTakenToReady Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;
}这个run这里分析得不够深入。受篇幅影响在后续文章中再来分析。
end. 参考
尚硅谷雷丰阳老师的课