学习笔记是学习了 小马哥在慕课网的 《Spring Boot 2.0深度实践之核心技术篇》根据自己的需要和理解做的笔记。
前言
Spring-boot 是基于Spring构建的,也就是说Spring Framework 是学好Spring-boot的基础。
首先理解Spring-boot中的SpringApplication
引导类的前提是要简单理解Spring Framework的基础技术。
Spring Framework基础技术包括以下几点,先列出来 用于我以后复习以及总结
- Spring 模式注解 ✔
- Spring BeanFactory
- Spring 应用上下文 ✔
- Spring Bean的加载 、生命周期
- Spring 工厂加载机制 ✔
- Spring 应用上下文功能扩展 ✔
- IOC(控制反转)& DI(依赖注入) AOP(面向切面编程)
- spring mvc
- Spring 应用事件/监听器 ✔
- Spring Environment 抽象 ✔
打对号的是针对SpringApplication
这个引导类所需要了解的。
Spring-boot 衍生技术
- SpringApplication
- SpringApplication Builder API
- SpringApplication 运行监听器
- SpringApplication 参数
- SpringApplication 故障分析
- SpringApplication 应用事件/监听器
SpringApplication的准备阶段也可以说是构造器阶段。下面我们来看一下源码。
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//第一部分
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//第二部分
this.webApplicationType = deduceWebApplicationType();
//第三部分
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//第四部分
this.mainApplicationClass = deduceMainApplicationClass();
}
在准备阶段主要有四部分
- 第一部分:配置我们的源,从源码的注释中我们也可以了解到这个源是可以从应用上下文中加载所有bean的指定的主要来源。通俗点讲也就是我们标注
@SpringBootApplication
模式注释的这个类。对于模式注解,可以看我之前的整理的文章。 - 第二部分:推断Web应用类型。
- 第三部分:应用上下文初始器 和 应用事件监听器,这部分可以查看Spring-boot---SpringApplication准备阶段的加载(二)
- 第四部分:推导引用主类。
配置:Spring Bean 来源
JAVA配置Class
Spring 模式注解所标注的类,用于 Spring 注解驱动中 Java 配置类。
比如我们的项目默认的启动类,在我的项目中是SpringBootDiveLearnApplication
@SpringBootApplication
public class SpringBootDiveLearnApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDiveLearnApplication.class, args);
}
}
我们可以看到,这个引导类配置了一个spring的一个模式注解@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 {}
在这个注解上又有两个特殊的注解 @SpringBootConfiguration
@EnableAutoConfiguration
如果我们进一步向上查找 @SpringBootConfiguration
我们可以发现其实这个注解就是 @Component
的"派生"注解。而 @EnableAutoConfiguration
则是一个接口编程方式的装配注解。
因此呢 在引导类SpringBootDiveLearnApplication
中我们才会在主函数方法中的 SpringApplication.run 方法里 放入 SpringBootDiveLearnApplication
作为启动参数。
XML上下文配置文件
用于spring 传统配置驱动中的XML文件。
我们知道引导类以JAVA配置的Class作为参数时,是如何启动容器的,即SpringApplication.run(SpringBootDiveLearnApplication.class, args);
那么 XML方式是如何启动的呢
SpringApplication springApplication = new SpringApplication();
springApplication.setSources();
springApplication.run(args);
我们可以在.setSources
方法里添加参数来配置源。
下面我们可以来看一下.setSources
的方法,来理解这个配置方法。
/**
* Set additional sources that will be used to create an ApplicationContext. A source
* can be: a class name, package name, or an XML resource location.
* <p>
* Sources set here will be used in addition to any primary sources set in the
* constructor.
* @param sources the application sources to set
* @see #SpringApplication(Class...)
* @see #getAllSources()
*/
public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
我们可以看到注释中的英文解释,参数可以类名,包名以及XML源路径。
那么spring boot 是使用什么方法来分别加载这两种数据源的呢。
答案就是org.springframework.boot.BeanDefinitionLoader
中的构造方法
/**
* Create a new {@link BeanDefinitionLoader} that will load beans into the specified
* {@link BeanDefinitionRegistry}.
* @param registry the bean definition registry that will contain the loaded beans
* @param sources the bean sources
*/
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
//JAVA 配置CLASS
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
//XML配置
this.xmlReader = new XmlBeanDefinitionReader(registry);
if (isGroovyPresent()) {
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
分别通过 AnnotatedBeanDefinitionReader
和 XmlBeanDefinitionReader
来读取并且将配置源解析加载为Spring Bean。
那么 BeanDefinitionLoader
又是在何处引用的呢。
是在引导类 SpringApplication
中的
/**
* Factory method used to create the {@link BeanDefinitionLoader}.
* @param registry the bean definition registry
* @param sources the sources to load
* @return the {@link BeanDefinitionLoader} that will be used to load beans
*/
protected BeanDefinitionLoader createBeanDefinitionLoader(
BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
这个方法中引用的,如果我们一步一步的往上找,那么最终的方法就是 SpringApplication.run()
方法。
由此我们也可以证实了 我们的结论,在spring-boot的配置阶段,可以有两个数据源。
推断:Web应用类型和主引导类
我们知道 spring-boot 不仅可以用作一个WEB工程,也可以作为非WEB工程来使用,那么我们如何去判断当前的工程师WEB还是非WEB的呢。我们可以从工作台来看出。如图,以下是非WEB类型的工作台打印的启动日志。
我们可以看到,输出的日志中没有显示是使用Tomcat启动也没有8080端口。
而WEB工程的启动日志中则会有以下输出。
从图中的输出日志中看出 即是Tomcat Server 又有8080端口。
spring-boot 是如何推断应用类型
根据当前应用 ClassPath 中是否存在相关实现类来推断 Web 应用的类型,包括:
-
Web Reactive:
WebApplicationType.REACTIVE
对应的类路径org.springframework. web.reactive.DispatcherHandler
Web Servlet:
WebApplicationType.SERVLET
对应的类路径org.springframework.web.servlet.DispatcherServlet"
非 Web:
WebApplicationType.NONE
判断方法 是 org.springframework.boot.SpringApplication#deduceWebApplicationType
/**
* Springboot 启动时判断当前WEB应用类型的方法
*/
private WebApplicationType deduceWebApplicationType() {
/**
* 当类路径下存在Web Reactive 类路径并且
*不存在WebApplicationType.SERVLET类路径是才会启用
* WEB REACTIVE 容器 也就是 Netty。
*/
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
/**
* 当类路径中都不存在时,会启用非WEB类型
*/
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
/**
* 如果都不满足,则默认启用TOMCAT
*/
return WebApplicationType.SERVLET;
}
以上的方法就是判断启动应用类型的方法。
spring-boot 是如何推断启动主类
我们会奇怪,spring-boot 默认启动的类不就是主类吗? 类似于下面的代码
@SpringBootApplication
public class SpringBootDiveLearnApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDiveLearnApplication.class, args);
}
}
我们可以清楚的看到 主类就是 SpringBootDiveLearnApplication
但是大家不要忘了,spring-boot有多种的启动方式,而且 SpringApplication.run();
中的第一个参数是可变的,换句话说,我拿其他的类并加上 @SpringBootApplication
注解,放入SpringApplication.run();
方法中,那么那个类就应该是主类,而不是当前类。那么到底spring-boot 是如何判断Main Class
的呢。
参考方法 : org.springframework.boot.SpringApplication#deduceMainApplicationClass
/**
* 使用异常堆栈来判断 当前主类
*/
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
spring 是使用异常堆栈来遍历最后判断出main方法的。让我们来Debugger一下。
我们可以看到 只有最后一个堆栈信息,即类名为 SpringBootDiveLearnApplication
的类中的方法名是main,从而推导出 SpringBootDiveLearnApplication
就是主类。
spring-boot的启动主类,一切都是由spring 自己推导的,所以我们不要所以然的认为主类是我们传给spring容器的。
在文章的结尾特别推荐一篇博文,也是最近才发现的,发现写的很好,我们可以根据博文里的内容,自己去debugger从而更深的理解spring Framework。
文章链接 : 给你一份Spring Boot知识清单