使用 spring boot 开发通用程序

news/2024/7/6 3:18:48

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

  • tag: spring 学习笔记
  • date: 2018-03

spring 是什么?spring 核心是应用组件容器,管理组件生命周期,依赖关系,并提倡面向接口编程实现模块间松耦合。
spring boot 是什么?spring boot 是按特定(约定)方式使用 spring 及相关程序库以简化应用开发的一套框架和工具。
以下统称 spring。
本文使用 spring boot 2.0.0.RELEASE 测试。

ApplicationRunner

spring 广泛应用于 web 应用开发,使用 spring 开发命令行工具、后台服务等通用程序也非常方便。
开发 web 应用时,web 服务器(如 tomcat)启动后即开始监听请求。
开发命令行工具时,只需要实现一个 ApplicationRunner,spring 容器启动后即自动执行之。
如开发一个查看文件大小的示例程序 atest.filesize.App,代码如下:

public class App implements ApplicationRunner {public static void main(String[] args) {SpringApplication app = new SpringApplication(App.class);app.setBannerMode(Banner.Mode.OFF);app.run(args);}@Overridepublic void run(ApplicationArguments args) throws Exception {List<String> fileList = args.getNonOptionArgs();Validate.isTrue(!fileList.isEmpty(), "missing file");Validate.isTrue(fileList.size() == 1, "require only one file, got: %s", fileList);String path = fileList.get(0);File file = new File(path);if (!file.exists()) {throw new FileNotFoundException(path);}long size = file.length();System.out.println(size);}}
  • ApplicationArguments 是 spring boot 解析后的命令行参数。
    如果需要原始命令行参数,可以调用 args.getSourceArgs(),或使用 CommandLineRunner
  • spring 容器生命周期即应用生命周期,spring boot 默认注册了 spring 容器 shutdown hook,jvm 退出时会自动关闭 spring 容器。
    当然也可以手动关闭 spring 容器,这时会自动移除注册的 shutdown hook。

程序退出码

程序退出时通常返回非 0 退出码表示错误(或非正常结束),方便 shell 脚本等自动化检查控制。
命令行下运行应用并查看退出码:

mvn compile dependency:build-classpath -Dmdep.outputFile=target/cp.txt
java -cp "target/classes/:$(cat target/cp.txt)" atest.filesize.App ; echo "exit code: ${?}"

可看到主线程抛出异常时,java 进程默认返回非 0 退出码(默认为 1)。
ApplicationRunner 在主线程中执行,异常堆栈如下:

java.lang.IllegalStateException: Failed to execute ApplicationRunnerat org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:784)at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:771)at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)at atest.filesize.App.main(App.java:18)
Caused by: java.lang.IllegalArgumentException: missing fileat org.apache.commons.lang3.Validate.isTrue(Validate.java:155)at atest.filesize.App.run(App.java:24)at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:781)... 3 common frames omitted

spring boot 主线程中处理异常时,SpringBootExceptionHandler 默认将自己设置为线程 UncaughtExceptionHandler,
其检查发现已经打印了异常日志,因此不再打印异常到 stderr。

程序异常映射为退出码

主线程发生异常时还可以自定义设置退出码:

  • 配置 ExitCodeExceptionMapper 可将主线程产生的异常映射为退出码。
  • 此时还会调用 "getExitCodeFromExitCodeGeneratorException()" 检查异常本身是否为 ExitCodeGenerator。
    但 SpringApplication.exit() 没有这个逻辑,为保持一致性,不建议使用此类异常(?)。
  • 定义好异常和退出码规范,可方便实现自动化检查控制。

将异常映射为退出码,示例代码如下:

public class AppExitCodeExceptionMapper implements ExitCodeExceptionMapper {@Overridepublic int getExitCode(Throwable exception) {return 2;}}
  • 上述简单示例将所有 Throwable 映射为退出码 2。

分析 spring 相关代码。

查看主线程 handleRunFailure 调用栈:

Thread [main](Suspended)SpringApplication.getExitCodeFromMappedException(ConfigurableApplicationContext, Throwable) line: 881    SpringApplication.getExitCodeFromException(ConfigurableApplicationContext, Throwable) line: 866    SpringApplication.handleExitCode(ConfigurableApplicationContext, Throwable) line: 852    SpringApplication.handleRunFailure(ConfigurableApplicationContext, SpringApplicationRunListeners, Collection<SpringBootExceptionReporter>, Throwable) line: 803    SpringApplication.run(String...) line: 338    App.main(String[]) line: 29    

SpringApplication 方法:

    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) {handler.registerExitCode(exitCode);}}}private int getExitCodeFromException(ConfigurableApplicationContext context,Throwable exception) {int exitCode = getExitCodeFromMappedException(context, exception);if (exitCode == 0) {exitCode = getExitCodeFromExitCodeGeneratorException(exception);}return exitCode;}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();generators.addAll(exception, beans);return generators.getExitCode();}

ExitCodeGenerators 方法:

    public void add(Throwable exception, ExitCodeExceptionMapper mapper) {Assert.notNull(exception, "Exception must not be null");Assert.notNull(mapper, "Mapper must not be null");add(new MappedExitCodeGenerator(exception, mapper));}

spring boot 获取退出码并注册到 SpringBootExceptionHandler,
其将自己设置为线程 UncaughtExceptionHandler,退出码非 0 时调用 System.exit() 退出进程。

代码 SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal

        @Overrideprotected SpringBootExceptionHandler initialValue() {SpringBootExceptionHandler handler = new SpringBootExceptionHandler(Thread.currentThread().getUncaughtExceptionHandler());Thread.currentThread().setUncaughtExceptionHandler(handler);return handler;}

代码 SpringBootExceptionHandler

    @Overridepublic 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);}}}

这里直接调用 System.exit() 过于粗暴,因此 只有主线程 handleRunFailure 执行了这个逻辑。

多线程应用中工作线程发生异常,可否设置进程退出码呢?

多线程应用结构

先来看看多线程应用结构:

  • 多线程应用默认最后一个非 deamon 线程结束后退出进程。
  • 可以显式控制应用生命周期,显式执行退出,这样就不用关心是否 daemon 线程,简化开发。
  • 退出应用时显式关闭 Spring 容器,线程池也由 spring 容器管理,此时即可退出所有线程。非 daemon 线程可以更优雅的结束,因为 jvm 会等待其结束。
  • 应用退出前需要保持至少一个非 daemon 线程,主线程即可作为这个线程,实现应用主控逻辑,主函数结束即退出应用。
  • 桌面应用、后台服务(如 web 服务器)等需要显式等待应用退出。显式等待应放在主函数主控逻辑之后。即所有 ApplicationRunner 之后,避免阻塞其他 ApplicationRunner。
  • 为简单一致性,将显式退出(关闭容器)操作放在主函数等待结束后。容器在主线程中创建,在主线程中销毁,逻辑更加清晰和一致。
  • Ctrl-C 或 kill 等显式退出进程时,shutdown hook 会关闭容器,但不会等待非 deamon 线程(如主线程)。(会唤醒 sleep ?但不会影响 CountDownLatch.await() ?)

示例程序移动部分逻辑到工作线程,代码如下:

转载于:https://my.oschina.net/u/3611008/blog/1860124


http://lihuaxi.xjx100.cn/news/236583.html

相关文章

蒙特卡洛粒子滤波定位算法_粒子滤波——来自哈佛的详细的粒子滤波器教程【1】...

本文原版链接&#xff1a;https://www.seas.harvard.edu/courses/cs281/papers/doucet-johansen.pdf本文是哈佛大学相关研究人员于2008年发表的一篇关于粒子滤波的详细教程&#xff0c;至今已被引用1687次。目录&#xff1a;介绍Introduction1.1 序言Preliminary remarks1.2 教…

微软开源数据处理引擎 Trill,每天可分析万亿次事件

微软近日开源了数据处理引擎 Trill&#xff0c;它每天能够分析万亿次事件。项目地址&#xff1a;https://github.com/Microsoft/trill当下每毫秒处理大量数据正成为一种常见的业务需求&#xff0c;此次微软开源的 Trill&#xff0c;据说每秒能够处理高达数十亿事件&#xff0c;…

搭建本地https

生成证书 1. 使用openssl生成密钥privkey.pem&#xff1a; openssl genrsa -out privkey.pem 1024/2038 2. 使用密钥生成证书server.pem&#xff1a; openssl req -new -x509 -key privkey.pem -out server.pem -days 365 证书信息可以随便填或者留空&#xff0c;只有Common Na…

python怎么切片提取_彻底搞懂Python切片操作

在利用Python解决各种实际问题的过程中&#xff0c;经常会遇到从某个对象中抽取部分值的情况&#xff0c;切片操作正是专门用于完成这一操作的有力武器。理论上而言&#xff0c;只要条件表达式得当&#xff0c;可以通过单次或多次切片操作实现任意切取目标值。切片操作的基本语…

从0到1构建网易云信IM私有化

本文来源于MOT技术管理课堂杭州站演讲实录&#xff0c;全文 2410 字&#xff0c;阅读约需 5分钟。网易云信资深研发工程师张翱从私有化面临的问题及需求说起&#xff0c;分享了网易云信IM私有化的解决方案和具体实践。想要阅读更多技术干货、行业洞察&#xff0c;欢迎关注网易云…

vue中动态样式不起作用? scoped了解一下

vue中style标签使用属性scoped的注意事项 style上添加属性scoped可以实现样式私有化&#xff0c;但是在使用动态样式时&#xff0c;样式会不起作用。可以先去掉scoped转载于:https://www.cnblogs.com/zuojiayi/p/9364347.html

mac远程连接windows工具_Windows远程MAC系统

第一步、在 Mac 上设置好屏幕共享1. 1先请在苹果 Mac 电脑上的“系统偏好设置”窗口中打开“共享”功能&#xff0c;如图所示接着在共享窗口中的左侧点击启用“屏幕共享”选项&#xff0c;如图所示当屏幕共享功能打开以后&#xff0c;请点击“电脑设置”按钮&#xff0c;如图所…

php 扩展包链接

https://pecl.php.net/package-stats.php?cid7转载于:https://www.cnblogs.com/gaoyuechen/p/10148754.html