• Spring Boot 异常处理


    Spring Boot 异常处理

    本节介绍一下 Spring Boot 启动时是如何处理异常的?核心类是 SpringBootExceptionReporter 和 SpringBootExceptionHandler。

    一、Spring Boot 异常处理流程

    Spring Boot 异常处理流程

    public ConfigurableApplicationContext run(String... args) {
    	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    	try {
    		exceptionReporters = getSpringFactoriesInstances(
    				SpringBootExceptionReporter.class,
    				new Class[] { ConfigurableApplicationContext.class }, context);
    	catch (Throwable ex) {
    		handleRunFailure(context, ex, exceptionReporters, listeners);
    		throw new IllegalStateException(ex);
    	}
    
    	try {
    		listeners.running(context);
    	} catch (Throwable ex) {
    		// 处理异常
    		handleRunFailure(context, ex, exceptionReporters, null);
    		throw new IllegalStateException(ex);
    	}
    	return context;
    }
    

    run 方法中的异常处理都交给 handleRunFailure 完成。

    private void handleRunFailure(ConfigurableApplicationContext context,
    		Throwable exception,
    		Collection<SpringBootExceptionReporter> exceptionReporters,
    		SpringApplicationRunListeners listeners) {
    	try {
    		try {
    			// 1. ExitCodeGenerators 根据异常获取是正常不是异常退出
    			handleExitCode(context, exception);
    			if (listeners != null) {
    				listeners.failed(context, exception);
    			}
    		} finally {
    			// 2. SpringBootExceptionReporter 处理异常报告
    			reportFailure(exceptionReporters, exception);
    			if (context != null) {
    				context.close();
    			}
    		}
    	} catch (Exception ex) {
    		logger.warn("Unable to close ApplicationContext", ex);
    	}
    	// 3. 重新报出异常,由 SpringBootExceptionHandler 处理
    	ReflectionUtils.rethrowRuntimeException(exception);
    }
    

    handleRunFailure 中主要依赖了三个组件完成异常的处理:

    • SpringBootExceptionReporter 生成错误报告并处理,主要是用于输出日志。
    • SpringBootExceptionHandler 实现了 Thread#UncaughtExceptionHandler 接口,可以在线程异常关闭的时候进行回调。主要用于退出程序 System.exit(xxx)
    • SpringApplicationRunListeners Spring Boot 事件机制

    1.1 handleExitCode

    handleExitCode 根据异常的类型决定如何退出程序,并将 exitCode(0 或 1) 退出码注册到 SpringBootExceptionHandler 上。

    private void handleExitCode(ConfigurableApplicationContext context,
    		Throwable exception) {
    	// 根据异常判断是正常退出还是异常退出
    	int exitCode = getExitCodeFromException(context, exception);
    	if (exitCode != 0) {
    		if (context != null) {
    			context.publishEvent(new ExitCodeEvent(context, exitCode));
    		}
    		SpringBootExceptionHandler handler = getSpringBootExceptionHandler();
    		if (handler != null) {
    			// 正常退出或异常退出,System.exit(exitCode) 用
    			handler.registerExitCode(exitCode);
    		}
    	}
    }
    

    getExitCodeFromException 根据异常判断是正常退出还是异常退出,委托给了 ExitCodeGenerators,最后将退出码注册到 SpringBootExceptionHandler 上。

    1.2 reportFailure

    private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters,
    		Throwable failure) {
    	try {
    		for (SpringBootExceptionReporter reporter : exceptionReporters) {
    			if (reporter.reportException(failure)) {
    				registerLoggedException(failure);
    				return;
    			}
    		}
    	} catch (Throwable ex) {
    	}
    	if (logger.isErrorEnabled()) {
    		logger.error("Application run failed", failure);
    		registerLoggedException(failure);
    	}
    }
    

    reportFailure 委托 SpringBootExceptionReporter 处理异常,并将异常注册到 SpringBootExceptionHandler 上。

    二、ExitCodeGenerators

    private int getExitCodeFromException(ConfigurableApplicationContext context,
    		Throwable exception) {
    	// ExitCodeGenerators 处理异常
    	int exitCode = getExitCodeFromMappedException(context, exception);
    	// 如果没有分析出来,则判断这个异常本身是实现了 ExitCodeGenerator 接口,继续分析
    	if (exitCode == 0) {
    		exitCode = getExitCodeFromExitCodeGeneratorException(exception);
    	}
    	return exitCode;
    }
    
    // 从 context 中获取所有的 ExitCodeExceptionMapper 来分析异常
    private int getExitCodeFromMappedException(ConfigurableApplicationContext context,
    		Throwable exception) {
    	if (context == null || !context.isActive()) {
    		return 0;
    	}
    	ExitCodeGenerators generators = new ExitCodeGenerators();
    	Collection<ExitCodeExceptionMapper> beans = context
    			.getBeansOfType(ExitCodeExceptionMapper.class).values();
    	// 将 exception 和 ExitCodeExceptionMapper 封装成 ExitCodeGenerator 注册到 generators 中
    	generators.addAll(exception, beans);
    	return generators.getExitCode();
    }
    
    // 异常本身实现了 ExitCodeGenerator 接口
    private int getExitCodeFromExitCodeGeneratorException(Throwable exception) {
    	if (exception == null) {
    		return 0;
    	}
    	if (exception instanceof ExitCodeGenerator) {
    		return ((ExitCodeGenerator) exception).getExitCode();
    	}
    	return getExitCodeFromExitCodeGeneratorException(exception.getCause());
    }
    

    ExitCodeGenerator 和 ExitCodeExceptionMapper 接口如下,ExitCodeGenerators 管理多个 ExitCodeGenerator。Spring 将 exception 和 ExitCodeExceptionMapper 封装成 ExitCodeGenerator 注册到 ExitCodeGenerators 中便于统一处理。

    @FunctionalInterface
    public interface ExitCodeGenerator {
    	int getExitCode();
    }
    
    @FunctionalInterface
    public interface ExitCodeExceptionMapper {
    	int getExitCode(Throwable exception);
    }
    

    三、SpringBootExceptionReporter

    SpringBootExceptionReporter 也是在 spring.factories 中配置的,默认实现为 FailureAnalyzers。FailureAnalyzers 持有多个 FailureAnalyzer 来分析异常生成 FailureAnalysis 报告,由 FailureAnalysisReporter 处理。这些类都位于 org.springframework.boot.diagnostics 包下。具体的配置如下:

    # Error Reporters
    org.springframework.boot.SpringBootExceptionReporter=
    org.springframework.boot.diagnostics.FailureAnalyzers
    
    # Failure Analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=
    org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
    
    # FailureAnalysisReporters
    org.springframework.boot.diagnostics.FailureAnalysisReporter=
    org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
    

    FailureAnalyzers 处理流程也非常简单。

    FailureAnalyzers 处理流程

    四、SpringBootExceptionHandler

    Thread#UncaughtExceptionHandler 处理线程异常关闭时未处理的异常:https://www.cnblogs.com/jadic/p/3532580.html

    SpringBootExceptionHandler 实现了 Thread#UncaughtExceptionHandler 接口,在线程关闭时退出程序。

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
    	try {
    		if (isPassedToParent(ex) && this.parent != null) {
    			this.parent.uncaughtException(thread, ex);
    		}
    	} finally {
    		this.loggedExceptions.clear();
    		if (this.exitCode != 0) {
    			System.exit(this.exitCode);
    		}
    	}
    }
    

    那 SpringBootExceptionHandler 是怎么注册到线程上的呢?实际上在初始化类的时候就注册到线程上了。

    // 初始化类的时候就实例了 SpringBootExceptionHandler
    private static LoggedExceptionHandlerThreadLocal handler = new LoggedExceptionHandlerThreadLocal();
    
    private static class LoggedExceptionHandlerThreadLocal
    		extends ThreadLocal<SpringBootExceptionHandler> {
    	@Override
    	protected SpringBootExceptionHandler initialValue() {
    		SpringBootExceptionHandler handler = new SpringBootExceptionHandler(
    				Thread.currentThread().getUncaughtExceptionHandler());
    		// 将 SpringBootExceptionHandler 注册到当前线程上
    		Thread.currentThread().setUncaughtExceptionHandler(handler);
    		return handler;
    	}
    }
    

    获取 SpringBootExceptionHandler 实例:

    static SpringBootExceptionHandler forCurrentThread() {
    	return handler.get();
    }
    

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    通过asp.net 生成xml文件
    listbox 多选处理
    girdview 找到其焦点的笨办法
    关于.net 中调用script的alert后 css失效的办法
    从数据库中读数据中寻找若隐若现的OOP
    Gitlab的安装部署和介绍
    守住你的网站:防御DDoS攻击指南
    分析SQL语句使用资源情况
    Linux下Sniffer程序的实现
    NDIS resources
  • 原文地址:https://www.cnblogs.com/binarylei/p/10646567.html
Copyright © 2020-2023  润新知