当前位置: 首页 > 编程日记 > 正文

使用 spring boot 开发通用程序

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

相关文章:

java.utilDate和java.sql.Date

java.utilDate和java.sql.Date由广州疯狂软件教育java培训分享&#xff1a; java程序中的时间类型有java.util.Date和java.sql.Date两种。两者之间的关系和转化如下&#xff1a; java.sql.Date是java.util.Date的子类&#xff0c;所以可以直接将java.sql.Date类型的对象赋值给j…

Oracle 10G重建EM DB Control.

Oracle 10G R2重建EM DB Control.如果安装后没有修改过IP地址或机器名字,可以跳过下面1和2两步。1.drop configuration files and repository run :emca -deconfig dbcontrol db -repos drop 2. Logon SQLPLUS as user SYS or SYSTEM, and drop the sysman account and mangem…

IDEA IntelliJ 如何快速查看一个类里面包含哪些方法

主页面停留在那个类上 点击屏幕左侧的Structure

ACM 关于521

关于521 时间限制&#xff1a;1000 ms | 内存限制&#xff1a;65535 KB难度&#xff1a;2描述Acm队的流年对数学的研究不是很透彻&#xff0c;但是固执的他还是想一头扎进去。 浏览网页的流年忽然看到了网上有人用玫瑰花瓣拼成了521三个数字&#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…

(转载)新年——顺民的牢骚

我是一个顺民&#xff0c;顺的不能再顺的民&#xff01;相信很多人和我一样&#xff0c;即使心中有很多的不满&#xff0c;也只是偶尔发发牢骚&#xff0c;而且&#xff0c;大多时&#xff0c;还不敢在人多的时候发&#xff01;即使在昏暗的灯光下的某个角落写写BLOG&#xff0…

IDEA IntelliJ 如何设置网站的欢迎页面

网站的目录结构如下 在web>WEB-INF>web.xml 文件中设置login.html为欢迎页面&#xff08;运行跳出的第一个页面&#xff09;

nyoj19 全排列

http://acm.nyist.net/JudgeOnline/status.php?pid19 1 #include<stdio.h>2 #include<stdlib.h>3 int n,a[10];4 bool vis[10];//标示数字是否被用过 5 void f(int k,int m)//k用来给a中第k个元素赋值&#xff0c;m表示还需要寻找的数字个数 6 {7 for(int i1;…

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

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

有关cmd.Parameters.Clear()

// detach the SqlParameters from the command object, so they can be used again.cmd.Parameters.Clear(); 这句话在某些时候非常有用,比如说你在一个事务中调用了2个以上的存储过程,而恰巧这两个存储过程又有同样的参数,(例如 p_Name) 这时不加cmd.Parameters.Clear() 就会…

IDEA IntelliJ 如何设置web项目的热部署

这里的热部署指不需要重启服务器&#xff0c;刷新页面就可以反应页面&#xff08;仅限html和JSP&#xff09;的改动 step1 step2 效果 原来的html页面 在IntelliJ中进行修改 回到Chrome&#xff0c;点击 结果

SQL Server 2012 OFFSET/FETCH NEXT分页示例

原文&#xff1a;http://beyondrelational.com/modules/29/presentations/483/scripts/12983/sql-server-2012-server-side-paging-demo-using-offsetfetch-next.aspx?utm_sourcebrnewsletter&utm_mediumemail&utm_campaign2012Apr SQL Server 2005 引入了 ROW_NUMBER…

python数据库学习--Mysql

摘要&#xff1a; Python使用mysql需要安装驱动&#xff0c;有两种1&#xff09;mysqldb 2&#xff09;umysql &#xff0c;两者都是开源&#xff0c;具体区别不详&#xff0c;umysql没用过 一、mysqldb 安装MySQL驱动--http://www.codegood.com/archives/129 提供下载…

master page頁面如何用js來做輸入驗證

問&#xff1a;在使用了master page後&#xff0c;內容頁就沒有html標識了&#xff0c;那麼取得內容頁面控件的cilentID地方法也不一樣&#xff0c;我不知道用js怎麼取得這個cilentID,請各位大大幫忙告知如何寫這個js,並對其進行驗證.答&#xff1a;你可以先浏览一下页面&#…

scanf与空格

1.scanf的%c格式可以读入空格和换行 例&#xff1a; #include<cstdio> int main(){int a;char c,str[10];scanf("%d%c%s",&a,&c,str);printf("a%d,c%c,str%s",a,c,str);return 0;}2.字符数组使用%s读入的时候以空格跟换行为读入结束的标识 …

java 读取文件,内容方置Person 并写到另外地址

a.txt 文本内容如下&#xff1a; nameuserage34imageaa.PNGurlE:\\ package cn.com.test05;import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream;class Person{String name;int…

从网页模版切图到网页生成全攻略(转)

鉴于很多朋友问到如何切图这个问题&#xff0c;又鉴于这个问题可大可小&#xff0c;一两句话是绝对讲不清楚的&#xff0c;所以今天有空闲在家里就举一个简单的例子来说明这个问题吧!OK&#xff0c;让我们开始&#xff1a; step1:在Photoshop中打开设计稿&#xff0c;如下图 选…

紫书 习题 10-17 UVa 11105 (筛法)

类似于素数筛的思想去做&#xff0c;不然暴力会超时而且还要判重 #include<cstdio> #include<cstring> #include<vector> #define REP(i, a, b) for(int i (a); i < (b); i) using namespace std;const int MAXN 1123456; int vis[MAXN]; vector<int…

(C++)按照公式求圆周率pi的近似值的两种方法

方法一 #include<cstdio> #include<cmath> //求圆周率pi的近似值int main(){double PI0,mu1;int i1;while(mu<1000000){if(i%2){PI 1/mu;}else{PI - 1/mu;}mu 2;i ;}PI * 4; printf("PI%10.8f",PI);return 0;} 方法2 #include<cstdio> #inc…

nginx rewrite 指令last break区别最详细的解释

总结&#xff1a; rewrite 可以在 server location 块&#xff0c; 正则比配的时候才重写&#xff0c;所以多条 rewrite 顺序靠前且匹配的优先执行。break跳出rewrite阶段&#xff0c;不会在匹配&#xff0c;进入输出阶段。 last 类似重新发起请求&#xff0c;所以会重新进行匹…

如何在Mac上加入adb服务

1,打开终端&#xff0c;敲入命令&#xff1a;sudo vi .bash_profile &#xff08;如果有密码就为本机登录密码&#xff09;&#xff08;如果没有这个文件就会创建一个新的&#xff09; 默认终端目录如图&#xff0c;生成的文件为隐藏文件&#xff1a; 2,在文件中写入一下内容&…

(C++)求Fibonacci数列的第n个数的两种方法

方法一 #include<cstdio> #include<cmath>int main(){int n;scanf("%d",&n);if(n1||n2){printf("1");}else{int Fibonacci[50];Fibonacci[1]Fibonacci[2]1;for(int i 3;i<n;i){Fibonacci[i]Fibonacci[i-1]Fibonacci[i-2];}printf(&qu…

GDI+ Bitmap与WPF BitmapImage的相互转换

原文:GDI Bitmap与WPF BitmapImage的相互转换using System.Windows.Interop; //... // Convert BitmapImage to Bitmap private Bitmap BitmapImage2Bitmap(BitmapImage bitmapImage) {return new Bitmap(bitmapImage.StreamSource);}// Convert Bitmap to BitmapImageprivate…

linux vim 基本操作

&#xff08;一定要在英文输入法的状态下才有效&#xff09;vi:实际上linux 上的 vi 不是真正的 vi,而是 vim;纯的 vi只在某些 unix 系统上还存在纯 的vi里面不支持退格键盘了&#xff0c;当按退格键盘以后&#xff0c;不是删除一个字符&#xff0c;而是切换到命令模式&#…

Oracle重建所有表和索引

定义两个存储过程&#xff0c;先执行表移动&#xff0c;再执行索引重建。&#xff08;如果记录很多&#xff0c;执行时间可能会很长&#xff0c;几个小时也有可能&#xff0c;建议在系统空闲时运行&#xff09;&#xff1a;create or replace procedure p_remove_all_table (ta…

线性回归预测PM2.5值

文章目录一、问题描述二、设计简要描述三、程序清单四、结果分析五、调试报告六、实验总结一、问题描述 希望用线性回归解决问题&#xff1a; 给定某个地区连续9小时包括PM2.5在内的18项污染物每小时的数据&#xff0c;预测第10个小时的PM2.5的值。 二、设计简要描述 机器学…

Sharding-eth

原文&#xff1a;https://github.com/ethereum/wiki/wiki/Sharding-FAQ 作者&#xff1a;vbuterin 简介 目前&#xff0c;在所有的区块链协议中每个节点存储所有的状态&#xff08;账户余额&#xff0c;合约代码和存储等等&#xff09;并且处理所有的交易。这提供了大量的安全性…

用CSS3制作很特别的波浪形菜单

用CSS3制作很特别的波浪形菜单 原文:用CSS3制作很特别的波浪形菜单网页菜单我们见过很多&#xff0c;各种炫酷的、实用的菜单比比皆是。昨天我看到一款很特别的CSS3菜单&#xff0c;它的外形是波浪形的&#xff0c;弯弯曲曲&#xff0c;结合背景&#xff0c;看上去还挺不错的&a…

二分类任务:确定一个人是否年收入超过5万美元

文章目录一、 问题描述二、 设计简要描述三、 程序清单四、 结果分析五、 调试报告六、 实验总结一、 问题描述 学会使用学习到的概率生成模型相关的知识&#xff0c;找出各类别最佳的高斯分布&#xff0c;从而达到通过输入测试&#xff0c;完成二分类任务&#xff0c;成功预测…

Android Splash界面支持用户点击 直接进入主界面

转载请注明出处&#xff1a;http://blog.csdn.net/lmj623565791/article/details/23613403 现在大部分APP都有Splash界面&#xff0c;下面列一下Splash页面的几个作用&#xff1a; 1、展示logo,提高公司形象 2、初始化数据 (拷贝数据到SD) 3、提高用户体验 4、连接服务器是否有…