首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >浅谈springboot启动过程

浅谈springboot启动过程

原创
作者头像
用户4844014
发布2023-05-02 09:30:47
发布2023-05-02 09:30:47
33200
代码可运行
举报
文章被收录于专栏:gitcat学编程gitcat学编程
运行总次数:0
代码可运行

1. 知识回顾

为了后文方便,我们先来回顾一下spring的一些核心概念。

spring最核心的功能无非是ioc容器,这个容器里管理着各种bean。ioc容器反映在java类上就是spring的核心类ApplicationContext。ApplicationContext有众多的子接口和子类,不同的实现类有不同的功能。比如ClassPathXmlApplicationContext支持从xml读取bean定义并注册到容器中,AnnotationConfigApplicationContext支持读取@Configuration、@Service等注解定义的bean。

ClassPathXmlApplicationContext和AnnotationConfigApplicationContext代表了我们定义bean的两种最常见方式,即xml和注解方式。还有一种方式是通过@Import来导入bean的定义。如下代码示例了@Import的一种用法。

代码语言:javascript
代码运行次数:0
运行
复制
@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

@Import(AppConfig.class)
public class AppMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
        MyBean bean = context.getBean(MyBean.class);
        System.out.println(bean);
}

@Import还有一种用法就是@Import(XxxImportSelector.class),其中XxxImportSelector是ImportSelector这个接口的实现类。下面演示了@Import(XxxImportSelector.class)的用法。

代码语言:javascript
代码运行次数:0
运行
复制
// 一个普通的java类
public class TestImportSelector {
}

// ImportSelector的实现类。
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {"com.example.springbootsimple.service.TestImportSelector"};
    }
}

// 测试ImportSelector
@Import(MyImportSelector.class)
public class AppMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);
        TestImportSelector bean = context.getBean(TestImportSelector.class);
        System.out.println(bean);
    }
}

如上代码运行后,spring容器中就会有TestImportSelector这个bean了,原理是如果@Import()中要import的是一个ImportSelector的实现类,spring就会自动调用ImportSelector.selectImports方法,这个方法会返回一个包含类名的String数组,spring会根据这些类名实例化类,注册到spring容器中。

2. 最简单的springboot启动

我们用springboot启动web应用用得比较多,用多了以后,往往会觉得springboot只能这么用,其实不然,我们先抛开各种配置,从最简单的springboot启动开始看,如下代码是最简单的springboot启动,注意AppMain类上没有任何注解。

代码语言:javascript
代码运行次数:0
运行
复制
public class AppMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(AppMain.class);
        // 打印 org.springframework.context.annotation.AnnotationConfigApplicationContext        
        System.out.println(context.getClass().getName());
    }
}

上面代码可以成功运行(注意成功运行的前提是只引入org.springframework.boot:spring-boot,不要引入spring-boot-starter-web)。我把SpringApplication.run的返回值也打印出来了,是想告诉你,springboot的启动过程其实就是创建一个ApplicationContext的过程。所以,别人问你springboot启动过程是什么时,你就可以说,就是创建了一个ApplicationContext,完事。如果我们从宏观角度来看,这个问题就是这么简单。当然,这个答案肯定不会让人满意,但是不要着急,我们要从简单到复杂,从宏观到微观,这样才能看清事物的本质。

3. 带Web应用的springboot启动

3.1 启动示例

我们引入spring-boot-starter-web,然后执行如下代码。

代码语言:javascript
代码运行次数:0
运行
复制
@SpringBootApplication
public class AppMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(AppMain.class);
        System.out.println(context.getClass().getName());
    }
}

此时,打印出的结果是org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext,可以看到和我们第一个例子打印的ApplicationContext不一样了。springboot是如何确定用哪个ApplicationContext的?AnnotationConfigServletWebServerApplicationContext做了什么事情,让我们什么也没做就启动了一个tomcat的web容器?

3.2 如何确定用哪个ApplicationContext?

我们追溯SpringApplication.run方法的源码,会看到如下run方法,这个方法就是比较核心的启动流程了。

我们不需要每一行代码都关心,因为那样会让我们迷失在源码里,我们通过方法名,我们很容易看出来 context = createApplicationContext();这一行代码就是在创建一个ApplicationContext,所以我们看下这个方法源码。

createApplicationContext();会调用DefaultApplicationContextFactory.create(WebApplicationType webApplicationType)这个工厂类来创建ApplicationContext。从上面代码可以看出,DefaultApplicationContextFactory相当于是一个代理,它会通过SpringFactoriesLoader.loadFactories加载其他ApplicationContextFactory来创建Application,如果其他工厂类没有返回结果,就会用默认的AnnotationConfigApplicationContext。SpringFactoriesLoader.loadFactories这个方法有兴趣的可以研究下,这个方法会到spring.factories这个文件里加载你想要的类,spring-boot jar包的spring.factories里定义了如下两个ApplicationContextFactory类,所以这两个类会被加载进来用于创建ApplicationContext。

上面两个Factory从名字也能很容易辨别,一个是用来创建ServletWeb应用的,一个是用来创建ReactiveWeb应用的。如下图是AnnotationConfigServletWebServerApplicationContext.Factory的create方法,可以看到如果webApplicationType是SERVLET类型,就会创建AnnotationConfigServletWebServerApplicationContext。

从上述源码来看,我们第一个疑问已经解决了,springboot会根据wepApplicationType的类型来决定创建什么样的ApplicationContext。至于WebApplicationType如何确定,看下WebApplicationType#deduceFromClasspath就可以了,比较简单,比如会根据类路径下是否有javax.servlet.Servlet等来判断web类型是否是SERVLET等等。当我们引入spring-boot-starter-web时,这个引用就会包含对servlet的引用,所以我们创建的就是SERVLET类型的WebApplicationType。

3.3 AnnotationConfigServletWebServerApplicationContext做了什么?

现在ApplicationContext创建好了,我们也知道什么情况下会创建什么类型的ApplicationContext了,但是这个ApplicationContext还是个比较原始的,很多事情都还没做。了解过一些spring源码的应该知道,ConfigurableApplicationContext有个非常重要的方法:refresh方法。这个方法内部会解析bean的定义,创建好单例bean,应用bean后处理器等。AnnotationConfigServletWebServerApplicationContext就是在refresh方法中搞了点事情,启动了tomcat容器。org.springframework.context.support.AbstractApplicationContext#refresh方法内会调用onRefresh()方法,AnnotationConfigServletWebServerApplicationContext就是重写了onRefresh方法,在这个方法里启动了tomcat。

至于tomcat是如何创建并启动的,这又是另一个话题了,感兴趣的可以自行研究。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 知识回顾
  • 2. 最简单的springboot启动
  • 3. 带Web应用的springboot启动
    • 3.1 启动示例
    • 3.2 如何确定用哪个ApplicationContext?
    • 3.3 AnnotationConfigServletWebServerApplicationContext做了什么?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档