1. springboot的启动流程
springboot的启动流程分两阶段:
- 构造函数,实例化SpringApplication阶段
- 实例化后,调用SpringApplication的run方法阶段
启动流程
@SpringBootApplication
public class MySpringBootWebApplication {
public static void main(String[] args) {
// 启动springboot项目
SpringApplication.run(AvfmsWebApplication.class, args);
}
}

1.1 实例化SpringApplication
源码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
// step1:安全验证:启动类必须不为空
Assert.notNull(primarySources, "PrimarySources must not be null");
// step2:设置启动类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// step3:推断当前项目类型
// 有3种:none、servlet、reactive
// 一般我们的项目都是servlet
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// step4:获取所有在META-INF/spring.factories文件中指定了的BootstrapRegistryInitializer实现类
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// step5:获取所有在META-INF/spring.factories文件中指定了的ApplicationContextInitializer实现类
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// step6:获取所有在META-INF/spring.factories文件中指定了的ApplicationListener实现类
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// step7:推断出当前main函数所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
比较重要的有:
- 推断了当前项目类型:none、servlet、reactive;这个项目类型,觉得了run方法里要创建什么样的ApplicationContext,也就是IOC容器。一般我们的项目为servlet类型
- 获取了所有指定的ApplicationContextInitializer实现类,run方法在IOC容器创建完,准备refresh阶段前,会调用这些实现类
- 获取了所有指定的ApplicationListener实现类,这些类会监听IOC容器创建bean的过程
1.2 调用SpringApplication实例的run方法
创建完SpringApplication实例后,调用run方法,每一个阶段,深入去看都有很多值得说的,但是我这篇文章点到为止(主要是自己水平还不够哈哈哈),就只做概括了,源码如下:
public ConfigurableApplicationContext run(String... args) {
// step1:开始计时
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// step2:获取SpringApplicationListener,用于发布springboot项目启动的各个阶段事件
SpringApplicationRunListeners listeners = getRunListeners(args);
// step3:发布当前项目为staring,启动中状态
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// step4:根据项目类型,获取IOC容器的创建环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// step5:控制台打印banner信息
Banner printedBanner = printBanner(environment);
// step6:根据项目类型,创建IOC实例
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// step7:调用IOC容器的刷新方法前,做的准备工作
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// step8:最核心的方法,IOC容器刷新
refreshContext(context);
// step9:IOC容器刷新后,里面其实是一个空方法,什么都没做
afterRefresh(context, applicationArguments);
// step10:结束计时,计算耗时
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// step11:发布当前项目为started,已启动状态
listeners.started(context, timeTakenToStartup);
// step12:调用ApplicationRunner接口的实现run方法
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;
}
最核心的方法就是创建IOC容器和刷新IOC容器了,如图:

1.2.1 根据项目类型创建IOC容器
就是根据实例化SpringApplication的时候,推断的项目类型,来创建对应的IOC容器:

如果是servlet项目类型,创建的IOC容器为:AnnotationConfigServletWebServerApplicationContext,具体怎么执行到这一步的,我就不放代码了,跳来跳去有点复杂。

1.2.2 刷新IOC容器
最重要的刷新IOC容器方法,其实就是执行spring里的bean实例创建等各种核心方法过程。如果是servlet项目类型,最终执行到的方法是AbstractApplicationContext的refresh方法:

为什么会执行到这个方法,因为servlet项目IOC实例是AnnotationConfigServletWebServerApplicationContext,这个类间接继承了AbstractApplicationContext抽象类,类图:

2. springboot自动装配原理
通过注解EnableAutoConfiguration来实现的,这个注解在springboot的启动注解SpringBootApplication中集成了。

这个注解开启,那么就会解析springboot内置的两个文件:
-
META-INF/spring.factories
-
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
这两个文件里定义了要加载的AutoConfiguration,有很多如RedisAutoConfiguration、DataSourceAutoConfiguration等,都是springboot帮我们定义好的,当然我们也可以自定义

举个例子,redis的RedisAutoConfiguration,通过ConditionOnClass注解来判断是否引入了redis的依赖,如果引入redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
那么就初始化RedisTemplate实例到spring的IOC容器中,这样就做到了引入依赖,就可以在项目直接使用了。

3. spring里bean的生命周期
或者也叫bean的创建流程,在spring中可以通过注解或者原始的XML定义的方式,来让spring帮我们创建bean并放入IOC容器中管理,创建的流程如下:

根据BeanDefinition获取bean的定义信息,准备实例化
实例化前,调用InstantiationAwareBeanPostProcessor前置处理器
构造方法实例化bean
实例化后,调用InstantiationAwareBeanPostProcessor后置处理器
执行依赖注入,如处理Autowired、Value等注解
执行Aware接口的方法
调用BeanPostProcesser前置处理器
执行初始化方法,如实现了
InitializingBean接口或自定义的init-method调用BeanPostProcesser后置处理器,可以对Bean做一些增强,比如使用动态代理
销毁阶段,调用销毁回调函数(如果实现了的话)
4. bean的循环依赖问题
循环依赖的问题是在bean的依赖注入阶段产生的,例如实例A在依赖注入的时候需要注入B,这个时候会走创建B实例的流程,而在实例B的依赖注入阶段,又需要注入A,就产生的循环依赖。
spring通过三级缓存的方案解决循环依赖的问题,三级缓存其实就是3个map,一级缓存存放的是完整的spring bean信息,二级缓存存放的是未走完spring初始化周期的不完整bean信息,三级缓存存放的是bean的BeanFactory

为什么要用三级缓存,而不是二级缓存,因为存在AOP动态代理生成实例的场景,举例,beanA是需要动态代理生成的实例,beanB是普通的单例,beanA与beanB循环依赖,他们的解决流程如下:
三级缓存解决循环依赖的问题:
实例化beanA,并把A的对象工厂BeanFactory放入三级缓存
依赖注入beanB
实例化beanB,并把B的对象工厂BeanFactory放入三级缓存
依赖注入beanA,会从三级缓存中获取A的BeanFactory,并调用getObject方法获取实例A,此时:
如果A是需要生成AOP动态代理对象的实例,那么此时直接生成代理实例beanA#proxy,放入二级缓存,返回这个代理实例beanA#proxy
如果beanA是一个普通的单例,那么获取到第1步实例化的beanA,返回这个普通单例beanA
B根据BeanFactory返回的A注入
初始化beanB完成,beanB把自己放到一级缓存里,同时删除三级缓存中自己的BeanFactory
初始化beanA,这里会来到BeanPostProcessor的后置处理器,AOP动态代理对象是在这里创建的,但是:
如果能在二级缓存中,找到beanA#proxy,那么就不会重新生成A的代理对象,直接用二级缓存的
beanA完成,将A或者动态代理对象A放入一级缓存,同时删除二级缓存、三级缓存中的自己
有一种循环依赖spring是没有办法解决的,就是在构造函数里,A跟B的相互依赖:
-- 在A的构造函数里依赖B,但是B的构造函数里又依赖A
public class A{
B b;
public A(B b){
this.b = b;
}
}
public class B{
A a;
public B(A a){
this.a = a;
}
}
-- 解决方案为,给其中一个bean添加懒加载,即用到的时候再加载
-- 比如给B添加懒加载
public class A{
B b;
public A(@Lazy B b){
this.b = b;
}
}
5. springMVC的执行流程
springMVC有4大组件,来解决一次请求的全过程,他们的流程如图,来着黑马于洪波老师的图:

前端请求首先都来到dispatcherServlet,相当于一个调度中心,响应并调度所有的请求
dispatcherServlet请求HandlerMapping,HandlerMapping中记录了请求路径和对应处理类#方法的映射
HandlerMapping返回处理本次请求的类#方法
dispatcherServlet不直接调用处理方法,而且请求HandlerAdaptor来处理
HandlerAdaptor封装调用类#方法的参数,然后调用处理方法
返回ModelAndView给dispatcherServlet(注意在现在很多项目中不返回ModelAndView,而且直接返回数据,那么dispatcherServlet也就直接把数据返回给前端就结束了)
dispatcherServlet调用ViewResolver
ViewResolver解析视图并返回
dispatcherServlet最后渲染视图然后返回前端

3664

被折叠的 条评论
为什么被折叠?



