springboot(二)自动化配置源码解析
@EnableAutoConfiguration 是开启自动配置的注解,在创建的 SpringBoot 项目中并不能直接看到此注解,它是由组合注解@SpringBootApplication 引入的。
让我们先从程序的启动类开始分析. 以下将分成三个部分来解析:
1. @SpringBootApplication注解
2. SpringApplication类的功能
3. run()方法
一. 先谈谈启动类和@SpringBootApplication 注解
@SpringBootApplication
public class SpringLearnApplication {public static void main(String[] args) {SpringApplication. run(DemoApplication. class, args);}
}
Spring Boot 项目创建完成会默认生成-个*Application 的入口类。 在默认情况下,无论是通过 IDEA 还是通过官方创建基于 Maven 的 Spring Boo 项目,入口类的命名规则都是artifactld+Application。
这个启动类中最重要的就是@SpringBootApplication注解. 它是 Spring Boot 项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableAutoConfiguration 开启了自动配置。
@SpringBootApplication
@Target(ElementType . TYPE)
@Retent ion(Retent ionPolicy . 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 = "basePackageClass")Class<?>[] scanBasePackageClasses() default {};//指定是否代理@Bean 方法以强制执行 bean 的生命周期行为@AliasFor( annotation = Configuration.class)boolean proxyBeanMethods() default true ;
}
通过源代码可以看出,该注解提供了以下成员属性(注解中的成员变量以方法的形式体现)。
- exclude:根据类(Class) 排除指定的自动配置,该成员属性覆盖了@SpringBoot-Application中组合的@ EnableAutoConfiguration 中定义的 exclude 成员属性。
- excludeName :根据类名排除指定的自动配置,覆盖了@ EnableAutoConfiguration 中的excludeName 的成员属性。
- scanBasePackages:指定扫描的基础 package,用于激活@Component 等注解类的初始化。
- scanBasePackageClasses:扫描指定的类,用于组件的初始化。
- proxyBeanMethods:指定是否代理@ Bean 方法以强制执行 bean 的生命周期行为。此功能需要通过运行时生成 CGLIB 子类来实现方法拦截。该子类有一定的限制,比如配置类及其方法不允许声明为 final 等。
proxyBeanMethods 的默认值为 true,允许配置类中进行 inter-beanreferences (bean 之 间的引用)以及对该配置的@Bean 方法的外部调用。如果@Bean 方法都是自包含的,并且仅提供了容器使用的普通工程方法的功能,则可设置为 false,避免处理 CGLIB 子类。SpringBoot 2.2 版本上市后新增该成员属性,后面章节涉及的自动配置类中基本都会用到proxyBeanMethods,一 般情况下都配置为 false。
通过以上源代码我们会发现,Spring Boot 中大量使用了@AliasFor 注解,该注解用于桥接到其他注解,该注解的属性中指定了所桥接的注解类。如果点进去查看,会发现@SpringBootApplication 定 义的属性在其他注解中已经定义过了。之所以使用@AliasFor注解并重新在@SpringBootApplication 中定义,更多是为了减少用户使用多注解带来的麻烦。
@SpringBootApplication注解的组合结构如下图:
@SpringBootApplication除 了组合元注解之外,其核心作用还包括:激活SpringBoot 自 动 配 置 的 @EnableAutoConfiguration 、 激 活 @Component 扫 描 的@ComponentScan、激活配置类的@Configuration。其中@ComponentScan,@Configuration在Spring中常用到, 这里分析一下@EnableAutoConfiguration的功能.
注解@EnableAutoConfiguration功能解析
在未使用 Spring Boot 的情况下,Bean 的生命周期由 Spring 来管理,然而 Spring 无法自动配置@Configuration 注解的类。而 Spring Boot 的核心功能之- 就是根据约定自动管理该注解标注的类。用来实现该功能的组件是@EnableAutoConfiguration 注解。
@EnableAutoConfiguration 的主要功能是启动 Spring 应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的 Bean。自动配置通常是基于项目 classpath 中引入的类和已定义的 Bean 来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的 jar包中。
例如: 如 果 将 tomcat-embedded.jar 添 加 到 classpath 下 , 那 么@EnableAutoConfiguration 会认为你准备用 TomcatServletWebServerFactory 类,并帮你初始化相关配置。与此同时,如果自定义了基于 ServletWebServerFactory 的 Bean ,那么@EnableAutoConfiguration 将不会进行 TomcatServletWebServerFactory 类的初始化。这一系列的操作判断都由 Spring Boot 来完成。
@EnableAutoConfiguration 注解的源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";//根据类(Class) 排除指定的自动配置Class<?>[] exclude() default {};//根据类名排除指定的自动配置String[] excludeName() default {};
}
@EnableAutoConfiguration 会猜 测你需要使用的 Bean,但如果在实战中你并不需要它预置初始化的 Bean,可通过该注解的 exclude 或 excludeName 参数进行有针对性的排除。比如,当不需要数据库的自动配置时,可通过以下两种方式让其自动配置失效。
//通过@SpringBootApplication 排除 DataSourceAutoConfiguration
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringLearnApplication {}
或:
//通过@EnableAutoConfiguration 排除 DataSourceAutoConfiguration
@Configuration
@EnableAutoConfiguration( exclude = DataSourceAutoConfiguration.class)
public class DemoConfiguration {}
被@EnableAutoConfiguration 注 解的类所在 package 还具有特定的意义,通常会被作为扫描注解@Entity 的根路径。这也是在使用@SpringBootApplication 注解时需要将被注解的类放在顶级 package 下的原因,如果放在较低层级,它所在 package 的同级或上级中的类就无法被扫描到。
@ComponetScan: 基于注解的类扫描
用于进行包扫描,检查类是否使用了@Controller,@Service等注解,有则获取这些类创建对应的bean对象注册到Spring容器。它在不特别指定扫描包路径的情况下会扫描当前启动所在的包及子包下的Bean.
@SpringBootConfiguration: 配置类
继承于@Configuration,本身只是说明这是一个SpringBoot项目的配置类,功能与@Configuration一样,使得Spring容器知道需要跟处理@Configuration注解的类一样处理这个类。
二. 再看看 SpringApplication类的功能
SpringApplication.run(EurekaApp2.class,args);
实际调用SpringApplication中的静态方法 run().
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args); //调用下面的方法}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {//实际上是在这里调用了 SpringAppication的构造方法return (new SpringApplication(primarySources)).run(args);}
从上面的方法run来看,它先调用SpringApplication构造方法,创建实例,再调用 SpringApplication中的run, 所以,要追踪到SpringApplication的构造方法上,这个构造方法主要完成了四件主要工作:
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 = new HashSet();this.isCustomEnvironment = false;this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");//先把主类保存起来this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));//1、判断运行项目的类型this.webApplicationType = WebApplicationType.deduceFromClasspath();//2、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationContextInitializer接口实例this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//3、扫描当前路径下META-INF/spring.factories文件的,加载ApplicationListener接口实例this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));//4、判断并设置main方法的定义类,找到运行的主类this.mainApplicationClass = this.deduceMainApplicationClass();
}
三. run()
public ConfigurableApplicationContext run(String... args) { <!--1、这个是一个计时器-->StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();<!--2、设置了一些环境变量-->configureHeadlessProperty();<!--3、获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法-->SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {<!--4、把参数args封装成DefaultApplicationArguments-->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);<!--5、这个很重要准备环境了,并且把环境跟spring上下文绑定好,并且执行environmentPrepared()方法-->ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);<!--6、判断一些环境的值,并设置一些环境的值-->configureIgnoreBeanInfo(environment);<!--7、打印banner-->Banner printedBanner = printBanner(environment);<!--8、创建上下文,根据项目类型创建上下文-->context = createApplicationContext();<!--9、获取异常报告事件监听-->exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);<!--10、准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法-->prepareContext(context, environment, listeners, applicationArguments,printedBanner);<!--11、这个是spring启动的代码了,这里就回去里面就回去扫描并且初始化单实列bean了-->//这个refreshContext()加载了bean,还启动了内置web容器//最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}<!--12、执行ApplicationRunListeners中的started()方法-->listeners.started(context);<!--执行Runner(ApplicationRunner和CommandLineRunner)-->callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, listeners, exceptionReporters, ex);throw new IllegalStateException(ex);}listeners.running(context);return context;}
下面以一个流程图来说明一下调用过程
springboot(二)自动化配置源码解析相关推荐
- 8145v5 参数_SpringBoot外化配置源码解析:外化配置简介、参数处理|value|spring|调用|参数值
SpringBoot外化配置源码解析 在前面章节我们讲解了 Spring Boot 的运作核心原理及启动过程中进行的一系列核心操作. 从本章开始,我们将针对在实践过程中应用的不同知识点的源代码进行解读 ...
- Spring Boot Profile使用详解及配置源码解析
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 二师兄 来源 | 程序新视界 在实践的过程中我 ...
- SpringBoot文件上传源码解析
一.SpringMVC文件上传源码分析前言(这部分我觉得原作者写的很好) 该如何研究SpringMVC的文件上传的源码呢? 研究源码并不是仅仅知道程序是怎样运行的,而应该从宏观的角度.不同的立场去看待 ...
- SpringBoot rest映射及源码解析
一.rest使用与原理 • @xxxMapping: • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作) • 以前:/getUser 获取用户 /deleteUser 删除用户 /ed ...
- Nacos的动态配置源码解析
文章目录 1. 如何使用 2. 原理详解 2.1 采用延迟线程池定时执行"监听"文件是否有修改 2.2 通过长轮询的方式获得修改过的文件及其内容 2.3 拿到配置后通过applic ...
- BIN,S19,M0T,SREC,HEX文件解析;FileParse(二)之源码解析
简介 一.摘要 1.描述 2.关键字 二.为何选择C#解析 三.BIN文件解析 四.BIN文件生成 五.S19,M0T,SREC文件解析 六.S19,M0T,SREC文件生成 七.HEX文件解析 八. ...
- 轻触开源(二)-Gson项目源码解析_壹
2019独角兽企业重金招聘Python工程师标准>>> 上篇文章<轻触开源-Java泛型Type类型的应用和实践(一)> https://my.oschina.net/u ...
- Android特别的数据结构(二)ArrayMap源码解析
1. 数据结构 public final class ArrayMap<K,V> implements Map<K,V> 由两个数组组成,一个int[] mHashes用来存放 ...
- 解析ViewPager(二)——ViewPager源码解析
前言 前一篇博客介绍了ViewPager的简单使用,这篇博客主要从源码的角度来解析ViewPager. ViewPager的一些变量 ViewPager是一组视图,那么它的父类必然是ViewGroup ...
最新文章
- Servlet 服务器端小程序
- C++中的静态成员变量
- MySQL Schema设计(三)利用Python操作Schema
- 【算法】五分钟快速了解代码复杂度
- [USACO1.5]数字三角形 Number Triangles
- NYOJ276 比较字母大小
- mysql 开发包 安装_mysql的zip包的安装方法
- Aligned公司在凤凰城建设数据中心将采用微电网的电力
- 竹间智能B轮3000万美元融资,打造业内领先的对话机器人及多模态情感计算平台...
- EF Core 小工具
- PMP中各种图形解释和使用场景
- 如何启动单线程实现多线程效果及保证安全?
- MacBook Pro电脑一键切换输入法
- su切换特别慢 linux,秋明 | 系统su切换用户时间非常长
- excel报错无法粘贴信息,原因复制区域跟粘贴区域形状不同
- StorageEngine
- oracle spool设置字符集,spool出来的文件格式一个是UTF8一个是ASCII ??
- css 实现上下、左右、左上、左下、右上、右下和对角线移动动画
- 微信小程序社群管理解决方案
- python是黑客吗_为什么黑客都用python