书籍引用: 《 SpringBoot 揭秘 快速构建微服务体系》 王福强 著
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.5.RELEASE</version> <relativePath/> </parent>
启动类 - 标准结构
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
第一次分解 注解分解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
第二次分解 类分解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public class SpringbootApplication {
}
public class MySpringbootRun {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
依据分解:启动类 MySpringbootRun
就是一个标准的 Standalone 类型Java程序的main函数启动类,没有什么特别的东西。
这是一种化繁为简的做法,springboot一站式复合Annotation显然更加方便
最上层复合注解
@SpringBootApplication
它的下层关键注解有三个
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
SpringBootConfiguration
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@SpringBootConfiguration 是 @Configuration的上层注解
这里的@Configuration 并不陌生,它就是JavaConfig形式的 Spring IoC容器的配置类使用的@Configuration,SpringBoot 内在就是一个Spring应用,必然的需要加载某个IoC容器的配置,这里的启动类标注了@Configuration 之后,本身就是一个IoC容器的配置类,观察第二次分解的结构。
EnableAutoConfiguration
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY="spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
在Spring框架中有各种@Enable开头的Annotation定义,比如那些@EnableScheduling、@EnableCaching等。
@EnableAutoConfiguration的理念和做事方式也是一脉相承的,简单概括一下就是,借助@Import 的支持,收集和注册特定场景相关的bean的定义:
- @EnableScheduling 是通过 @Import 将Spring调度框架相关的bean定义加载到 IoC 容器。
- @EnableMBeanExport 是借助 @Import 将JMX 相关的bean定义加载到 IoC 容器。
而@EnableAutoConfiguration 也是借助了 @Import 的帮助,将所有复合自动配置条件的bean定义加载到 IoC 容器,仅此而已!
@Import({AutoConfigurationImportSelector.class})
@EnableAutoConfiguration 借助AutoConfigurationImportSelector.class 可以帮助SpringBoot应用将所有符合条件的 @Configuration 配置都加载到当前SpringBoot创建并使用的IoC容器,就像是一个积木一样,把需要的零件拼接进来,不需要的排除在外。
自动配置的关键 是Spring框架原有的一个工具类 SpringFactoriesLoader
import org.springframework.core.io.support.SpringFactoriesLoader;
SpringFactoriesLoader 属于Spring 框架私有的一种扩展方案(类似于Java的SPI方案 Java.util.ServiceLoader),其主要功能就是从指定的配置文件 META-INF/spring.factories 加载配置,spring.factories 是一个典型的java properties 文件,配置格式为 Key = Value 形式, Key 和 Value 都是Java类型的完整类名。然后框架就可以根据某个类型作为Key来查找对应的类型名称列表了。
META-INF/spring.factories 配置文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
..................
AutoConfigurationImportSelector 自动配置导入选择器
public class AutoConfigurationImportSelector//......
//......
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
//......
SpringFactoriesLoader 工具类
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//......
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader)
//......
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader
//......
对于@EnableAutoConfiguration 来说,SpringFactoriesLoader 的用途稍有不同一些,本意是为了提供 SPI 扩展的场景,而在 @EnableAutoConfiguration 的场景中,它更多提供一种配置查找的功能支持,根据org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为查找的Key,获取对应的一组 @Configuration类。
因此,@EnableAutoConfiguration 自动配置的魔法实其实就是从classpath 中搜寻所有META-INF/spring.factories配置文件,并将其中EnableAutoConfiguration 对应的配置项通过反射(Java Reflection)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总加载到 IoC 容器中。
ComponentScan
可有可无的 ComponentScan !原则上来说,作为Spring框架里的老一辈革命家,@ComponentScan 的功能其实就是自动扫描并加载符合条件的组件或Bean定义,最终将这些bean定义加载到容器中。加载bean定义到Spring的IoC容器,我们可以手工单个注册,不一定非要通过批量的自动扫描完成,所以说@ComponentScan是可有可无的。
SpringApplication:SpringBoot 程序启动的一站式解决方案
SpringApplication 将一个典型的Spring 应用启动的流程”模板化“(这里是动词),在没有特殊需求的情况下,默认的执行流程就可以满足需求;但有特殊需求的情况下,SpringApplication 在合适的流程节点开放了一系列不同类型的扩展点,我们可以通过这些扩展点对SpringBoot 程序的启动和关闭过程进行扩展。
SpringApplication 执行流程
SpringApplication 的 run 方法的实现是主要的执行线路,该方法的主要流程大体有以下几点:
-
如果我们使用的是 SpringApplication的静态run方法,这个方法里面首先需要创建一个SpringApplication对象实例,然后调用这个创建好的 SpringApplication 的实例run方法。在 SpringApplication 实例化时提前做几件事:
根据classpath 里面是否存在某个特征类org.springframework.web.context.WebApplicationContext来决定是否应该创建一个为WEB 应用所使用的 ApplicationContext类型,还是创建一个标准Standalone应用使用的类型。
使用 SpringFactoriesLoader 在应用的 classpath中查找并加载所有可用的 ApplicationContextInitializer
使用 SpringFactoriesLoader 在应用的 classpath中查找并加载所有可用的 ApplicationListener
推断并设置main方法的定义类
# spring.factories # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ ......
-
SpringApplication 实例初始化完成并且完成设置后,开始执行 run 方法的逻辑了,方法执行开始,首先遍历执行所有通过 SpringFactoriesLoader 可以查找到并加载的 SpringApplicationRunListener, 调用它们的 started() 方法,告诉这些SpringApplicationRunListener ,“嘿,SpringBoot 应用要开始执行了"。
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
public class EventPublishingRunListener //...... //...... public void started(ConfigurableApplicationContext context) { context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context)); AvailabilityChangeEvent.publish(context, LivenessState.CORRECT); }
创建并配置当前 SpringBoot 应用将要使用的 Environment(包括配置要使用的 PropertySource 以及 Profile)
遍历调用所有 SpringApplicationRunListener 的 environmentPrepared() 的方法,告诉它们: "当前SpringBoot应用使用的 Environment 准备好了"!
-
如果 SpringApplication 的showBanner 属性被设置为 true;则打印banner,这一步不用关心。
# application.properties 关闭打印banner spring.main.banner-mode=off
根据用户是否明确设置了 applicationContextClass 类型以及初始化阶段的推断结果,决定该为当前 SpringBoot 应用创建什么类型的 ApplicationContext 并创建完成,然后根据条件决定是否添加 ShutdownHook,决定是否使用自定义的 BeanNameGenerator, 决定是否使用自定义的 ResourceLoader,当然,最重要的,将之前准备好的 Environment 设置给创建好的 ApplicationContext 使用。
-
ApplicationContext 创建好之后,SpringApplication 会再次借助 SpringFactoriesLoader,查找并加载 classpath 中所有可用的 ApplicationContextInitializer ,然后遍历调用这些类中的initialize(applicationContext) 方法对已经创建好的ApplicationContext 进一步的处理。
# Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\
public class ConfigurationWarningsApplicationContextInitializer//...... public void initialize(ConfigurableApplicationContext context) { //...... }
public class ContextIdApplicationContextInitializer//...... public void initialize(ConfigurableApplicationContext applicationContext) { //...... }
都是对ApplicationContext 的进一步处理
-
遍历调用所有 SpringApplicationRunListener 的 contextPrepared 方法,通知它们:"SpringBoot应用使用的ApplicationContext 准备 好了!
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
public class EventPublishingRunListener//......
public void contextPrepared(ConfigurableApplicationContext context) {
this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
最核心的一步 ,将之前通过 @EnableAutoConfiguration 获取的所有配置以及其他形式的 IoC 容器配置加载到已经准备完毕的 ApplicationContext. 这是核心的应用容器
-
遍历调用所有 SpringApplicationRunListener 的 contextLoaded 方法,告知所有 SpringApplicationRunListener ,ApplicationContext 装填 完毕!
public class EventPublishingRunListener//...... public void contextLoaded(ConfigurableApplicationContext context) { //...... }
-
调用 ApplicationContext 的 refresh() 方法,完成 IoC 容器可用的最后一道工序。
// interface public interface ConfigurableApplicationContext//...... void refresh() throws BeansException, IllegalStateException; // AbstractApplicationContext implements ConfigurableApplicationContext public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext public void refresh() throws BeansException, IllegalStateException { synchronized(this.startupShutdownMonitor) { //...... } } // package org.springframework.boot // SpringApplication public class SpringApplication //...... protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); this.refresh((ConfigurableApplicationContext)applicationContext); } protected void refresh(ConfigurableApplicationContext applicationContext) { applicationContext.refresh(); }
-
查找当前 ApplicationContext 中是否注册有 CommandLineRunner, 如果有,则遍历执行它们。
package org.springframework.boot; @FunctionalInterface public interface CommandLineRunner { void run(String... args) throws Exception; }
自定义一个类去继承它并打印输出
@Component
public class MyCommand implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner Running");
}
}
> 启动SpringBoot应用可以在控制台中看到输出
- 正常情况下,遍历执行 SpringApplicationRunListener 的 finished() 方法,告知它们:“搞定”。(如果整个过程出现异常,则依然调用所有 finished() 方法,只不过这种情况下会将异常信息一并传入处理)
到这一个完整的 SpringBoot 应用启动完毕~!!
把所有流程压缩精简:
- 收集各种条件和回调接口 通告 started()
- 创建并准备 Environment 通告 environmentPrepared()
- 创建并初始化 ApplicationContext,设置Environment ,加载配置等
- 通告 contextPrepared()
- 通告 contextLoaded()
- 调用 ApplicationContext 的 refresh() 方法
- 执行 CommandLineRunner
- 最后通告 finished()
SpringApplicationRunListener
SpringApplicationRunListener 是一个只有 SpringBoot应用的main 方法执行过程中接收不同执行时点事件通知的监听者:
// package org.springframework.boot;
public interface SpringApplicationRunListener {
default void starting() {
}
default void environmentPrepared(ConfigurableEnvironment environment) {
}
default void contextPrepared(ConfigurableApplicationContext context) {
}
default void contextLoaded(ConfigurableApplicationContext context) {
}
default void started(ConfigurableApplicationContext context) {
}
default void running(ConfigurableApplicationContext context) {
}
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
基本没什么常见的场景需要自己实现一个 SpringApplicationRunListener ,即使 SpringBoot 默认只是实现了一个EventPublishingRunListener ,用于在 SpringBoot 启动的不同时点发布不同的应用事件类型(ApplicationEvent),如果有哪些 ApplicationListener 对这些应用事件感兴趣,则可以接收并处理。
假设 我们真的有场景需要自己定义一个 SpringApplicationRunListener 实现,任何一个实现类的构造方法需要有两个构造参数,一个构造参数是 SpringApplication ,另一个就是 args 参数列表的 String[]
public EventPublishingRunListener(SpringApplication application, String[] args) {
之后我们可以通过 SpringFactoriesLoader 立下的规矩,在META-INF/spring.factories 文件中修改配置
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.songlin.springboot.demo.DemoSpringApplicationRunListener
之后 SpringApplication 就会在运行的时候调用它
ApplicationListener
ApplicationListener 其实是老面孔,属于Spring 框架对Java 中实现的监听者模式的一种框架实现。
Spring中ApplicationListener的使用
背景
ApplicationListener是Spring事件机制的一部分,与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制。
如果容器中存在ApplicationListener的Bean,当ApplicationContext调用publishEvent方法时,对应的Bean会被触发。这一过程是典型的观察者模式的实现。
ApplicationListener源码
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
ContextRefreshedEvent事件的监听
以Spring的内置事件ContextRefreshedEvent为例,当ApplicationContext被初始化或刷新时,会触发ContextRefreshedEvent事件,下面我们就实现一个ApplicationListener来监听此事件的发生。
@Component // 需对该类进行Bean的实例化
public class LearnListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 打印容器中出事Bean的数量
System.out.println("监听器获得容器中初始化Bean数量:" + event.getApplicationContext().getBeanDefinitionCount());
}
}
这便完成了一个事件监听类的实现和实例化
自定义事件及监听
首先自定义事件:NotifyEvent
public class NotifyEvent extends ApplicationEvent {
private String email;
private String content;
public NotifyEvent(Object source) {
super(source);
}
public NotifyEvent(Object source, String email, String content) {
super(source);
this.email = email;
this.content = content;
}
// 省略getter/setter方法
}
定义监听器NotifyListener:
@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {
@Override
public void onApplicationEvent(NotifyEvent event) {
System.out.println("邮件地址:" + event.getEmail());
System.out.println("邮件内容:" + event.getContent());
}
}
监听器通过@Component注解进行实例化,并在onApplicationEvent中打印相关信息。
单元测试类:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {
@Autowired
private WebApplicationContext webApplicationContext;
@Test
public void testListener() {
NotifyEvent event = new NotifyEvent("object", "pinephp@gmail.com", "This is the content");
webApplicationContext.publishEvent(event);
}
}
执行单元测试,会发现事件发布之后,监听器方法被调用,日志被打印出来
如果我们要为SpringBoot 应用添加自定义的 ApplicationListener,有两种方式:
-
通过 SpringApplication.addListeners(...) 或者 SpringApplication.setListeners(...)方法添加一个或者多个自定义的 ApplicationListener;
package org.springframework.boot; public class SpringApplication { private List<ApplicationListener<?>> listeners; //...... public void setListeners(Collection<? extends ApplicationListener<?>> listeners) { this.listeners = new ArrayList(listeners); } public void addListeners(ApplicationListener<?>... listeners) { this.listeners.addAll(Arrays.asList(listeners)); }
-
借助 SpringFactoriesLoader 机制,在META-INF/spring.factories文件中添加配置 在factories中有默认配置
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ ......
ApplicationContextInitializer
ApplicationContextInitializer 也是 Spring 框架原有的概念,这个类的主要目的就是在 ConfigurableApplicationContext 类型(或者子类型)的ApplicationContext 做 refresh之前,允许我们对 ConfigurableApplicationContext 的实例做进一步的设置或者处理。
实现一个ApplicationContextInitializer 很简单,它只有一个方法需要实现:
package org.springframework.context;
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
一般情况下,我们基本不会自定义一个ApplicationContextInitializer,SpringBoot 框架默认注册了几个实现
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
如果我们真的需要自定义一个 ApplicationContextInitializer ,那么只要像上面这样,通过SpringFactoriesLoader 机制,在META-INF/spring.factories 添加,或者通过 SpringApplication.addInitializers方法设置即可
package org.springframework.boot;
public class SpringApplication {
//......
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList(initializers);
}
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
}
CommandLineRunner
CommandLineRunner 属于 SpringBoot 应用特定的回调扩展接口
其中有两点需要关注
- 所有的CommandLineRunner 的执行时间点在 SpringBoot 应用的ApplicationContext 完全初始化开始工作之后,也可以认为是 main 方法执行完成之前的最后一步。
- 只要存在于当前 SpringBoot 应用的ApplicationContext 中的任何CommandLineRunner ,都会被加载执行。不管是手动注册,还是自动扫描到IoC容器里。
与其他几个扩展点接口类型相似,建议 CommandLineRunner 的实现类使用 @org.springframework.core.annotation.Order 注解进行标注 或者实现 org.springframework.core.Ordered接口,便于对它们的执行顺序进行调整,这其实十分重要,我们不希望顺序不当的CommandLineRunner 实现类阻塞了后面其他CommandLineRunner 的执行。
细谈 自动配置
大概说明自动配置是: @EnableAutoConfiguration 借助 SpringFactoriesLoader 工具类 将标注了 @Configuration 的JavaConfig 类 ”一股脑“ 的汇总并加载到最终的 ApplicationContext 中。
实际上,基于@EnableAutoConfiguration 的自动配置功能拥有更加强大的调控能力,通过配合比如基于条件的配置能力,或者调整加载顺序,我们可以对自动配置进行更加细粒度的调整和控制。
基于条件的自动配置
基于条件的自动配置来源于 Spring 框架中”基于条件的配置" 这一特性。在 Spring框架中,我们可以使用@Conditional 这个 注解 配合 @Configuration 或者 @Bean 等注解来干预一个配置或者 bean定义是否能够生效,其最终实现的效果或者语义类似于如下伪代码
if( 符合@Conditional 规定的条件 ){
加载当前配置( enable current Configuration )
或者注册当前bean 定义
}
要实现基于条件的配置,我们只要通过 @Conditional 指定自己的 Condition 实现类就可以了(Type的标注和方法的标注)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
最主要的是,@Conditional 可以作为一个 Meta Annotation 用来标注其他 Annotation实现类,从而构建各色的复合注解类
在SpringBoot 中有很多这样的复合注解
@ConditionalOnClass()
@ConditionalOnMissingBean()
@ConditionalOnProperty()
//......
//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Condition 是一个接口 需要实现Condition接口,并重写方法来自定义match规则 true 通过,false 不通过
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
这意味着所有的依赖模块都是按需加载的,只有符合某些特定田间,这些依赖模块才会生效,所谓的智能配置
调整自动配置的顺序
我们可以对当前提供的配置或者组件的加载顺序进行相应调整,从而让这些配置或者组件之间的依赖分析和组装可以顺利完成。
我们可以使用 @AutoConfigureAfter 或者 @AutoConfigureBefore 让当前配置或者组件在某个其他组件之前或者组件之后进行。
package org.springframework.boot.autoconfigure.jdbc;
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({JdbcProperties.class})
@Import({JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class})
public class JdbcTemplateAutoConfiguration {
public JdbcTemplateAutoConfiguration() {
}
}
总结
以上是对 SpringBoot 的核心组件完成了基本的剖析,大部分东西都是Spring 框架背后原有的一些概念和实践方法,SpringBoot 只是在这些概念和实践方法上对特定的场景进行了固化和升华。