SpringBoot —SpringApplication.run()方法剖析

一、run

SpringBoot项目启动特别简单,只需要点击一下,一切就自动运行,其背后的原理呢?

本文会将源码拆分,最后部分会附上run方法的源码。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ServiceOrderApp {
    public static void main(String[] args) {
        SpringApplication.run(ServiceOrderApp.class,args);
    }
}

1.1

点击run方法后,我们会发现有两个重写的静态run方法和一个返回值是ConfigurableApplicationContext的run方法。这两个方法的使用规则是run方法进SpringApplication实例化操作,再根据实例化对象调用另一个run方法来完成整个项目的启动和初始化。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

1.2

       我们先跟踪一下SpringAppliaction的构造方法源码:

	@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 = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

1.2.1拆分SpringApplication构造方法

流程图

//resourceLoader:资源加载的接口,用来在启动时打印banner艺术字
//primarySources:可变参数,默认传入SpringBoot入口类。
也就是启动类的类。但是作为项目的入口类,此类还必须满足一个条件,
就是被@EnableConfiguration注解标注;
因为@SpringBootApplication注解也包含了此注解,
所以被@SpringBootApplication注解修饰的类也可以作为参数传入。
虽然也可以传入普通类,但是只有被@EnableConfiguration修饰的类才能开启自动配置!
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
//将入口类设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//获取到应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();

获取应用类型的方法deduceFromClasspath()比较简单,三种返回结果,四种常量;在初始化容器的时候,会对这四个常量进行判断,如果存在就返回对应的结果

  • NONE: 非web应用,即不会启动服务器
  • SERVLET: 基于servlet的web应用
  • REACTIVE: 响应式web应用(暂未接触过)
  • WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
  • JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
  • SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
          "org.springframework.web.context.ConfigurableWebApplicationContext" }
  • WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
//去meta-inf/spring.factories目录下查找ApplicationContextInitializer并且保存起来
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
//获取所有初始化器
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
getSpringFactoriesInstances()方法有两个重写方法,主要是下面的那个;

先走第一个方法

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

而这个方法调用的👇

//参数type:就是从上个方法传递过来的
//interface org.springframework.context.ApplicationContextInitializer

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;
	}
// 参数:
 	// Class<?> factoryType:需要被加载的工厂类的class
 	// ClassLoader classLoader:类加载器
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			// 若没传入类加载器,使用该本类的类加载器
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		// class.getName():获取该类的全类限定名字
		String factoryTypeName = factoryType.getName();
		// loadSpringFactories(classLoaderToUse) 返回是Map
		// Map.getOrDefault(A,B): A为Key,从Map中获取Value,若Value为Null,则返回B 当作返回值
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}
// 该方法是最核心部分:
// 1. 从项目中找到所有META-INF/spring.factories
// 2. 并解析所有spring.factories文件,生成 Map<一个接口类--->对应所有具体实现类集合>,即Map<key,List<String>>
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		// Map<ClassLoader, Map<String, List<String>>> cache:
		// 该缓存存储的是:一个类加载器对应 加载spring.factories的结果集
		// 这里类加载器的知识往后我再补吧,望有好心人提醒我
		Map<String, List<String>> result = cache.get(classLoader);
		// 有则说明已经加载过了,直接返回
		// Map<String, List<String>> result:
		// key: 接口类的名字--->value: 具体实现类的名字
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			// public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
			// springboot项目部署之后如何读取到 Resource(ClassPath)下的资源,请看下面博客:
			// https://blog.csdn.net/xueyijin/article/details/121441738
			// Enumeration:可以认为是旧版的迭代器,目前被Iterator取代
			// 加载spring.factories资源并封装为URL对象
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				// UrlResource 类是spring 对URL资源一种封装而已
				UrlResource resource = new UrlResource(url);
				// 解析properties文件,因为spring.factories 本身就是以key-->value来书写的
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					// key:接口全限定名字
					String factoryTypeName = ((String) entry.getKey()).trim();
					// 因为value:是可以写多个,并且要求之间 用,\ 来隔开
					// commaDelimitedListToStringArray方法就是把value的值,解析并封装为数组
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						// result 格式:Map<String, List<String>>
						// computeIfAbsent(key,function):
							// 若key 找不到则执行function方法得到value,并put(key,value)
							// 若key 找的到则直接返回get(key)的值,这里则是List<String>因此后面可以直接跟list.add(String)方法
						// Map中compute()、computeIfPresent()、computeIfAbsent()、merge()的使用以及原理,请看这篇博客:
						// https://blog.csdn.net/xueyijin/article/details/122851868
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			// replaceAll(BiFunction):将对map中的value值做一系列变化操作,比如原来key:5,value:5 可以改成key:5 value:5-5
			// 由BiFunction 实现最终value值的逻辑
			// Collections::unmodifiableList:是不可修改的List类,如果执行set、add等修改操作,直接报错,具体下文会说
			// 把spring.factories的key对应的value结果集去重,并且转化为不可变List类
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			// 放入缓存,防止多次执行上述操作,提高性能
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}
//获取所有监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//定位main方法
this.mainApplicationClass = deduceMainApplicationClass();
	private Class<?> deduceMainApplicationClass() {
		try {
        //通过创建运行时异常的方式获取栈
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        //遍历获取main方法所在的类并且返回
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

整个流程就是这样,参数为rsourceLoader用来打印艺术字,primarySources用来接收入口类;

288行:获取到的入口类实例;

289行:获取应用类型
290行:去meta-inf/spring.factories目录下查找ApplicationContextInitializer并且保存起来
291行:获取所有的初始化器

292行:获取所有的监听器

293行:定位main方法;

1.3run()

了解完构造方法所有的功能之后,现在要做的事情就是走到run()方法了,拆分了解,代码沾到最后。

 首先是构造SpringApplication实例代码,

return new SpringApplication(primarySources).run(args);

会将当前入口类(SpringApplication.class:你自己入口类的名字)传入构造方法,构造方法中会将此参数放在

this.primarySources进行存储。(看构造方法)

run方法:

//用来记录当前SpringBoot启动耗时
StopWatch stopWatch = new StopWatch();
//记录启动开始时间
		stopWatch.start();

//创建引导启动器,类似一个ApplicationContext,可以往里面添加一些对象。
DefaultBootstrapContext bootstrapContext = createBootstrapContext();

/*
*去spring.factroies中读取了 org.springframework.boot.SpringApplicationRunListener为key的对
*象,默认是EventPublishingRunListener对象,
*然后封装进SpringApplicationRunListeners 对象中,
*此对象还是比较有用的,用来发布springboot启动进行中的各个状态的事件,
*上面方法中读取到的监听器就可以监听到这些事件,
*所以可以运用这些特性进行自己的扩展。
*/
 SpringApplicationRunListeners listeners = getRunListeners(args);

//发布ApplicationStartingEvent事件,在运行开始时发送 ,发送springboot启动开始事件;
listeners.starting(bootstrapContext, this.mainApplicationClass);

try{}里面的方法:

//根据启动项目命令所带的参数创建applicationArguments 对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//根据SpringApplicationRunListeners对象和参数来准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//打字:启动SpringBoot的时候打印在控制台上的ASCII艺术字
Banner printedBanner = printBanner(environment);
//根据容器类型,创建对应的Spring上下文,也就是ApplicainContext上下文对象。
context = createApplicationContext();
//给spring上下文赋值DefaultApplicationStartup对象
context.setApplicationStartup(this.applicationStartup);
//预初始化上下文,添加environment,上面获取到的initializers集合中的ApplicationContextInitializer
//对象执行其入参为上下文initialize方法,对上下文做编辑,所以此处我们可以做扩展;发送
//ApplicationContextInitializedEvent容器初始化事件;发送BootstrapContextClosedEvent事件;最重
//要的一个方法就是把启动配置类注册成了beanDefinition;发送ApplicationPreparedEvent事件,并把
//listeners集合属性中的事件添加到上下文中;
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新容器,和spring中的refresh()方法的作用是一样的,主要的作用就是读取所有的bean转成、
//beanDefinition然后再创建bean对象;不过这个容器重写了其中的onRefresh()方法,在此方法中,创建了
//springboot内置的tomcat对象并进行启动;接下来特别说明一下这个内置tomcat
refreshContext(context);
//Spring容器后置处理
        afterRefresh(context, applicationArguments);
//打印启动时间
if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}


 

//发布容器启动事件ApplicationStartedEvent;
//发布AvailabilityChangeEvent容器;
listeners.started(context);

started源码👇
@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}
//执行容器中ApplicationRunner、ApplicationRunner类型对象的run方法
callRunners(context, applicationArguments);
//发布ApplicationReadyEvent容器已经正常事件
listeners.running(context);
prepareEnvironment()
        getOrCreateEnvironment()👇
private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new ApplicationServletEnvironment();
		case REACTIVE:
			return new ApplicationReactiveWebEnvironment();
		default:
			return new ApplicationEnvironment();
		}
	}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // 根据webApplicationType 创建Environment  创建就会读取: java环境变量和系统环境变量
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   // 将命令行参数读取环境变量中
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   // 将@PropertieSource的配置信息 放在第一位, 因为读取配置文件@PropertieSource优先级是最低的
   ConfigurationPropertySources.attach(environment);
   // 发布了ApplicationEnvironmentPreparedEvent 的监听器  读取了全局配置文件
   listeners.environmentPrepared(environment);
   // 将所有spring.main 开头的配置信息绑定SpringApplication
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   //更新PropertySources
   ConfigurationPropertySources.attach(environment);
   return environment;
}
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

1.4分析细节处理

监听器

// 1:获取并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();

这部分代码,用的也是此方法

SpringFactoriesLoader.loadFactoryNames(type, classLoader),

目的是去spring.properties中获取SpringApplicationRunListener实现类的全限定名,然后通过反射构造实例,再将实例给引用的类型:

SpringApplicationRunListeners容器来管理

创建容器

跟踪源码

context = createApplicationContext();

 我们再次点击create();有两个实现类,点击进去

@Override
		public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
			return (webApplicationType != WebApplicationType.SERVLET) ? null
					: new AnnotationConfigServletWebServerApplicationContext();
		}
@Override
		public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
			return (webApplicationType != WebApplicationType.REACTIVE) ? null
					: new AnnotationConfigReactiveWebServerApplicationContext();
		}

我们发现会进行WebApplicationType的判断,如果为SERVLET,则返回的容器是AnnotationConfigServletWebServerApplicationContext

兄弟们需要注意,这两个类型全都是继承了或者变量继承了GenericApplicationContext

容器前置处理

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

在容器的前置准备中,我追踪了很深的源码,注意看

其中,重要的是prepareContext完成了对启动类的加载,注入到容器

追踪getAllSources()方法👇

	public Set<Object> getAllSources() {
		Set<Object> allSources = new LinkedHashSet<>();
		if (!CollectionUtils.isEmpty(this.primarySources)) {
			allSources.addAll(this.primarySources);
		}
		if (!CollectionUtils.isEmpty(this.sources)) {
			allSources.addAll(this.sources);
		}
		return Collections.unmodifiableSet(allSources);
	}
其中参数this.primarySources,debug我们发现是我们的启动类!!!

 也就是说,Set<Object> sources = getAllSources();返回的sources是我们的启动类!

然后注意看下一行代码👇

load(context, sources.toArray(new Object[0]));

sources.toArray(new Object[0]))不用说了,就是启动类对象,context也是我们上面获取的应用上下文对象,一切都很清晰,接着向下走👇

protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}

在这里,BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);这行代码创建了BeanDefinitionLoader对象,并且也是使用的其load()方法去实现的,这个createBeanDefinitionLoader()方法也是new了一个新的BeanDefinitionLoader实例

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
   return new BeanDefinitionLoader(registry, sources);
}
然后进入load()👇
	private void load(Object source) {
		Assert.notNull(source, "Source must not be null");
		if (source instanceof Class<?>) {
			load((Class<?>) source);
			return;
		}
		if (source instanceof Resource) {
			load((Resource) source);
			return;
		}
		if (source instanceof Package) {
			load((Package) source);
			return;
		}
		if (source instanceof CharSequence) {
			load((CharSequence) source);
			return;
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}

将启动类对象传递进来比较为true,走load((Class<?>) source方法,

private void load(Class<?> source) {
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
		}
		if (isEligible(source)) {
			this.annotatedReader.register(source);
		}
	}

第一个判断为  判断使用groovy脚本,false跳过;第二个判断 (判断类是否符合条件){ //使用注解读取器注册资源};后续该启动类将 作为开启自动化配置的入口

刷新容器

前面都是SpringBoot的工作,Bean放在IOC容器中,从现在开始就是Spring的部分了。

太多了,不写了

从 SpringApplication.run 开始

后置处理

afterRefresh(context, applicationArguments);
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

截止时间记录

stopWatch.stop();

发出结束执行的事件

listeners.started(context)
	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}

一个是发布容器启动事件ApplicationStartedEvent,

另一个是发布AvailabilityChangeEvent容器可用于实践

callRunners

callRunners(context, applicationArguments)
private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

SpringBoot源码解析(二十)ApplicationRunner_一元咖啡的博客-CSDN博客_spring applicationrunner原理可以自定义 ApplicationRunner 或 CommandLineRunner,实现 run 方法,注入到容器中, 在SpringBoot 启动后,就会执行所有的 runner 的 run 方法。

return context

END

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据你提供的引用内容,"org.springframework.boot.SpringApplication - Application run failed"错误可能有多个原因。根据引用,可能是在配置SpringBoot的profile时出现了问题。确保你的.yml文件与老师提供的内容完全相同,并且没有任何拼写错误或格式错误。如果问题仍然存在,你可以根据引用,逐句分析你的报错情况。下面是一些常见的原因和解决方法: 1. 确保你在启动类上添加了`@EnableAutoConfiguration`注解。 2. 确保你在启动类上添加了`@SpringBootApplication`注解。 3. 如果你的项目中包含了tomcat相关的依赖,可以删除maven依赖并重新下载来解决。 4. 如果你的项目中使用了`spring-boot-starter-parent`依赖,可能会出现依赖冲突问题。尝试删除其中一个依赖即可。 5. 确保你的启动类与项目在同一级目录下。 请根据你的具体情况逐一排查这些问题,并尝试解决。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [解决ERROR org.springframework.boot.SpringApplication -- Application run failed报错问题](https://blog.csdn.net/qq_42294095/article/details/129828174)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Spring boot 启动失败 ERROR org.springframework.boot.SpringApplication - Application run failed](https://blog.csdn.net/m0_67391121/article/details/123682518)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

你好,我是一名保安

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

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

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

打赏作者

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

抵扣说明:

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

余额充值