SpringBoot run()方法全链路解析
一、SpringBoot启动流程概述
1.1 SpringBoot应用的入口点
SpringBoot应用的启动通常从main方法开始,其中最核心的是调用SpringApplication.run()方法:
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
这行简单的代码背后隐藏着复杂的启动流程,SpringApplication.run()方法是整个SpringBoot应用启动的核心入口,它负责创建并配置Spring应用上下文、启动嵌入式服务器等一系列关键操作。
1.2 启动流程的主要阶段
SpringBoot应用的启动流程可以大致分为以下几个主要阶段:
- 初始化阶段:创建SpringApplication实例,准备应用上下文环境
- 配置加载阶段:加载并解析应用配置,包括application.properties/yml文件、命令行参数等
- 自动配置阶段:基于类路径和配置信息,应用自动配置机制
- 上下文创建阶段:创建并刷新Spring应用上下文
- 嵌入式服务器启动阶段:启动嵌入式Web服务器,处理HTTP请求
- 应用就绪阶段:发布应用就绪事件,标志应用已成功启动
1.3 核心组件概述
在启动过程中,涉及到多个核心组件:
- SpringApplication:启动SpringBoot应用的核心类,负责协调整个启动过程
- ApplicationContext:Spring应用上下文,管理应用中的所有Bean
- Environment:应用环境抽象,负责加载和管理配置属性
- ApplicationEventPublisher:事件发布器,负责发布应用生命周期事件
- AutoConfiguration:自动配置机制,基于条件注解自动配置应用
二、SpringApplication实例的创建
2.1 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);
}
可以看到,最终会创建一个SpringApplication实例并调用其run()方法。
2.2 SpringApplication构造函数
SpringApplication的构造函数会执行一系列初始化操作:
public SpringApplication(Class<?>... primarySources) {this(null, primarySources);
}@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));// 判断应用类型(REACTIVE, SERVLET, NONE)this.webApplicationType = WebApplicationType.deduceFromClasspath();// 设置初始化器setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 设置监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 推断主应用类this.mainApplicationClass = deduceMainApplicationClass();
}
2.3 应用类型的推断
在构造函数中,会通过WebApplicationType.deduceFromClasspath()方法推断应用类型:
static WebApplicationType deduceFromClasspath() {// 判断是否存在REACTIVE相关类if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}// 判断是否存在SERVLET相关类for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}return WebApplicationType.SERVLET;
}
这里通过检查类路径中是否存在特定的类来判断应用类型:
- REACTIVE:存在org.springframework.web.reactive.DispatcherHandler且不存在org.springframework.web.servlet.DispatcherServlet
- SERVLET:存在javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext
- NONE:既不是REACTIVE也不是SERVLET
2.4 初始化器和监听器的加载
SpringApplication会从META-INF/spring.factories文件中加载初始化器和监听器:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});
}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// 从META-INF/spring.factories加载指定类型的类Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 实例化这些类List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);// 排序AnnotationAwareOrderComparator.sort(instances);return instances;
}
SpringFactoriesLoader.loadFactoryNames()方法会从所有META-INF/spring.factories文件中查找指定类型的实现类:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {// 查找所有META-INF/spring.factories资源Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();// 解析每个spring.factories文件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();String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}
}
2.5 主应用类的推断
SpringApplication会尝试推断主应用类:
private Class<?> deduceMainApplicationClass() {try {// 获取当前线程的堆栈轨迹StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {if ("main".equals(stackTraceElement.getMethodName())) {// 返回包含main方法的类return Class.forName(stackTraceElement.getClassName());}}}catch (ClassNotFoundException ex) {// 忽略异常}return null;
}
三、run()方法的核心流程
3.1 核心run()方法实现
SpringApplication实例创建完成后,会调用其run()方法:
public ConfigurableApplicationContext run(String... args) {// 记录启动开始时间long startTime = System.nanoTime();// 创建默认的应用上下文和异常报告器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);// 配置忽略的Bean信息configureIgnoreBeanInfo(environment);// 打印BannerBanner printedBanner = printBanner(environment);// 创建应用上下文context = createApplicationContext();// 设置引导上下文context.setBootstrapContext(bootstrapContext);// 准备上下文prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 刷新上下文refreshContext(context);// 刷新后的处理afterRefresh(context, applicationArguments);// 计算并记录启动时间Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);// 记录启动完成日志if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 发布应用就绪事件listeners.started(context, timeTakenToStartup);// 调用所有RunnercallRunners(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) {// 处理就绪异常if (context != null) {context.close();throw new IllegalStateException("Application run failed", ex);}throw new IllegalStateException("Application run failed", ex);}// 返回应用上下文return context;
}
3.2 启动监听器的初始化与启动
在run()方法开始时,会初始化并启动应用监听器:
private SpringApplicationRunListeners getRunListeners(String... args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),this.applicationStartup);
}// SpringApplicationRunListeners类
public void starting(DefaultBootstrapContext bootstrapContext, @Nullable Class<?> mainApplicationClass) {doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),(step) -> {if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}return step;});
}
SpringApplicationRunListener是SpringBoot应用启动过程中的核心监听器接口,它定义了应用启动各个阶段的回调方法。
3.3 应用参数与环境的准备
run()方法会创建并准备应用参数和环境:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// 创建并配置环境ConfigurableEnvironment environment = getOrCreateEnvironment();configureEnvironment(environment, applicationArguments.getSourceArgs());// 应用属性转换服务ConfigurationPropertySources.attach(environment);// 发布环境准备就绪事件listeners.environmentPrepared(bootstrapContext, environment);// 绑定应用参数bindToSpringApplication(environment);// 确保是标准环境if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}// 配置属性源ConfigurationPropertySources.attach(environment);return environment;
}
3.4 Banner的打印
SpringBoot应用启动时会打印Banner:
private Banner printBanner(ConfigurableEnvironment environment) {if (this.bannerMode == Banner.Mode.OFF) {return null;}// 获取资源加载器ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(getClassLoader());// 创建Banner打印器SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);// 打印控制台Bannerif (this.bannerMode == Banner.Mode.CONSOLE) {return bannerPrinter.print(environment, this.mainApplicationClass, System.out);}// 打印日志Bannerreturn bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
Banner打印器会尝试从classpath下加载banner.txt或banner.gif等文件作为Banner:
public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {Banner banner = getBanner(environment);banner.printBanner(environment, sourceClass, out);return banner;
}private Banner getBanner(Environment environment) {Banners banners = new Banners();// 添加文本Bannerbanners.addIfNotNull(getTextBanner(environment));// 添加图像Bannerbanners.addIfNotNull(getImageBanner(environment));// 如果没有找到任何Banner,使用默认Bannerif (banners.hasAtLeastOneBanner()) {return banners;}// 返回默认Bannerreturn this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
}
3.5 应用上下文的创建与准备
run()方法会创建并准备应用上下文:
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 to create a default ApplicationContext, please specify an ApplicationContextClass",ex);}}// 实例化上下文return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// 设置环境context.setEnvironment(environment);// 应用上下文后置处理器postProcessApplicationContext(context);// 应用初始化器applyInitializers(context);// 发布上下文准备就绪事件listeners.contextPrepared(context);// 加载资源if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// 添加单例BeanConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}// 设置懒加载if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}// 加载主资源if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// 加载所有资源Set<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");// 加载主配置类load(context, sources.toArray(new Object[0]));// 发布上下文加载完成事件listeners.contextLoaded(context);
}
四、应用上下文的刷新
4.1 刷新上下文的核心方法
run()方法中调用的refreshContext(context)会触发应用上下文的刷新:
private void refreshContext(ConfigurableApplicationContext context) {// 如果需要刷新,则刷新上下文if (this.registerShutdownHook) {try {// 注册关闭钩子,确保应用优雅关闭context.registerShutdownHook();}catch (AccessControlException ex) {// 忽略安全管理器异常}}// 刷新上下文refresh(context);
}protected void refresh(ConfigurableApplicationContext context) {// 调用标准的上下文刷新方法context.refresh();
}
4.2 AbstractApplicationContext的refresh()方法
refresh()方法是Spring应用上下文的核心方法,定义在AbstractApplicationContext中:
@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 准备刷新上下文prepareRefresh();// 获取并刷新Bean工厂ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 配置Bean工厂prepareBeanFactory(beanFactory);try {// 允许Bean工厂后置处理器对Bean定义进行后置处理postProcessBeanFactory(beanFactory);// 调用所有注册的Bean工厂后置处理器invokeBeanFactoryPostProcessors(beanFactory);// 注册所有Bean后置处理器registerBeanPostProcessors(beanFactory);// 初始化消息源initMessageSource();// 初始化应用事件多播器initApplicationEventMulticaster();// 初始化其他特殊BeanonRefresh();// 检查并注册监听器registerListeners();// 实例化所有剩余的(非懒加载)单例finishBeanFactoryInitialization(beanFactory);// 完成刷新,发布相应事件finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// 销毁已创建的单例以避免资源悬空destroyBeans();// 重置"active"标志cancelRefresh(ex);// 传播异常到调用者throw ex;}finally {// 重置公共缓存resetCommonCaches();}}
}
4.3 Bean工厂的准备与后置处理
在refresh()方法中,会准备并配置Bean工厂:
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {// 设置Bean工厂的类加载器beanFactory.setBeanClassLoader(getClassLoader());// 设置Bean表达式解析器beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));// 添加属性编辑器注册器beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));// 添加Bean后置处理器beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));// 设置忽略的自动装配接口beanFactory.ignoreDependencyInterface(EnvironmentAware.class);beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);beanFactory.ignoreDependencyInterface(MessageSourceAware.class);beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);// 注册可解析的依赖beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);beanFactory.registerResolvableDependency(ResourceLoader.class, this);beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);beanFactory.registerResolvableDependency(ApplicationContext.class, this);// 添加ApplicationListener探测器beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));// 添加LoadTimeWeaver探测器if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));// 设置临时类加载器进行类型匹配beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}// 注册默认环境Beanif (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());}// 注册系统属性和环境变量if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());}// 注册系统环境if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());}
}
4.4 Bean工厂后置处理器的调用
refresh()方法会调用所有注册的Bean工厂后置处理器:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {// 调用BeanDefinitionRegistryPostProcessor类型的处理器PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());// 检测LoadTimeWeaver并准备编织器if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}
}
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()方法会处理所有BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor:
public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {// 保存已经处理的处理器名称,避免重复处理Set<String> processedBeans = new HashSet<>();// 处理BeanDefinitionRegistry类型的工厂if (beanFactory instanceof BeanDefinitionRegistry) {BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;// 区分普通的BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessorList<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();// 首先处理传入的BeanFactoryPostProcessorfor (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {BeanDefinitionRegistryPostProcessor registryProcessor =(BeanDefinitionRegistryPostProcessor) postProcessor;registryProcessor.postProcessBeanDefinitionRegistry(registry);registryProcessors.add(registryProcessor);}else {regularPostProcessors.add(postProcessor);}}// 现在,处理在上下文中定义的BeanDefinitionRegistryPostProcessorList<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();// 首先,调用实现PriorityOrdered接口的BeanDefinitionRegistryPostProcessorString[] postProcessorNames =beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}// 排序并调用sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();// 接下来,调用实现Ordered接口的BeanDefinitionRegistryPostProcessorpostProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}// 排序并调用sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();// 最后,调用所有其他的BeanDefinitionRegistryPostProcessor,直到不再有新的处理器出现boolean reiterate = true;while (reiterate) {reiterate = false;postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (!processedBeans.contains(ppName)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);reiterate = true;}}// 排序并调用sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);currentRegistryProcessors.clear();}// 调用所有BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);// 调用普通的BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);}else {// 如果不是BeanDefinitionRegistry类型的工厂,直接调用所有BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);}// 现在,调用在上下文中定义的所有BeanFactoryPostProcessorString[] postProcessorNames =beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);// 分离实现PriorityOrdered、Ordered和其他的BeanFactoryPostProcessorList<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();List<String> orderedPostProcessorNames = new ArrayList<>();List<String> nonOrderedPostProcessorNames = new ArrayList<>();for (String ppName : postProcessorNames) {if (processedBeans.contains(ppName)) {// 跳过已处理的处理器}else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));}else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}else {nonOrderedPostProcessorNames.add(ppName);}}// 首先,调用实现PriorityOrdered接口的BeanFactoryPostProcessorsortPostProcessors(priorityOrderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);// 接下来,调用实现Ordered接口的BeanFactoryPostProcessorList<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());for (String postProcessorName : orderedPostProcessorNames) {orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}sortPostProcessors(orderedPostProcessors, beanFactory);invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);// 最后,调用所有其他的BeanFactoryPostProcessorList<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());for (String postProcessorName : nonOrderedPostProcessorNames) {nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));}invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);// 清除缓存的合并Bean定义,因为后处理器可能修改了原始元数据beanFactory.clearMetadataCache();
}
4.5 Bean后置处理器的注册
refresh()方法会注册所有Bean后置处理器:
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
PostProcessorRegistrationDelegate.registerBeanPostProcessors()方法会注册并排序所有Bean后置处理器:
public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {// 获取所有Bean后置处理器的名称String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);// 注册BeanPostProcessorChecker,用于记录信息int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));// 分离实现PriorityOrdered、Ordered和其他的BeanPostProcessorList<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();List<String> orderedPostProcessorNames = new ArrayList<>();List<String> nonOrderedPostProcessorNames = new ArrayList<>();for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);priorityOrderedPostProcessors.add(pp);if (pp instanceof MergedBeanDefinitionPostProcessor) {internalPostProcessors.add(pp);}}else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}else {nonOrderedPostProcessorNames.add(ppName);}}// 首先,注册实现PriorityOrdered接口的BeanPostProcessorsortPostProcessors(priorityOrderedPostProcessors, beanFactory);registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);// 接下来,注册实现Ordered接口的BeanPostProcessorList<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());for (String ppName : orderedPostProcessorNames) {BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);orderedPostProcessors.add(pp);if (pp instanceof MergedBeanDefinitionPostProcessor) {internalPostProcessors.add(pp);}}sortPostProcessors(orderedPostProcessors, beanFactory);registerBeanPostProcessors(beanFactory, orderedPostProcessors);// 然后,注册所有其他的BeanPostProcessorList<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());for (String ppName : nonOrderedPostProcessorNames) {BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);nonOrderedPostProcessors.add(pp);if (pp instanceof MergedBeanDefinitionPostProcessor) {internalPostProcessors.add(pp);}}registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);// 最后,注册所有内部BeanPostProcessorsortPostProcessors(internalPostProcessors, beanFactory);registerBeanPostProcessors(beanFactory, internalPostProcessors);// 添加ApplicationListenerDetector以检测ApplicationListener BeanbeanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}
4.6 消息源与事件多播器的初始化
refresh()方法会初始化消息源和事件多播器:
protected void initMessageSource() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();// 检查是否存在用户定义的messageSource Beanif (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);// 如果messageSource是HierarchicalMessageSource类型,设置父消息源if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;if (hms.getParentMessageSource() == null) {// 只有在没有父消息源的情况下设置,避免覆盖用户设置hms.setParentMessageSource(getInternalParentMessageSource());}}if (logger.isTraceEnabled()) {logger.trace("Using MessageSource [" + this.messageSource + "]");}}else {// 创建并注册默认的DelegatingMessageSourceDelegatingMessageSource dms = new DelegatingMessageSource();dms.setParentMessageSource(getInternalParentMessageSource());this.messageSource = dms;beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);if (logger.isTraceEnabled()) {logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");}}
}protected void initApplicationEventMulticaster() {ConfigurableListableBeanFactory beanFactory = getBeanFactory();// 检查是否存在用户定义的applicationEventMulticaster Beanif (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {this.applicationEventMulticaster =beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);if (logger.isTraceEnabled()) {logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");}}else {// 创建并注册默认的SimpleApplicationEventMulticasterthis.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);if (logger.isTraceEnabled()) {logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");}}
}
4.7 特殊Bean的初始化与刷新完成
refresh()方法会调用onRefresh()方法初始化特殊Bean:
protected void onRefresh() throws BeansException {// 对于Web应用上下文,这里会初始化Servlet上下文// 子类可以重写此方法以初始化其他特殊Bean
}
然后注册事件监听器并完成刷新:
protected void registerListeners() {// 注册静态指定的监听器for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// 从Bean工厂中获取所有ApplicationListener Bean并注册String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// 发布早期应用事件Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (earlyEventsToProcess != null) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}
}protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// 初始化类型转换器if (beanFactory.containsBean(TYPE_CONVERTER_BEAN_NAME)) {beanFactory.addPropertyEditorRegistrar(new BeanFactoryTypeConverter(beanFactory.getBean(TYPE_CONVERTER_BEAN_NAME, TypeConverter.class)));}// 初始化LoadTimeWeaverAware Beanif (!beanFactory.getTempClassLoader().getClass().getName().contains("ContextTypeMatchClassLoader")) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}// 冻结所有Bean定义,表明注册的Bean定义不会被修改或进一步后处理beanFactory.freezeConfiguration();// 实例化所有剩余的(非懒加载)单例beanFactory.preInstantiateSingletons();
}protected void finishRefresh() {// 清除资源缓存clearResourceCaches();// 初始化生命周期处理器initLifecycleProcessor();// 注册生命周期处理器的回调getLifecycleProcessor().onRefresh();// 发布ContextRefreshedEvent事件publishEvent(new ContextRefreshedEvent(this));// Participate in LiveBeansView MBean, if active.LiveBeansView.registerApplicationContext(this);
}
五、嵌入式服务器的启动
5.1 嵌入式服务器的创建与启动
在应用上下文刷新完成后,SpringBoot会启动嵌入式服务器:
// 在ServletWebServerApplicationContext类中
@Override
protected void onRefresh() {super.onRefresh();try {// 创建并启动Web服务器createWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}
}private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {// 获取ServletWebServerFactoryServletWebServerFactory factory = getWebServerFactory();// 创建Web服务器this.webServer = factory.getWebServer(getSelfInitializer());}else if (servletContext != null) {try {// 如果已经有Servlet上下文,初始化它getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context", ex);}}// 初始化属性源initPropertySources();
}protected ServletWebServerFactory getWebServerFactory() {// 获取ServletWebServerFactory Bean名称String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);if (beanNames.length == 0) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " +"ServletWebServerFactory bean.");}if (beanNames.length > 1) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " +"ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));}// 获取并返回ServletWebServerFactory Beanreturn getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
5.2 ServletWebServerFactory的实现
SpringBoot提供了多种嵌入式服务器的实现,如Tomcat、Jetty和Undertow:
// TomcatServletWebServerFactory类
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {// 创建Tomcat实例Tomcat tomcat = new Tomcat();// 配置基本目录File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());// 配置连接器Connector connector = new Connector(this.protocol);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);tomcat.getHost().setAutoDeploy(false);// 配置Engine和HostconfigureEngine(tomcat.getEngine());for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}// 准备上下文prepareContext(tomcat.getHost(), initializers);// 返回Web服务器包装器return getTomcatWebServer(tomcat);
}// JettyServletWebServerFactory类
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {// 创建Server实例Server server = new Server();// 配置连接器ServerConnector connector = new ServerConnector(server);if (this.port != null) {connector.setPort(this.port);}server.addConnector(connector);// 配置处理程序configureHandler(server);// 配置Sessionif (this.session != null && this.session.getTimeout() != null) {Duration timeout = this.session.getTimeout();long seconds = timeout.getSeconds();if (seconds >= 0) {this.sessionTimeout = (int) seconds;}}// 准备上下文ServletContextHandler contextHandler = prepareContext(server, initializers);// 返回Web服务器包装器return new JettyWebServer(server, this.autoStart, contextHandler);
}// UndertowServletWebServerFactory类
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {// 创建Undertow构建器Undertow.Builder builder = createBuilder(getPort());// 创建Servlet容器DeploymentInfo servletBuilder = getDeploymentInfo(initializers);DeploymentManager manager = defaultContainer.addDeployment(servletBuilder);manager.deploy();// 获取Servlet上下文try {ServletContext context = manager.start();configureContext(context, initializers);}catch (ServletException ex) {throw new WebServerException("Unable to start embedded Undertow server", ex);}// 添加Servlet部署到构建器builder.deploy(servletBuilder);// 创建并启动Undertow服务器Undertow undertow = builder.build();startDaemonAwaitThread(undertow);// 返回Web服务器包装器return new UndertowWebServer(undertow, this.autoStart, getPort() >= 0);
}
5.3 嵌入式服务器的配置与定制
嵌入式服务器的配置可以通过多种方式进行定制:
// 通过配置属性定制
@Configuration
public class WebServerCustomizationConfiguration {@Beanpublic WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {return factory -> {// 定制Tomcat服务器factory.addConnectorCustomizers(connector -> {// 配置连接器connector.setPort(8080);connector.setMaxThreads(200);connector.setMaxConnections(8192);});// 配置Tomcat引擎factory.addEngineCustomizers(engine -> {engine.setRealm(new MemoryRealm());});// 配置上下文factory.addContextCustomizers(context -> {context.setSessionTimeout(30);});};}@Beanpublic WebServerFactoryCustomizer<JettyServletWebServerFactory> jettyCustomizer() {return factory -> {// 定制Jetty服务器factory.addServerCustomizers(server -> {// 配置Jetty服务器server.setStopAtShutdown(true);server.setStopTimeout(5000);});// 配置连接器factory.addConnectorCustomizers(connector -> {connector.setIdleTimeout(30000);});};}@Beanpublic WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowCustomizer() {return factory -> {// 定制Undertow服务器factory.addBuilderCustomizers(builder -> {// 配置Undertow构建器builder.setIoThreads(4);builder.setWorkerThreads(20);});// 添加Servlet容器定制器factory.addDeploymentInfoCustomizers(deploymentInfo -> {deploymentInfo.setSessionTimeout(30);});};}
}
六、应用事件与监听器机制
6.1 应用事件的类型与发布
SpringBoot应用在启动过程中会发布多种事件:
// SpringApplicationRunListeners类
public void starting(DefaultBootstrapContext bootstrapContext, @Nullable Class<?> mainApplicationClass) {doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),(step) -> {if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}return step;});
}public void environmentPrepared(DefaultBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {doWithListeners("spring.boot.application.environment-prepared",(listener) -> listener.environmentPrepared(bootstrapContext, environment),(step) -> step.tag("configLocations", getConfigLocations(environment)));
}public void contextPrepared(ConfigurableApplicationContext context) {doWithListeners("spring.boot.application.context-prepared",(listener) -> listener.contextPrepared(context), null);
}public void contextLoaded(ConfigurableApplicationContext context) {doWithListeners("spring.boot.application.context-loaded",(listener) -> listener.contextLoaded(context), null);
}public void started(ConfigurableApplicationContext context, Duration timeTaken) {doWithListeners("spring.boot.application.started",(listener) -> listener.started(context, timeTaken), null);
}public void running(ConfigurableApplicationContext context, Duration timeTaken) {doWithListeners("spring.boot.application.running",(listener) -> listener.running(context, timeTaken), null);
}public void failed(ConfigurableApplicationContext context, Throwable exception) {doWithListeners("spring.boot.application.failed",(listener) -> listener.failed(context, exception), null);
}
6.2 事件监听器的注册与处理
应用事件监听器可以通过多种方式注册:
// 通过META-INF/spring.factories注册
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener// 通过@Bean方法注册
@Configuration
public class MyApplicationConfiguration {@Beanpublic ApplicationListener<ApplicationReadyEvent> readyListener() {return event -> {// 应用准备就绪后的处理逻辑System.out.println("Application is ready!");};}@Beanpublic ApplicationListener<ContextRefreshedEvent> refreshedListener() {return event -> {// 上下文刷新后的处理逻辑System.out.println("Context is refreshed!");};}
}// 通过@Component和@EventListener注解注册
@Component
public class MyApplicationListener {@EventListenerpublic void handleContextRefreshed(ContextRefreshedEvent event) {// 处理上下文刷新事件System.out.println("Context refreshed: " + event.getApplicationContext());}@EventListenerpublic void handleApplicationReady(ApplicationReadyEvent event) {// 处理应用就绪事件System.out.println("Application ready: " + event.getApplicationContext());}@EventListenerpublic void handleApplicationFailed(ApplicationFailedEvent event) {// 处理应用失败事件System.out.println("Application failed: " + event.getException().getMessage());}
}
6.3 事件处理的顺序与优先级
事件监听器可以通过实现Ordered接口或使用@Order注解来指定处理顺序:
// 实现Ordered接口
@Component
public class MyOrderedListener implements ApplicationListener<ApplicationReadyEvent>, Ordered {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// 处理应用就绪事件System.out.println("MyOrderedListener: Application is ready!");}@Overridepublic int getOrder() {// 返回优先级,值越小优先级越高return Ordered.HIGHEST_PRECEDENCE + 10;}
}// 使用@Order注解
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class MyAnnotatedListener implements ApplicationListener<ApplicationReadyEvent> {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// 处理应用就绪事件System.out.println("MyAnnotatedListener: Application is ready!");}
}
七、自动配置机制解析
7.1 自动配置的核心原理
SpringBoot的自动配置是基于条件注解实现的:
// EnableAutoConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";/*** 排除特定的自动配置类*/Class<?>[] exclude() default {};/*** 排除特定的自动配置类名*/String[] excludeName() default {};
}
AutoConfigurationImportSelector会从META-INF/spring.factories文件中加载所有自动配置类:
// AutoConfigurationImportSelector类
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}// 获取自动配置元数据AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);// 获取自动配置类AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);// 返回自动配置类名数组return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// 获取注解属性AnnotationAttributes attributes = getAttributes(annotationMetadata);// 加载候选自动配置类List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 移除重复项configurations = removeDuplicates(configurations);// 获取需要排除的配置类Set<String> exclusions = getExclusions(annotationMetadata, attributes);// 检查排除的配置类checkExcludedClasses(configurations, exclusions);// 移除需要排除的配置类configurations.removeAll(exclusions);// 应用自动配置过滤器configurations = filter(configurations, autoConfigurationMetadata);// 触发自动配置导入事件fireAutoConfigurationImportEvents(configurations, exclusions);// 返回自动配置条目return new AutoConfigurationEntry(configurations, exclusions);
}
7.2 条件注解的应用
SpringBoot使用多种条件注解来控制自动配置的生效条件:
// ConditionalOnClass注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {/*** 指定类必须存在于类路径上*/Class<?>[] value() default {};/*** 指定类名必须存在于类路径上*/String[] name() default {};
}// ConditionalOnMissingBean注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {/*** 指定必须不存在的Bean类型*/Class<?>[] value() default {};/*** 指定必须不存在的Bean名称*/String[] name() default {};/*** 指定查找Bean的搜索范围*/SearchStrategy search() default SearchStrategy.ALL;
}// ConditionalOnProperty注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {/*** 指定属性名前缀*/String prefix() default "";/*** 指定属性名*/String[] name() default {};/*** 指定属性值必须等于的值*/String havingValue() default "";/*** 指定当属性不存在时是否匹配*/boolean matchIfMissing() default false;
}
7.3 自动配置类的结构
自动配置类通常包含多个@Bean方法,并使用条件注解控制这些方法的生效条件:
// DataSourceAutoConfiguration类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic DataSourceInitializerInvoker dataSourceInitializerInvoker(DataSource dataSource, DataSourceProperties properties) {// 创建数据源初始化调用器return new DataSourceInitializerInvoker(dataSource, properties);}@Configuration(proxyBeanMethods = false)@Conditional(EmbeddedDatabaseCondition.class)@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")static class EmbeddedDatabaseConfiguration {@Bean@ConditionalOnMissingBeanpublic DataSource dataSource(DataSourceProperties properties) {// 创建嵌入式数据源return properties.initializeDataSourceBuilder().build();}}@Configuration(proxyBeanMethods = false)@Conditional(PooledDataSourceCondition.class)@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")@Import({ Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class,Generic.class, DataSourceJmxConfiguration.class })static class PooledDataSourceConfiguration {@Bean@ConditionalOnMissingBeanpublic DataSource dataSource(DataSourceProperties properties) {// 创建连接池数据源return properties.initializeDataSourceBuilder().build();}}// 其他内部配置类和方法...
}
八、命令行参数处理
8.1 命令行参数的解析
SpringBoot应用启动时会解析命令行参数:
// DefaultApplicationArguments类
public DefaultApplicationArguments(String... args) {Assert.notNull(args, "Args must not be null");this.source = new Source(args);this.args = args;
}private static class Source extends SimpleCommandLinePropertySource {Source(String[] args) {super(args);}@Overridepublic String[] getPropertyNames() {synchronized (this) {if (this.propertyNames == null) {// 初始化属性名数组this.propertyNames = extractPropertyNames(getOptionNames());}return this.propertyNames.clone();}}private String[] extractPropertyNames(Set<String> optionNames) {// 提取属性名return StringUtils.toStringArray(optionNames);}@Overridepublic boolean containsProperty(String name) {return getOptionNames().contains(name);}@Overridepublic Object getProperty(String name) {return getOptionValues(name);}
}
8.2 命令行参数的优先级
命令行参数的优先级高于其他配置源:
// ConfigFileApplicationListener类
private void addCommandLineProperties(ConfigurableEnvironment environment, String[] args) {if (args.length > 0) {// 创建命令行属性源String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;CommandLinePropertySource<?> source = new SimpleCommandLinePropertySource(args);// 移除默认的命令行属性源if (environment.getPropertySources().contains(name)) {environment.getPropertySources().replace(name, source);}else {// 添加命令行属性源,优先级最高environment.getPropertySources().addFirst(source);}}
}
8.3 自定义命令行参数处理器
开发者可以通过实现ApplicationRunner或CommandLineRunner接口来处理命令行参数:
// 实现CommandLineRunner接口
@Component
public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {// 处理命令行参数System.out.println("Command line arguments:");for (String arg : args) {System.out.println("- " + arg);}// 可以在这里执行应用启动后的初始化操作}
}// 实现ApplicationRunner接口
@Component
public class MyApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {// 处理应用参数System.out.println("Application arguments:");// 获取非选项参数List<String> nonOptionArgs = args.getNonOptionArgs();System.out.println("Non-option arguments: " + nonOptionArgs);// 获取选项参数Set<String> optionNames = args.getOptionNames();for (String optionName : optionNames) {List<String> optionValues = args.getOptionValues(optionName);System.out.println("Option '" + optionName + "': " + optionValues);}// 可以在这里执行应用启动后的初始化操作}
}
九、应用启动错误处理
9.1 启动异常的捕获与处理
SpringBoot应用启动过程中的异常会被捕获并处理:
// SpringApplication类
private void handleRunFailure(ConfigurableApplicationContext context, Throwable ex,SpringApplicationRunListeners listeners) {try {try {// 发布应用失败事件if (listeners != null) {listeners.failed(context, ex);}}catch (Throwable ex2) {logger.error("Error handling failed event", ex2);}// 记录启动失败日志if (context != null) {context.close();}// 报告异常reportFailure(getExceptionReporters(context), ex);}catch (Throwable ex3) {logger.error("Unable to close ApplicationContext", ex3);}// 重新抛出异常ReflectionUtils.rethrowRuntimeException(ex);
}private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {try {// 尝试使用异常报告器报告异常if (!exceptionReporters.isEmpty()) {boolean reported = false;for (SpringBootExceptionReporter reporter : exceptionReporters) {if (reporter.reportException(failure)) {reported = true;break;}}// 如果没有报告器处理异常,记录错误日志if (!reported && logger.isErrorEnabled()) {logger.error("Application run failed", failure);}
9.1 启动异常的捕获与处理
在handleRunFailure
方法中,除了发布应用失败事件和关闭上下文外,reportFailure
方法负责将异常信息以更友好的方式呈现。Spring Boot通过SpringBootExceptionReporter
接口的实现类来完成这一任务,常见的实现包括DefaultErrorPageExceptionReporter
等。
private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {try {if (!exceptionReporters.isEmpty()) {boolean reported = false;for (SpringBootExceptionReporter reporter : exceptionReporters) {if (reporter.reportException(failure)) {reported = true;break;}}if (!reported && logger.isErrorEnabled()) {logger.error("Application run failed", failure);}}} catch (Throwable ex) {if (logger.isDebugEnabled()) {logger.debug("Error reporting failure", ex);}if (logger.isErrorEnabled()) {logger.error("Application run failed", failure);}}
}
上述代码中,会依次尝试让每个SpringBootExceptionReporter
处理异常。如果有任何一个报告器处理成功(即reportException
方法返回true
),则认为异常已被妥善处理;若所有报告器都无法处理,且日志级别允许记录错误,就会直接记录异常信息到日志中。
以DefaultErrorPageExceptionReporter
为例,它主要用于Web应用场景,会尝试生成一个包含错误信息的HTML页面。在生成错误页面时,它会获取异常的详细堆栈信息、错误类型等内容,并结合Spring Boot默认的错误页面模板,将这些信息渲染到页面上。当应用启动过程中出现异常时,若满足一定条件(如在Web环境下),用户访问应用时就能看到这个详细的错误页面,而不是晦涩难懂的堆栈跟踪信息。
9.2 异常报告器的工作机制
FailureAnalyzers
类在异常分析过程中扮演着关键角色。它负责从类路径中加载所有实现了FailureAnalyzer
接口的类,并按照一定顺序对异常进行分析。
public class FailureAnalyzers {private final List<FailureAnalyzer> analyzers;private final Log logger;public FailureAnalyzers(ClassLoader classLoader, Log logger) {this(classLoader, null, logger);}public FailureAnalyzers(ClassLoader classLoader, List<FailureAnalyzer> analyzers, Log logger) {this.logger = logger;List<FailureAnalyzer> loadedAnalyzers = (analyzers != null) ? new ArrayList<>(analyzers): SpringFactoriesLoader.loadFactories(FailureAnalyzer.class, classLoader);AnnotationAwareOrderComparator.sort(loadedAnalyzers);this.analyzers = Collections.unmodifiableList(loadedAnalyzers);}public FailureAnalysis analyze(Throwable failure) {Throwable rootFailure = findRootCause(failure);if (rootFailure != null) {for (FailureAnalyzer analyzer : this.analyzers) {try {FailureAnalysis analysis = analyzer.analyze(rootFailure);if (analysis != null) {return analysis;}} catch (Throwable ex) {if (this.logger.isDebugEnabled()) {String message = ex.getMessage();message = (message != null) ? message : "no error message";this.logger.debug("FailureAnalyzer " + analyzer.getClass().getName()+ " failed to analyze cause: " + message, ex);}}}}return null;}private Throwable findRootCause(Throwable failure) {Throwable root = failure;Throwable cause;while ((cause = root.getCause()) != null) {root = cause;}return root;}
}
首先,在构造函数中,FailureAnalyzers
通过SpringFactoriesLoader
从META-INF/spring.factories
文件中加载所有的FailureAnalyzer
实现类,并使用AnnotationAwareOrderComparator
进行排序,确保按照优先级顺序处理。
在analyze
方法中,它会先找到异常的根本原因,然后依次让每个FailureAnalyzer
尝试分析该根本原因。每个FailureAnalyzer
实现类都有自己的分析逻辑,比如判断异常类型是否是自己能处理的,如果是,则根据异常信息生成一个FailureAnalysis
对象,该对象包含了错误描述和解决建议。一旦有FailureAnalyzer
成功生成FailureAnalysis
对象,就会立即返回,不再继续尝试其他分析器。
例如,DataSourceUnavailableFailureAnalyzer
专门用于分析数据库连接相关的异常。当应用启动时无法连接到数据库,抛出SQLException
等异常时,DataSourceUnavailableFailureAnalyzer
会判断异常是否属于数据库连接失败相关类型。如果是,它会从异常信息中提取出数据库URL、用户名等关键信息,并生成包含错误原因(如“无法连接到指定的数据库URL”)和解决建议(如“检查数据库URL是否正确,确保数据库服务已启动”)的FailureAnalysis
对象,帮助开发者快速定位和解决问题。
9.3 常见启动错误及解决方案
9.3.1 Bean定义冲突
当Spring容器中存在多个相同类型或名称的Bean定义时,就会引发Bean定义冲突问题。主要有两种常见情况:
- 类型冲突:多个
@Component
、@Service
、@Repository
等注解标注的类,或通过@Bean
方法定义的Bean,它们的类型相同。例如,定义了两个UserService
类:
@Service
public class UserService {// 业务逻辑
}@Service
public class AnotherUserService implements UserService {// 另一种实现的业务逻辑
}
当在其他类中通过@Autowired
注入UserService
时,Spring容器无法确定应该注入哪一个,就会抛出NoUniqueBeanDefinitionException
异常。
- 名称冲突:即使Bean类型不同,但如果通过
@Bean
方法显式指定了相同的Bean名称,也会产生冲突。比如:
@Configuration
public class AppConfig {@Bean("commonBean")public Object bean1() {return new Object();}@Bean("commonBean")public Object bean2() {return new Object();}
}
此时会抛出BeanDefinitionOverrideException
异常。
解决方案:
- 使用
@Primary
注解:在多个同类型Bean中,将其中一个标注为@Primary
,表示该Bean为首选注入对象。例如,在上述UserService
的例子中,可以将其中一个UserService
类标注为@Primary
:
@Service
@Primary
public class UserService {// 业务逻辑
}
这样在注入UserService
时,Spring会优先选择带有@Primary
注解的Bean。
- 使用
@Qualifier
注解:通过@Qualifier
指定具体的Bean名称或限定条件。在注入时明确指出要使用的Bean,如:
@Service
public class UserService {// 业务逻辑
}@Service
public class AnotherUserService {// 业务逻辑
}@Service
public class UserController {private final UserService userService;@Autowiredpublic UserController(@Qualifier("userService") UserService userService) {this.userService = userService;}
}
这里通过@Qualifier("userService")
指定了要注入的具体UserService
Bean。
- 检查并移除重复定义:仔细检查代码,删除不必要的重复Bean定义,确保每个Bean在容器中具有唯一性。
- 设置
spring.main.allow-bean-definition-overriding=true
:在application.properties
或application.yml
中设置该属性,允许Bean定义覆盖。但这种方式可能会导致一些难以排查的问题,不建议在生产环境中使用,仅用于开发调试阶段临时解决冲突。
9.3.2 端口占用
当Spring Boot应用使用嵌入式服务器(如Tomcat、Jetty、Undertow)启动时,会尝试绑定到指定的端口。如果该端口已经被其他进程占用,就会抛出类似于Address already in use: bind
的异常。例如,默认情况下,Spring Boot的Web应用会尝试绑定到8080端口,如果8080端口已被其他Web服务占用,应用启动就会失败。
解决方案:
- 修改端口配置:在
application.properties
或application.yml
文件中,通过server.port
属性指定其他未被占用的端口。如在application.properties
中设置:
server.port=8081
或在application.yml
中设置:
server:port: 8081
- 关闭占用端口的进程:通过系统命令(如在Windows下使用
netstat -ano | findstr :端口号
查看占用端口的进程ID,然后使用taskkill /pid 进程ID /f
关闭进程;在Linux下使用lsof -i :端口号
查看进程,使用kill -9 进程ID
关闭进程)找到并关闭占用该端口的应用程序。 - 使用随机端口:将
server.port
设置为0
,此时Spring Boot会随机分配一个可用的端口。不过这种方式在需要明确访问端口的场景下不太适用,一般用于测试环境或对端口没有固定要求的情况。
9.3.3 自动配置冲突
Spring Boot的自动配置机制会根据类路径下的依赖和配置属性,自动配置应用所需的各种组件。但当多个自动配置类都尝试配置同一组件,或者自动配置类与开发者自定义配置产生冲突时,就会出现自动配置冲突问题。例如,同时引入了两个不同的数据访问框架依赖,它们各自的自动配置类都想配置数据源相关组件,就可能导致冲突。
解决方案:
- 使用
@SpringBootApplication
的exclude
属性:在@SpringBootApplication
注解中,通过exclude
属性排除不需要的自动配置类。比如,不想使用Spring Boot默认的数据库连接池自动配置,可以这样写:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
- 使用条件注解控制:利用
@ConditionalOnMissingBean
、@ConditionalOnClass
、@ConditionalOnProperty
等条件注解,明确指定Bean创建的条件,避免冲突。例如,只有当类路径中不存在特定的数据源实现类时,才创建自定义的数据源Bean:
@Configuration
public class MyDataSourceConfig {@Bean@ConditionalOnMissingBean(DataSource.class)public DataSource customDataSource() {// 自定义数据源创建逻辑}
}
- 检查并排除冲突依赖:查看项目的依赖树,找出导致冲突的依赖项,通过
exclusions
排除不必要的依赖。例如,在Maven项目中:
<dependency><groupId>com.example</groupId><artifactId>example-dependency</artifactId><version>1.0.0</version><exclusions><exclusion><groupId>冲突依赖的groupId</groupId><artifactId>冲突依赖的artifactId</artifactId></exclusion></exclusions>
</dependency>
9.3.4 类路径问题
类路径问题主要包括缺少必要的依赖类,或者依赖类的版本不兼容,从而导致ClassNotFoundException
或NoClassDefFoundError
等异常。例如,项目中使用了某个第三方库的特定功能,但没有引入对应的依赖;或者引入的多个依赖之间存在版本冲突,导致某些类无法正确加载。
解决方案:
- 检查并添加缺失依赖:仔细查看异常信息中提示缺失的类,确定对应的依赖库,然后在项目的构建文件(如Maven的
pom.xml
或Gradle的build.gradle
)中添加正确的依赖。比如,在Maven项目中添加Jackson
依赖:
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.0</version>
</dependency>
- 解决依赖版本冲突:使用
dependencyManagement
统一管理依赖版本,避免不同依赖引入不一致的版本。例如,在Maven的pom.xml
中:
<dependencyManagement><dependencies><dependency><groupId>com.example</groupId><artifactId>common-dependency</artifactId><version>1.0.0</version></dependency></dependencies>
</dependencyManagement>
或者通过exclusions
排除冲突版本的依赖,再手动引入正确版本的依赖。
- 清理并重建项目:有时类路径问题可能是由于项目构建缓存导致的,可以尝试清理项目的构建缓存(如在IDEA中使用
File -> Invalidate Caches / Restart
),然后重新构建项目,确保类路径正确更新。
9.3.5 配置错误
配置错误涵盖多种情况,包括配置文件格式错误、属性值类型不匹配、属性名拼写错误、配置层级结构混乱等。在Spring Boot中,配置信息通常从application.properties
、application.yml
等文件加载,并通过@ConfigurationProperties
等机制绑定到Java类中。
例如,在application.yml
文件中,错误地缩进配置层级:
spring:datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
这里url
、username
、password
的缩进与datasource
同级,不符合YAML格式要求,会导致解析错误。
又如,属性值类型不匹配的情况:
myapp:max-connections: "ten"
假设MyAppConfig
类中max-connections
对应的属性是int
类型,那么将字符串"ten"
绑定到int
类型属性时就会失败。
解决方案:
- 检查配置文件格式:确保
application.properties
和application.yml
文件的格式正确。对于application.yml
,严格遵循YAML的缩进规则,每个层级的缩进保持一致;对于application.properties
,属性名和值之间使用正确的=
分隔,且不包含非法字符。可以借助IDE的语法检查功能,及时发现格式错误。 - 验证属性值类型:仔细核对配置文件中的属性值类型是否与配置类中定义的属性类型相匹配。如果需要进行类型转换,确保转换的可行性。例如,将字符串转换为
Date
类型时,可以使用@DateTimeFormat
注解进行格式化转换。 - 修正属性名拼写:认真检查属性名的拼写,确保与配置类中的
@ConfigurationProperties
注解的prefix
和属性名一致。对于复杂的配置类,可以使用IDE的重构功能修改属性名,避免手动修改导致的拼写错误。 - 使用配置验证:利用
@Validated
注解对配置类进行验证。在配置类的属性上添加验证注解,如@NotNull
、@Min
、@Max
等,在配置绑定完成后,Spring会自动对配置进行验证,若不满足验证条件,则会抛出异常,提示配置错误信息。例如:
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;@ConfigurationProperties(prefix = "myapp")
@Validated
public class MyAppConfig {@NotBlankprivate String name;@NotNull@Positiveprivate int age;// 省略getter和setter
}
这样当配置中的name
为空字符串,或者age
为负数或null
时,就会在启动时抛出验证异常,帮助开发者快速定位配置错误。
9.3.6 数据库连接失败
数据库连接失败是Spring Boot应用启动时常见的问题之一,主要原因包括数据库URL错误、用户名密码错误、数据库驱动版本不兼容、数据库服务未启动等。
例如,数据库URL中的IP地址、端口号、数据库名称写错:
spring:datasource:url: jdbc:mysql://192.168.1.100:3307/mydbusername: rootpassword: 123456
如果实际的数据库IP是192.168.1.101
,或者端口是3306
,就会导致连接失败。
又或者使用的数据库驱动版本与数据库不兼容,比如使用较旧的MySQL驱动连接新版本的MySQL数据库,可能会出现不支持的协议等问题。
解决方案:
- 检查数据库配置:仔细核对
application.properties
或application.yml
中的数据库连接配置,确保数据库URL、用户名、密码正确无误。可以尝试使用数据库客户端工具(如Navicat、DBeaver)连接数据库,验证配置信息是否能正常连接。 - 更新数据库驱动:根据所使用的数据库版本,选择合适的数据库驱动版本。在Maven或Gradle项目中,更新对应的依赖版本。例如,对于MySQL数据库,在Maven项目中:
解决方案:
- 更新数据库驱动:根据所使用的数据库版本,选择合适的数据库驱动版本。在Maven或Gradle项目中,更新对应的依赖版本。例如,对于MySQL数据库,在Maven项目中:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version> <!-- 根据实际情况选择合适的版本 -->
</dependency>
如果使用的是HikariCP连接池(Spring Boot默认),确保其版本与数据库驱动兼容。例如,HikariCP 4.x版本与MySQL Connector/J 8.x兼容。
- 检查数据库服务状态:确认数据库服务已启动且正常运行。可以通过系统服务管理工具(如Windows服务、Linux的systemctl命令)检查数据库服务状态。例如,在Linux上检查MySQL服务状态:
systemctl status mysql
如果服务未启动,使用相应命令启动服务:
systemctl start mysql
- 验证网络连接:确保应用服务器能够访问数据库服务器,检查网络连接是否正常,防火墙是否放行数据库端口。例如,MySQL默认使用3306端口,需要确保该端口在防火墙中是开放的。
- 配置连接属性:在数据库URL中添加必要的连接参数,如字符集、时区等。例如,对于MySQL:
spring:datasource:url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
其中,useUnicode
和characterEncoding
确保字符集正确,useSSL
设置是否使用SSL连接,serverTimezone
设置时区以避免时间相关问题。
- 使用连接测试工具:可以编写简单的测试代码,单独测试数据库连接,排除Spring Boot应用本身的问题。例如:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;public class DatabaseConnectionTest {public static void main(String[] args) {String url = "jdbc:mysql://localhost:3306/mydb";String username = "root";String password = "123456";try (Connection connection = DriverManager.getConnection(url, username, password)) {System.out.println("数据库连接成功!");} catch (SQLException e) {System.out.println("数据库连接失败:" + e.getMessage());e.printStackTrace();}}
}
9.3.7 组件扫描问题
Spring Boot通过组件扫描(Component Scanning)自动发现并注册应用中的组件(如@Component
、@Service
、@Repository
等注解标注的类)。当组件扫描配置不正确时,可能导致组件未被正确注册,从而在运行时出现NoSuchBeanDefinitionException
等异常。
常见原因:
- 组件类不在
@SpringBootApplication
(或@ComponentScan
)指定的扫描包路径下。默认情况下,Spring Boot会扫描@SpringBootApplication
所在类的同级包及其子包。 - 自定义了
@ComponentScan
注解,排除了需要扫描的包或类。 - 使用了错误的包名或类名。
解决方案:
- 调整扫描路径:确保组件类位于
@SpringBootApplication
所在类的同级包或子包中,或者通过@ComponentScan
显式指定扫描路径。例如:
@SpringBootApplication
@ComponentScan(basePackages = "com.example.myapp")
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
这里指定了扫描com.example.myapp
包及其子包。
- 检查排除配置:如果使用了
@ComponentScan
的excludeFilters
属性,确保没有错误地排除了需要的组件。例如:
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.myapp.excluded.*")
})
确保正则表达式或其他过滤条件不会意外排除关键组件。
- 使用明确的组件注册:对于无法通过组件扫描自动发现的组件,可以通过
@Configuration
类和@Bean
方法手动注册。例如:
@Configuration
public class AppConfig {@Beanpublic MyService myService() {return new MyService();}
}
9.3.8 依赖注入问题
依赖注入是Spring框架的核心特性之一,但在实际应用中可能会遇到各种问题,如循环依赖、注入类型不匹配、找不到合适的Bean等。
常见问题及解决方案:
- 循环依赖:当两个或多个Bean之间存在循环依赖关系时,Spring容器在创建这些Bean时会陷入无限循环,导致
BeanCurrentlyInCreationException
异常。例如:
@Service
public class ServiceA {@Autowiredprivate ServiceB serviceB;// 构造函数或方法使用serviceB
}@Service
public class ServiceB {@Autowiredprivate ServiceA serviceA;// 构造函数或方法使用serviceA
}
解决方案:
- 重构代码:重新设计类的结构,消除循环依赖。例如,提取公共逻辑到一个新的类中,让两个类都依赖于这个新类。
- 使用Setter注入或字段注入:将构造函数注入改为Setter注入或字段注入,允许Spring在Bean创建后再设置依赖。不过这种方法只是掩盖了设计问题,并没有真正解决它。
- 使用
@Lazy
注解:在其中一个依赖上使用@Lazy
注解,延迟加载该依赖,直到实际使用时才创建。例如:
@Service
public class ServiceA {private final ServiceB serviceB;@Autowiredpublic ServiceA(@Lazy ServiceB serviceB) {this.serviceB = serviceB;}
}
- 注入类型不匹配:当注入的目标类型与实际Bean的类型不匹配时,会抛出
BeanNotOfRequiredTypeException
异常。例如:
@Service
public class MyService {// 业务逻辑
}@Service
public class MyController {@Autowiredprivate AnotherService myService; // 类型不匹配,AnotherService与MyService不兼容
}
解决方案:
- 检查注入点的类型声明,确保与实际Bean的类型一致。
- 如果需要注入接口的实现类,确保实现类正确实现了该接口,并且在容器中注册为该接口类型的Bean。
- 找不到合适的Bean:当没有找到符合条件的Bean时,会抛出
NoSuchBeanDefinitionException
异常。例如:
@Service
public class MyService {@Autowiredprivate MyRepository repository; // 容器中没有MyRepository类型的Bean
}
解决方案:
- 确保
MyRepository
类被正确标注为@Repository
或其他组件注解,并且在组件扫描路径内。 - 检查是否存在拼写错误或包路径问题。
- 如果是自定义的Bean,确保在配置类中通过
@Bean
方法正确注册。
9.3.9 版本冲突问题
在Spring Boot应用中,版本冲突是常见的问题,特别是当引入多个第三方依赖时,可能会导致依赖库的不同版本之间存在兼容性问题。例如,不同的依赖可能引用了同一库的不同版本,导致类加载冲突或方法调用异常。
常见症状:
-
NoClassDefFoundError
或ClassNotFoundException
:表示找不到某个类,可能是因为依赖版本不一致导致某些类被覆盖或缺失。 -
NoSuchMethodError
:表示调用了不存在的方法,通常是由于依赖库版本不兼容,方法签名发生了变化。 - 运行时异常:如
IllegalArgumentException
、NullPointerException
等,可能是由于不同版本的库行为不一致导致的。
解决方案:
- 查看依赖树:使用Maven或Gradle的依赖树命令查看项目的依赖结构,找出冲突的依赖。例如,在Maven中使用:
mvn dependency:tree
在Gradle中使用:
gradle dependencies
- 统一依赖版本:使用
dependencyManagement
(Maven)或resolutionStrategy
(Gradle)统一管理依赖版本,确保同一库只使用一个版本。例如,在Maven的pom.xml
中:
<dependencyManagement><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.18</version></dependency></dependencies>
</dependencyManagement>
- 排除冲突依赖:在依赖声明中排除冲突的传递依赖。例如:
<dependency><groupId>com.example</groupId><artifactId>example-library</artifactId><version>1.0.0</version><exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion></exclusions>
</dependency>
- 使用Spring Boot的依赖管理:Spring Boot的
spring-boot-starter-parent
提供了一组默认的依赖版本,这些版本经过了测试,相互兼容。尽量使用Spring Boot推荐的版本,避免手动指定版本号,除非必要。
9.3.10 初始化Bean失败
当Bean的初始化过程中发生错误时,会导致应用启动失败。常见的原因包括:
- Bean的构造函数或初始化方法抛出异常。
- Bean依赖的其他组件未正确初始化。
- Bean的配置属性无效或不完整。
解决方案:
- 检查初始化方法:确保
@PostConstruct
注解的方法或InitializingBean
接口的afterPropertiesSet
方法没有抛出异常。例如:
@Service
public class MyService implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {// 初始化逻辑,确保不会抛出异常}
}
- 检查依赖组件:确保Bean依赖的其他组件已经正确初始化。可以通过日志或调试工具查看初始化顺序,找出依赖关系中的问题。
- 验证配置属性:确保注入到Bean中的配置属性有效且完整。可以使用
@Validated
注解和JSR-303验证注解对配置属性进行验证。 - 使用
@DependsOn
注解:如果Bean之间存在隐式依赖关系,可以使用@DependsOn
注解显式指定依赖顺序。例如:
@Service
@DependsOn("dataSource")
public class MyService {// 业务逻辑
}
这里确保dataSource
Bean在MyService
之前初始化。
9.4 调试技巧
当遇到Spring Boot应用启动失败的问题时,可以使用以下调试技巧来快速定位和解决问题:
9.4.1 启用调试日志
在application.properties
或application.yml
中添加以下配置,启用Spring的调试日志:
logging.level.root=INFO
logging.level.org.springframework=DEBUG
或在application.yml
中:
logging:level:root: INFOorg.springframework: DEBUG
这样可以获得更详细的启动过程日志,帮助分析问题所在。
9.4.2 使用断点调试
在IDE中设置断点,逐步调试应用的启动过程。特别是在SpringApplication.run()
方法和关键组件的初始化方法中设置断点,观察变量值和执行流程,找出异常发生的位置。
9.4.3 使用--debug
参数
在启动应用时添加--debug
参数,可以启用调试模式,输出更多的调试信息:
java -jar myapp.jar --debug
9.4.4 禁用自动配置
如果怀疑某个自动配置类导致了问题,可以使用@SpringBootApplication
的exclude
属性暂时禁用该自动配置类,观察应用是否能正常启动:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
9.4.5 使用条件断点
在复杂的应用中,可以使用条件断点,只在满足特定条件时暂停执行。例如,在异常抛出的代码行设置条件断点,当特定的异常类型或条件满足时触发断点。
9.4.6 检查环境配置
确保应用运行的环境配置正确,包括系统变量、环境变量、配置文件等。可以使用以下代码在应用启动时打印当前的环境配置:
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);Environment env = context.getEnvironment();System.out.println("Active profiles: " + Arrays.asList(env.getActiveProfiles()));System.out.println("Application properties: " + env.getPropertySources());}
}
9.5 总结
Spring Boot应用启动失败可能由多种原因导致,包括配置错误、依赖问题、组件扫描问题、数据库连接问题等。通过深入理解Spring Boot的启动机制和常见错误模式,结合有效的调试技巧,可以快速定位和解决启动问题。
在排查问题时,建议按照以下步骤进行:
- 查看详细的错误日志,确定异常类型和错误信息。
- 分析可能的原因,如配置错误、依赖冲突、组件缺失等。
- 使用调试工具和技巧,逐步定位问题所在。
- 针对具体问题,采取相应的解决方案,如修正配置、调整依赖版本、修改代码结构等。
- 验证解决方案的有效性,确保应用能够正常启动。
通过不断积累经验和掌握有效的排查方法,可以更加高效地解决Spring Boot应用启动过程中遇到的各种问题。