⾯试题:SpringBoot的⾃启动原理
个⼈博客⽹: (你想要这⾥多有)
引⾔
不论在⼯作中,亦或是求职⾯试,Spring Boot 已经成为我们必知必会的技能项。除了⽐较⽼旧的政府项⽬或⾦融项⽬依旧使⽤如 SSM 或 SSH 做单体框架开发项⽬外,如今的各⾏各业基于项⽬的快速开发与发布、迭代更新,都在逐渐替换使⽤ Spring Boot 框架,⽽逐步摒弃配置沉重和效率低下的 Spring 启动框架。
使⽤⼀门新的技术,⽴⾜于对它⾜够了解的基础上,能够让你更加得⼼应⼿的去进⾏应⽤、开发。SpringBoot 的精髓 “⾃动配置原理” 不仅仅是在⾯试过程中才⽤的上;在⼯作中如果能深⼊理解Spring Boot 的⾃动配置原理,我们就可以根据⾃⼰的需求⾃定义⼀个 started 满⾜开发的需求,让开发更加便捷与⾼效。
Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置、难以集成的内容(⼤多数流⾏第三⽅技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能⼒。
本篇博⽂主要针对三个⽅⾯进⾏分析、讲解;这三个⽅⾯分别是:
1. Spring Boot 与 Spring 的差别与优点?以及与Spring MVC 的关系?
2. Spring Boot 主启动类的启动机制
3. Spring Boot 的⾃动配置原理是怎么实现的?
4. 根据⾃动配置原理⾃定义⼀个个性的 started ?
1、Spring Boot 与 Spring 的差别与优点?以及与Spring MVC的关系?
1.1 Spring Boot 和 Spring MVC 的关系?
Spring MVC 是基于Spring 的MVC框架;
⽽Spring Boot 是基于Spring 配置的开发⼯具框架,使⽤注解更加简洁和适应快速开发。
1.2 Spring Boot 的优势
简单来说:Spring boot 简化了使⽤ Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上⼿。
(1)编码更简单
1. Spring 框架由于超重量级的 XML,annotation 配置,使得系统变得很笨重,难以维护
2. SpringBoot 采⽤约点⼤于配置的⽅法,直接引⼊依赖,即可实现代码的开发
(2)配置更简单
Xml⽂件使⽤javaConfig代替,XML中bean的创建,使⽤@bean代替后可以直接注⼊。
配置⽂件变少很多,只保留 l
(3)部署更简单
(4)监控更简单
Spring-boot-start-actuator:
可以查看属性配置
线程⼯作状态
环境变量
JVM性能监控
2、Spring Boot 主启动类的启动机制
关于程序的⼊⼝问题:
在使⽤SSM或SSH以及原⽣的Servlet 作为架构的项⽬中,项⽬的⼊⼝都不是 main ⽅法,main ⽅法更多的是作为 HelloWorld 练习或测试时使⽤。其程序的⼊⼝基本都是
handler 中对外暴露的每个接⼝:
@RequestMapping(value = "/listarea",method = RequestMethod.GET)
private Map<String,Object> listArea(){
HashMap<String, Object> modelMap = new HashMap<>();
List<Area> list = AreaList();
modelMap.put("areaList",list);
ArrayList arrayList = new ArrayList();
return modelMap;
}
外部通过对接⼝地址的调⽤,从⽽实现交互获取数据、进⾏页⾯渲染和展⽰。
但是,在使⽤了 Spring Boot 后, Main ⽅法⼜成为了程序的⼊⼝;
使⽤过springBoot进⾏项⽬打包和部署启动的都知道, SpringBoot 打包成 jar 包后,可以不需要部署到Tomcat 上⽽直接⽤命令启动,如下:
java -jar xxx.jar
这是因为Spring Boot 的jar 在启动时,程序经过主启动类中的main ⽅法进⾏启动的;主启动类代码如下:
all;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
在这⾥为项⽬创建⼀个接⼝,并分别在接⼝和主启动类main⽅法上打断点,
在Spring Boot 项⽬中每次调⽤接⼝前都会先启动主启动类,这⾥对主启动类的 main ⽅法⼊⼝进⾏分析;
在上图中,主启动类启动后会进⼊main ⽅法中,并在⽅法中由 SpringApplication 调⽤ run ()⽅法,⽅法中形参传⼊当前类的 class 对象以及main ⽅法的 args 参数。下⼀步进⼊ run() ⽅法中,
public static ConfigurableApplicationContext run(Object source, args) {
return run(new Object[]{source}, args);
}
由断点可以知道,在run()⽅法中还调⽤了另⼀个 run( )⽅法,再点击进⼊下⼀步,
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}
由截图可知,传⼊的class 对象被⽤来创建 SpringApplication实例对象,并由⽣成的实例调⽤ run (args)⽅法做启动操作,⽅法形参为 args 参数。
①创建 SpringApplication 实例,进⾏初始化对象:
new SpringApplication ( sources)
public sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.additionalProfiles = new HashSet();
this.initialize(sources); // 初始化启动类
}
在该构造函数的⽅法体中,对当前类进⾏了初始化,
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources)); // 添加启动类 DemoApplication.class 到 set 集合中,以备后⽤
}
this.webEnvironment = this.deduceWebEnvironment(); // 重点:检测当前是否为 web 环境
this.SpringFactoriesInstances(ApplicationContextInitializer.class));
this.SpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
上图中的 webEnvironment ⽤于检测当前环境是否为 web 环境,如果是,则返回true.
具体如何判断当前环境是否为 web 环境,后⾯再做分析.
获取初始化器:
在确定是否为web环境后,会根据 ApplicationContextInitializer.class 获取⼯⼚实例参数从⽽设置初始化器的参数;
随后⽤setListeners 设置
判断并获取主启动类的类型,通过获取jvm运⾏时的栈(StackTraceElement[])中的多个加载的类,判断类中⽅法是否符合 main ⽅法的类即为当前启动类,
到这⾥,SpringApplication 的初始化过程就执⾏完毕了;此时的SpringApplication 实例的启动需要调⽤run() ⽅法来实现,下⼀节进⾏分析;
以下是初始化过程的重要环节的关系图:
判断当前环境是否为 web 环境:
this.webEnvironment = this.deduceWebEnvironment(); // 重点:检测当前是否为 web 环境
我们进⼊ deduceWebEnvironment()⽅法去看⼀看,
private boolean deduceWebEnvironment() {
String[] var1 = WEB_ENVIRONMENT_CLASSES;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String className = var1[var3];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return false;
}
spring ioc注解}
return true;
}
由源码可知, deduceWebEnvironment 中对私有变量 WEB_ENVIRONMENT_CLASSES进⾏了循环,继续查看它所代表的是什么?
private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet", "org.t.ConfigurableWebApplicationContext"};
该私有变量数组有两个参数构成,第⼀个参数是 “javax.servlet.Servlet”,第⼆个参数是 “ConfigurableWebApplicationContext”,这⾥
对“ConfigurableWebApplicationContext”进⾏分析:
进⼊ConfigurableWebApplicationContext类中,我们看到该类继承⾃ WebApplicationContext 和 ConfigurableApplicationContext两个类,再往下看:
我们看到 WebApplicationContext 继承⾃ ApplicationContext 类,⽽我们知道 ApplicationContext 是可以⽤来创建IOC 容器的对象的,如:
ApplicatioonContext context = new ClasspathXmlApplicationContext();
该ApplicationContext类是所有IOC 容器对象的顶级接⼝
阶段⼩结:ConfigurableWebApplicationContext 是 web 环境下可以配置的IOC 容器。
那么拿到该类的全类名有什么⽤呢?
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return false;
}
public static boolean isPresent(String className, ClassLoader classLoader) {
try {
forName(className, classLoader);
return true;
} catch (Throwable var3) {
return false;
}
}
在 deduceWebEnvironment ⽅法中调⽤了 isPresent ⽅法,⽽在isPresent⽅法中有调⽤ forName ,将全类名 className 作为形参传⼊ forName中,此处的 classLoader 是
null值。
这⾥进⼊ ClassUtils的forName()⽅法中,代码如下:
public static Class<?> forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
clazz = ((name);
}
if (clazz != null) {
return clazz;
} else {
Class elementClass;
String elementName;
if (dsWith("[]")) {
elementName = name.substring(0, name.length() - "[]".length());
elementClass = forName(elementName, classLoader);
wInstance(elementClass, 0).getClass();
} else if (name.startsWith("[L") && dsWith(";")) {
elementName = name.substring("[L".length(), name.length() - 1);
elementClass = forName(elementName, classLoader);
wInstance(elementClass, 0).getClass();
} else if (name.startsWith("[")) {
elementName = name.substring("[".length());
elementClass = forName(elementName, classLoader);
wInstance(elementClass, 0).getClass();
} else {
ClassLoader clToUse = classLoader;
if (classLoader == null) {
clToUse = getDefaultClassLoader();
}
try {
return clToUse != null ? clToUse.loadClass(name) : Class.forName(name);
} catch (ClassNotFoundException var9) {
int lastDotIndex = name.lastIndexOf(46);
if (lastDotIndex != -1) {
String innerClassName = name.substring(0, lastDotIndex) + '$' + name.substring(lastDotIndex + 1);
try {
return clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName);
} catch (ClassNotFoundException var8) {
;
}
}
throw var9;
}
}
}
}
其中的 Class<?> clazz = resolvePrimitiveClassName(name); 根据全类名获取class 字节码⽂件对象;在获取到的class 对象不为null 的情况下,clazz =
((name); 获取常⽤类缓存中是否也存在该class对象;
如果两次获取的 clazz 都不为null是存在的,那么就直接返回:
if (clazz != null) {
return clazz;
由此我们可以知道,isPresent()⽅法的作⽤,是为了查看当前环境中有没有指定的⼯程项⽬的类。
从这⾥,我们可以做出总结,deduceWebEnvironment () ⽅法中通过判断 “WEB_ENVIRONMENT_CLASSES”数组中声明的两个类能否在当前环境中到,如果能,说明当前环境为web 环境。
private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet", "org.t.ConfigurableWebApplicationContext"};
② SpringApplication实例的启动过程:
在获取到SpringApplication的实例并初始化完成后,其启动使⽤run() ⽅法实现,
进⼊ run(args)⽅法中,
public ConfigurableApplicationContext args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null; // 失败分析器
SpringApplicationRunListeners listeners = RunListeners(args); // 加载
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 应⽤参数
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); // 配置⽂件参数(有默认值,可读取⾃定义配置⽂件),⽤于配置当前环境
Banner printedBanner = this.printBanner(environment); // banner的⾃定义机制
context = ateApplicationContext(); // 创建可配置的 IOC 容器
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论