一、背景
写个程序我需要一种场景,在程序启动时在控制台进行主动询问。如果输入为'y'则执行逻辑A,如果输入为非'y'或当等待输入时间大于3秒时执行逻辑B。
二、问题分析
采用System.in.read来截获键盘输入比较常见,但想做到自动输入超时,好像并没有原生提供该功能。我想到应该用Thread来解决,在一个新的Thread中输入,主Thread进行等待。
但后台来现如果Thread被中断,Thread中的System.in.read并不会被终止。所以改为Channles的方式读取控制台输入,如果Thread被中断read会抛出异常来彻底结束该Thread。
三、程序代码
代码中有注释,这里就不细说了。
package org.noahx.inreadtimeout;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ClosedByInterruptException;
/**
* Created with IntelliJ IDEA.
* User: noah
* Date: 8/26/13
* Time: 10:37 PM
* To change this template use File | Settings | File Templates.
*/
public class TestTimeout {
public static void main(String[] args) {
final TestTimeout testTimeout = new TestTimeout();
boolean doClean = testTimeout.readInput(); //等待输入3秒,超时按false处理
if (doClean) {
//cleanDb();
System.out.println("The database was cleaned!");
} else {
System.out.println("The clean operation was ignored.");
}
}
public boolean readInput() {
System.out.println("Do you want to clean and initialize the database?(y/n)");
final boolean[] doClean = {false}; //用数组接收返回值
Thread inThread = new Thread() {
@Override
public void run() {
// 不采用System.in是因为,该线程被终止时System.in无法终止
InputStream stdInCh = Channels.newInputStream((
new FileInputStream(FileDescriptor.in)).getChannel()); // 采用Channels的InputStream,同样的输入效果,但会触发ClosedByInterruptException异常
try {
switch ((char) stdInCh.read()) { //读取按键
case 'y':
case 'Y': {
doClean[0] = true; //设置返回为true
break;
}
}
synchronized (TestTimeout.this) {
TestTimeout.this.notify(); //通知主线程停止等待
}
} catch (ClosedByInterruptException e) { //调用inThread.interrupt()方法,会触发该异常
//忽略中断异常
} catch (IOException e) {
e.printStackTrace();
}
}
};
inThread.start();
try {
synchronized (this) {
this.wait(3000); //等待3秒,可以修改为更长的时间来观查效果,如6000
}
if (inThread.isAlive()) { //如果inThread还活着说明,是3000超时导致
inThread.interrupt(); //强行终止inThread
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return doClean[0];
}
}
四、执行效果
1、3秒内输入y
程序选择true逻辑
Do you want to clean and initialize the database?(y/n)
y
The database was cleaned!
2、3秒不输入任何内容或输入y以外字符
程序选择false逻辑
Do you want to clean and initialize the database?(y/n)
The clean operation was ignored
五、总结
这种方式不光可以应用在纯Java Application上,也可以写在Spring的Bean(implements InitializingBean)与Web容器中。实际项目中我就是这样应用的,在afterPropertiesSet中加入询问是否需要初始化数据库代码,来完成快速初始化数据(不修改配置文件)。
P.S.
我的使用场景
采用询问方式初始化数据库的好处是,方便开发调试(IDE都有控制台)。提示时输入一个y字符,库表就会被全部清空,hibernate调用create建表(正常validate),再自动调用导入程序导入初始数据。提示时如果不理睬(不会初始化数据库)3秒后程序继续正常执行。