JAVA后端开发面试基础知识(九)——SpringBoot

启动原理

SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可:

@SpringBootApplication(scanBasePackages = {
   "cn.dark"})
public class SpringbootDemo {
   

    public static void main(String[] args) {
   
     		// 第一种
        SpringApplication.run(SpringbootDemo .class, args);

  			// 第二种
        new SpringApplicationBuilder(SpringbootDemo .class)).run(args);

  			// 第三种
        SpringApplication springApplication = new SpringApplication(SpringbootDemo.class);
        springApplication.run();  
    }
}

可以看到第一种是最简单的,也是最常用的方式,需要注意类上面需要标注@SpringBootApplication注解,这是自动配置的核心实现,稍后分析,先来看看SpringBoot启动做了些什么?

在往下之前,不妨先猜测一下,run方法中需要做什么?对比Spring源码,我们知道,Spring的启动都会创建一个ApplicationContext的应用上下文对象,并调用其refresh方法启动容器,SpringBoot只是Spring的一层壳,肯定也避免不了这样的操作。

另一方面,以前通过Spring搭建的项目,都需要打成War包发布到Tomcat才行,而现在SpringBoot已经内置了Tomcat,只需要打成Jar包启动即可,所以在run方法中肯定也会创建对应的Tomcat对象并启动。以上只是我们的猜想,下面就来验证,进入run方法:

 public ConfigurableApplicationContext run(String... args) {
   
  // 统计时间用的工具类
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  configureHeadlessProperty();
  // 获取实现了SpringApplicationRunListener接口的实现类,通过SPI机制加载
  // META-INF/spring.factories文件下的类
  SpringApplicationRunListeners listeners = getRunListeners(args);

  // 首先调用SpringApplicationRunListener的starting方法
  listeners.starting();
  try {
   
   ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

   // 处理配置数据
   ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
   configureIgnoreBeanInfo(environment);

   // 启动时打印banner
   Banner printedBanner = printBanner(environment);

   // 创建上下文对象
   context = createApplicationContext();

   // 获取SpringBootExceptionReporter接口的类,异常报告
   exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
     new Class[] {
    ConfigurableApplicationContext.class }, context);

   prepareContext(context, environment, listeners, applicationArguments, printedBanner);

   // 核心方法,启动spring容器
   refreshContext(context);
   afterRefresh(context, applicationArguments);

   // 统计结束
   stopWatch.stop();
   if (this.logStartupInfo) {
   
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
   }
   // 调用started
   listeners.started(context);

   // ApplicationRunner
   // CommandLineRunner
   // 获取这两个接口的实现类,并调用其run方法
   callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
   
   handleRunFailure(context, ex, exceptionReporters, listeners);
   throw new IllegalStateException(ex);
  }

  try {
   
   // 最后调用running方法
   listeners.running(context);
  }
  catch (Throwable ex) {
   
   handleRunFailure(context, ex, exceptionReporters, null);
   throw new IllegalStateException(ex);
  }
  return context;
 }

SpringBoot的启动流程就是这个方法,先看getRunListeners方法,这个方法就是去拿到所有的SpringApplicationRunListener实现类,这些类是用于SpringBoot事件发布的,关于事件驱动稍后分析,这里主要看这个方法的实现原理:

 private SpringApplicationRunListeners getRunListeners(String[] args) {
   
  Class<?>[] types = new Class<?>[] {
    SpringApplication.class, String[].class };
  return new SpringApplicationRunListeners(logger,
    getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
 }

 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   
  ClassLoader classLoader = getClassLoader();
  // Use names and ensure unique to protect against duplicates
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  // 加载上来后反射实例化
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances);
  return instances;
 }

 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   
  String factoryTypeName = factoryType.getName();
  return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
 }

 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   
  MultiValueMap<String, String> result = cache.get(classLoader);
  if (result != null) {
   
   return result;
  }

  try {
   
   Enumeration<URL> urls = (classLoader != null ?
     classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
   result = new LinkedMultiValueMap<>();
   while (urls.hasMoreElements()) {
   
    URL url = urls.nextElement();
    UrlResource resource = new UrlResource(url);
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    for (Map.Entry<?, ?> entry : properties.entrySet()) {
   
     String factoryTypeName = ((String) entry.getKey()).trim();
     for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
   
      result.add(factoryTypeName, factoryImplementationName.trim());
     }
    }
   }
   cache.put(classLoader, result);
   return result;
  }
 }

一步步追踪下去可以看到最终就是通过SPI机制根据接口类型从META-INF/spring.factories文件中加载对应的实现类并实例化,SpringBoot的自动配置也是这样实现的。

为什么要这样实现呢?通过注解扫描不可以么?当然不行,这些类都在第三方jar包中,注解扫描实现是很麻烦的,当然你也可以通过@Import注解导入,但是这种方式不适合扩展类特别多的情况,所以这里采用SPI的优点就显而易见了。

回到run方法中,可以看到调用了createApplicationContext方法,见名知意,这个就是去创建应用上下文对象:

 public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
   + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

 protected ConfigurableApplicationContext createApplicationContext() {
   
  	Class<?> contextClass = this.applicationContextClass;
  	if (contextClass == null) {
   
   			try {
   
    				switch (this.webApplicationType) {
   
    						case SERVLET:
     								contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
     								break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
               		  contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
    				}
   			}
   			catch (ClassNotFoundException ex) {
   
    				throw new IllegalStateException(
      				"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
   			}
  	}
  	return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

注意这里通过反射实例化了一个新的没见过的上下文对象AnnotationConfigServletWebServerApplicationContext,这个是SpringBoot扩展的,看看其构造方法:

 public AnnotationConfigServletWebServerApplicationContext() {
   
 		this.reader = new AnnotatedBeanDefinitionReader(this);
 		this.scanner = new ClassPathBeanDefinitionScanner(this);
 }
  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达分柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值