月曜日, 3月 27, 2017

Quartz で定期処理: Job へ値を引き渡す方法

プログラムを一定時間ごと走らせたい場合、スケジューラーを使います。

シェルスクリプトなどで走らせる方法もありますが、異質なものを組み合わせるのは限界があります。

Java で書けるスケジューラーで、Quartz なる優れた(ちょっと癖のある)ライブラリがあります。

Quartz の使い方はネットでもあがってますが、自前の Job を走らせたいときつまづきやすいのが Job へ値を引き渡したいとき。

ネットであまり記述がなかったので書きます。

このさいコンストラクタを使って値を引き渡す方法はx(だめ)です。

ここは .usingJobData(String, String) を用います。

import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class CronTest {
public static void main(String[] args) {
try {
JobDetail job = JobBuilder.newJob(CronTestJob.class).usingJobData("cmd", "echo")
.withIdentity("trigger", "group").build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "group").startNow()
.withSchedule(simpleSchedule().withIntervalInMilliseconds(1000).repeatForever()).build();
Scheduler scheduler = null;
scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(job, trigger);
scheduler.start();
Thread.sleep(10000);
scheduler.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
view raw CronTest.java hosted with ❤ by GitHub

受け取る側は JobExecutionContext から getJobDataMap() を使って受け取ります。

import java.io.IOException;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class CronTestJob implements Job{
String cmd="yahho";
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
try {
String cmd=(String)arg0.getJobDetail().getJobDataMap().get("cmd");
System.out.println(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}
}

土曜日, 3月 11, 2017

Java でログを出力:slf4j+logger

Jar 実行形式ファイルではコンソール入出力が使えません。コンソールが使えずともファイルを使う便利な方法があります。ロガーを使う方法です。

そうでなくともログは重要です。デバッグでなくともアプリのトレースが必要な場合は多いです。

Java でログを出力するための便利なライブラリは多種さまざまありますが、そのなかでも slf4j+logger なる方法が便利です。static で Logger を定義できて、出力形式を XML で指定できます。

必要な jar ファイルは以下の通りです[*]。

logback-classic-1.2.1.jar
logback-core-1.2.1.jar
slf4j-api-1.7.24.jar

必要最小限なコードを示します。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogTest {
public static void main(String[] args) {
Logger log = LoggerFactory.getLogger(LogTest.class);
log.info("LogTest running ...");
}
}
view raw LogTest.java hosted with ❤ by GitHub

出力用のログファイルの設定は logback.xml で。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>
<configuration>
<timestamp key="time" datePattern="yyyy-MM-dd_HHmmss" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>log/SCDFgen-${time}.log</file>
<encoder>
<charset>UTF-8</charset>
<pattern>%m%n</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="FILE" />
</root>
</configuration>
view raw logback.xml hosted with ❤ by GitHub

こいつは奥が深いです。ライブラリのソースの規模を見ると圧倒されます。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>
<configuration>
<timestamp key="time" datePattern="yyyy-MM-dd_HHmmss" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>log/WavSplit-${time}.log</file>
<encoder>
<charset>UTF-8</charset>
<pattern>%m%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-7([%level]) %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root>
<appender-ref ref="STDOUT" />
</root>
</configuration>

ログのレベルは error>warn>info>debug>trace などあります。デフォルトではファイル出力はすべて、標準出力はデバッグレベルまで。レベルごと分けておくと便利です。

Apache Commons CLI: Java でコマンドライン処理

JavaをWindows上で使うのはだからちょっとした障壁をクリアせねばなりません。この種の困難は「世界市場を独占するマイクロソフトへモノを言っているんだ」ぐらいのノリで立ち向かう必要があります。

ですからJavaでコンソールアプリってのはどうしても多少の困難を覚悟せねばなりません。コマンドライン処理など想定されていないわけです。

プロパティファイルで初期化パラメタを設定なる方法がありますが、昔ながらの伝統的スタイルを継承し、クラシックなノリでコマンドライン引数なるものを使うとプロパティなどというものを使わずまるでUnix/Linux/Gnuのコマンドのようなプログラムが書けます。

ここは外部ライブラリを使うのがオススメです。Apache Commons CLI を使う方法を示します。

public static void main(String args[]) {
Options opt = new Options();
try {
opt.addOption("?", OPTION_HELP, false, "print this message");
Option option = Option.builder("f").required(true).longOpt(OPTION_FILE).hasArgs().desc("file name").build();
opt.addOption(option);
Option dirOption = Option.builder("d").required(true).longOpt(OPTION_DIR).hasArgs().desc("target directory")
.build();
opt.addOption(dirOption);
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(opt, args);
if (cmd.hasOption(OPTION_HELP)) {
throw new Exception();
} else if (cmd.hasOption(OPTION_DIR) && cmd.hasOption(OPTION_FILE)) {
String dir = cmd.getOptionValue(OPTION_DIR);
String fileName = cmd.getOptionValue(OPTION_FILE);
new FindFile(fileName, dir);
}else{
throw new Exception();
}
} catch (Exception e) {
HelpFormatter help = new HelpFormatter();
help.printHelp("DirList", opt, true);
}
}

Java: jar 実行ファイルをつくる

Windows で動く Java の実行ファイルを作るのは手間がかかります。

マイクロソフト社の陰謀ですね。ちょっとした関税外障壁があるわけです。

まず、そのままjavacでコンパイルしてもclassファイルができるだけです。ライブラリを組み込んで動いても、とっても不便なディレクトリ構造は動かせない。

ここは jar ファイルの作り方を工夫すれば動きます。プロジェクトを右クリック、Export からダイアログで Runnable JAR file を選択します。



或いはライブラリを組み込んでマニフェストファイルを設定し Main-Class を設定します。これだってかなりな手間ですが、話はこれで終わらない。

<target name="jar" depends="build">
<jar jarfile="SCDF_Generator.jar" basedir="bin">
<manifest>
<attribute name="Main-Class" value="DirList" />
</manifest>
<zipgroupfileset dir="lib" includes="*.jar" />
</jar>
</target>
view raw build.xml hosted with ❤ by GitHub

出来上がった jar 実行ファイルがエラーも出さず動いたと思えば動かしているのはjavawでjavaです。つまりコンソールアプリは動かない。出力はまったくなしです。

どうしてもコマンド一つで動く jar 実行ファイルを作りたい場合。copy コマンドでバッチファイルをコピーする裏技があります。

@echo off
java -jar "%~f0" %*
exit /b %ERRORLEVEL%
view raw src.bat hosted with ❤ by GitHub
copy /b src.jar+DirList.ar DirList.bat
view raw copy hosted with ❤ by GitHub

Qt: 外部プログラムを起動する

  Qt/C++ のアプリは、外部へ直接アクセスできます。これはネットアプリでは不可能な Qt のメリットです。 外部プログラムを起動することもできます。QProcess::startDetached() を使うと独立したプロセスを立ち上げることができます。 この QProces...