- 投稿日:2022-01-31T15:16:33+09:00
[Java] Spring Boot を導入する
記事作成の目的 これからjava学習をするにあたっての環境構築 初心者のため備忘録としてアウトプット 同じ所で詰まった人がいたときに参考にして頂くため Spring Bootについて Spring Bootとは、Javaのフレームワークの一つ。 Spring フレームワークというプロジェクトで出来ることは数多ありますが、環境構築等含め汎用的で使い勝手の良いものとして凝縮したものが「Spring Boot」です。 今回は導入までをシンプルに記述するため、メリットに関しては以下記事等を参考にいただければと思います。 Spring Bootの導入 前提 eclipceのインストール(Pleiades All in One からダウンロード) まだの方は上記からインストールしてください。 Windowsの方は環境変数設定が別途必要なので、下記リンクを参考にしてください。 Java 環境構築 環境変数設定 プロジェクトの作成 ファイル → 新規 → その他 Spring Boot → Spring スターター・プロジェクト プロジェクト名を設定 → 次へ 依存関係の設定 Spring Boot DevTools Thymeleaf Spring Web を選択して完了。 ※ここで依存関係を設定しないと後々エラーが出て実行できなくなります! 5.ViewとControllerを配置 下記画像のcom.example.demo下にコントローラー、templates下にビューを配置します。 SampleController.java package com.example.demo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SampleController { @RequestMapping("/index") public String index() { return "index"; } } template下で右クリック → 新規 → ファイル → ファイル名:index.html index.html Hello World!! 実行 実行 → Spring Boot アプリケーション → 新規構成 → プロジェクトでプロジェクト名指定 → メイン型でcomを入力し検索 → 実行 http://localhost:8080/index に飛ぶ。 以上です! エラーメッセージが出たときは設定間違ってないかなど確認してみてください! 参考文献 SpringBootやってみる~準備編~ 【5分でわかる】Spring Boot超入門! 特徴やできること、Spring Frameworkとの違いなどをわかりやすく解説 SpringBoot で Hello World
- 投稿日:2022-01-31T08:48:47+09:00
Reactorのdeferの使い方
reactor-coreのMonoやFluxを使う上で、よくやってしまうリソース解放に関する誤りを、Mono#deferの使い方とともに説明します。 reactor-coreのその他の使い方については、Reactor 3 Reference Guidを参考にして下さい。 前準備 reactor-coreに従って、reactor-coreを使ったJavaプログラミングが出来る環境を準備します。 問題のあるコード MonoやFluxの処理は、subscribeやblock等の終端処理が呼ばれた際に実行されます。このため、終端処理が呼ばれるたびに、doOnTerminate等のハンドラーが実行されます。以下のように、Monoの外側で作成したリソースをdoOnTerminateでcloseすると、2回目の終端処理の呼び出しはすでにcloseされているリソースに対して実行されてしまいます。 private static class _SampleCloseable implements Closeable { private boolean _closed; @Override public synchronized void close() { // 2回以上、closeが呼ばれるとエラーになる。 if (_closed) { throw new RuntimeException("Already closed."); } _closed = true; } } @Test public void testInvalidClose() throws Exception { var closeable = new _SampleCloseable(); var mono = Mono.just("good").doOnTerminate(() -> { closeable.close(); }); // 内部でcloseが呼ばれる var value = mono.block(); Assertions.assertEquals("good", value); // 内部でcloseが呼ばれる(2回目なのでエラーになる) Assertions.assertThrows(RuntimeException.class, () -> mono.block()); } deferを使用した修正 deferを使用することで、Monoの内部でリソースの初期化を行う事が出来ます。 @Test public void testDefer() throws Exception { var mono = Mono.defer(() -> { // リソースの初期化をdefer内で行います。 var closeable = new _SampleCloseable(); return Mono.just("good").doOnTerminate(() -> { closeable.close(); }); }); // block実行時に、リソースが初期化されて、閉じられます var value = mono.block(); Assertions.assertEquals("good", value); // block実行時に、リソースが初期化されて、閉じられます value = mono.block(); Assertions.assertEquals("good", value); } 結論 deferを使うことで、リソースの初期化と解放をMonoやFluxに閉じて行う事が出来ます。 MonoやFluxの生成と同じスコープでリソースの初期化をしているケースでは、こちらの問題が発生する可能性を常に考慮する必要があります。
- 投稿日:2022-01-31T07:31:32+09:00
Log4j2をとりあえず javacで動かす話
はじめに この記事は、Javaの動作を実感することを目的として作成しています。そのため、 イマドキがどうとか、Mavenがどうとか、IDEがどうとか。そういった議論は一切省きます。これを知って初めて効率のよいやり方に移らないと、根本的な事が抜け落ちてしまうからです。 資料の目的 誰かがかいたライブラリを自分のプログラムに組込むまでの手順を整理し、クラスパスやパッケージ importなどの概念を獲得することが目的です。そのために、「プログラム実行時に動作を目で見て確認できる」Log出力機能を選定します。選定ライブラリは今を時めくLog4j2です。僕のお友達が、仕事でよくLog4j2の名前を聞くということで。せっかくだからこいつを使おうと決めました。だからLog4j2の使い方とか、設定方法とかも全くの範囲外です 前提条件 jdkが既に導入されていること(javac/javaコマンドが利用可能であること) インターネットに接続できること。 Windows でやってね。(コマンドプロンプト) Linuxは適宜読み替えてください。 >javac --version javac 17 JDKが入っていれば、バージョン情報が出力される。 準備作業 まずは、作業の準備から。いつものようにコマンドプロンプトで > mkdir c:\dev\workspace\java\libconfirm > cd c:\dev\workspace\java\libconfirm > mkdir src\main\java\ > mkdir src\main\java\cococlub > mkdir lib > curl -o log4j.zip https://dlcdn.apache.org/logging/log4j/2.3.2/apache-log4j-2.3.2-bin.zip > jar xvf log4j.zip > copy apache-log4j-2.3.2-bin lib\ > del /s apache-log4j-2.3.2-bin > rmdir apache-log4j-2.3.2-bin > del log4j.zip > set classpath= コードを作成しよう まずは。自分の作りたいコードを作るところからです。 >notepad src\main\java\cococlub\MyApp.java (ダイアログで新しく作るか聴かれるので、作成ボタンを押下してください。) 入力するコードは以下の通りです。 public class MyApp{ } さっそくコンパイルしてみましょう > javac -d bin src\main\java\cococlub\MyApp.java コンパイルが終了するので > dir bin c:\dev\workspace\java\libconfirm\bin のディレクトリ 2022/01/30 17:48 <DIR> . 2022/01/30 17:48 <DIR> .. 2022/01/30 17:48 184 MyApp.class ここで -d の意味ですが、コンパイルした結果を -d で指定したディレクトリに保存してくださいという意味です。え?何がいいのかわからない。では指定していない場合の結果と見比べてみましょう まず指定した場合 libconfirm>dir /S /A-D /B src bin c:\dev\workspace\java\libconfirm\src\main\java\cococlub\MyApp.java c:\dev\workspace\java\libconfirm\bin\MyApp.class 指定していない場合 libconfirm>dir /S /A-D /B src bin c:\dev\workspace\java\libconfirm\src\main\java\cococlub\MyApp.class c:\dev\workspace\java\libconfirm\src\main\java\cococlub\MyApp.java ★ソースコードと同じ場所に.classファイルができちゃいます。 これは、「前の結果を忘れて綺麗な状態でコンパイルしたい」って場合にとても都合が悪いのです。 指定していれば、binディレクトリそのものを消してしまえばいいのですが、指定しない場合。*.classファイルを探し出して消さなければならない。消し漏らすと、問題なケースなどもあるので、都合が悪いのです。以後 dオプションは必ず指定して実行するようにしましょう。 さて、こんな何にもないコードでも、いろいろ分かることがあります。こうやって、少しずついろいろ変えながら理解することは初学者にとってとても大切なステップです。ここをおろそかにするとあとから全くわからないことがでてきます。遠きに行くには必ず邇きよりす 武田先生。貴方すごくいい言葉を教えてくれていますよ!!! パッケージを作ってみよう 実はJava は、パッケージに属さない(デフォルトパッケージ)クラスを推奨していません。まずは最初にパッケージをつけてみます。パッケージでは .class を格納するフォルダはパッケージ名と一致さえる必要がある という鉄のおきてがあります。つまりパッケージを付けると、生成される.classは<package名のフォルダ> に格納されます。そのため、ソースコードも、パッケージ名と格納先をそろえておくのがいいでしょう。(エラーの内容から、どのソースが問題だかすぐに分かるようにしておきたいのです。) cococlub.net ⇒ ...../cococlub/net にそのソースが置いておく。 という事です。今ソースは「cococlub」にあるので、cococlubというパッケージにします。出来上がるソースは package cococlub; public class MyApp{ } です。では早速コンパイルしてみましょう >javac -d bin src\main\java\cococlub\MyApp.java >dir /S /B /A-D bin c:\dev\workspace\java\libconfirm\bin\cococlub\MyApp.class cococlub配下にMyApp.classが生成されています。 Log4j2を使ってみよう プログラムを書こう!! まずは、本格的に使う前に、Log4jにおいて、ログを記録する役割をになうorg.apache.logging.log4j.Logger型の変数を宣言しましょう。 package cococlub; public class MyApp{ public static void main(String[] args){ org.apache.logging.log4j.Logger logger; //★↑ちょっと長くて面倒くさいので、別の方法を後述します。 } } さてと、コンパイルコンパイル set classpath= javac -encoding UTF-8 -d bin src\main\java\cococlub\MyApp.java ★以下実行結果 src\main\java\cococlub\MyApp.java:5: エラー: パッケージorg.apache.logging.log4jは存在しません org.apache.logging.log4j.Logger logger; ^ エラー1個 -encoding UTF-8サンプルプログラムに日本語のコメントをいれたので、ファイルの文字コードがUTF-8であることをjavacコマンドにおしえている。これを教えないと、javacはシステムのデフォルト文字コードwindows-31j であることを仮定して動作するため、実際の文字コードと異なりエラーとなる。 えらー・・・・ですと・・・だってさっきlibに解凍したのに・・・・・と思いますよね?でも。そのlibフォルダにlog4jが置いてあるなんて、コンパイラはいつ知るのでしょうか?知りえないんですよね。。 知らぬなら、教えてあげようjarの位置 javac -encoding UTF-8 -d bin --class-path=lib\log4j-api-2.3.2.jar src\main\java\cococlub\MyApp.java 今度はうまくいきました。class-pathはjavac/javaに「ソースにないClass=外部ライブラリの位置はここだよ~」と教えげ上げるための仕掛けです。今回は「org.apache.logging.log4j.Logger」が入っている「lib\log4j-api-2.3.2.jar」を追加しています。さてもう少しコードを追加しましょう。 package cococlub; public class MyApp{ public static void main(String[] args){ org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(MyApp.class); logger.error("An Error is Occured!"); } } さぁコンパイルコンパイル♪さっき、class-pathも追加したし、今回は・・・・ > javac -encoding UTF-8 -d bin --class-path=lib\log4j-api-2.3.2.jar src\main\java\cococlub\MyApp.java お!!!とおりましたよ!!! やった!!やったよ!! 動かしてみよう では動かしてみましょう。 >java cococlub.MyApp エラー: メイン・クラスcococlub.MyAppが見つからなかったかロードできませんでした ええええそりゃないよおとっつぁん。さっきコンパイルしたじゃん!!だってほら > dir /A-D /B /S bin c:\dev\workspace\java\libconfirm\bin\cococlub\MyApp.class いるじゃん・・・・はっ!!!このクラスがbin に置いてあるなんて、愛しのJavaチャンにだれが伝えたのか。。。つたえてねー------。そりゃ呼べないよ。。java(javacであれjavaであれ)に「クラスの在処」を教えるためには、classpathを使えばよかったのです。では積んでみましょう set classpath=bin java -cp %classpath% cococlub.MyApp ★以下実行結果 Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/log4j/LogManager at cococlub.MyApp.main(MyApp.java:5) Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.LogManager at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) -cp がjava(javacではなく) にクラスパスを与える方法です。覚えておこうね。どうやらMyAppは呼べたようですが。。なんと、log4jが・・・log4j忘れてたごめんよ。log4j。。。だって教えてないんだもん。「og4j-api-2.3.2.jar」を使えばいいんだよね。もう慣れてるから簡単さ。 set classpath= set classpath=bin set classpath=%classpath%;lib\log4j-api-2.3.2.jar java -cp %classpath% cococlub.MyApp ★以下実行結果 ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console... ERROR MyApp An Error is Occured!←ただメッセージが出ただけで、ログのフォーマットとしてだめだめ。 あ、怒られた。log4j-coreをclasspathにいれろだって・・・なんでだ・・・コンパイル時にはいらなかったじゃないか!! これ、javaあるあるなので気をつけましょう。 コンパイル時に必要なライブラリ(jar)=プログラムが直接呼ぶライブラリ(jar) 実行時に必要なライブラリ(jar)=プログラムが直接呼ぶライブラリ(jar)が呼ぶライブラリ(jar)を全部 なので、呼んだ先で必要なライブラリを要求されているのです。場合によっては先の先の先でとかすごい数のjarが必要いなったりもします。今回はlog4jの自己申告通りlog4-coreを追加してあげましょう set classpath= set classpath=bin set classpath=%classpath%;lib\log4j-api-2.3.2.jar;lib\log4j-core-2.3.2.jar java -cp %classpath% cococlub.MyApp ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. ★log4j2の設定ファイルがないから、デフォルトの動作として Errorレベルだけ標準出力に出力するよ。設定ファイル面倒なので今回は割愛。見逃し。 22:33:18.852 [main] ERROR cococlub.MyApp - An Error is Occured!←お、どのコードからいつ出されたのか一目瞭然。 見事エラーレベルのログが出力されました。参考まで、infoも追加してみましょう。 package cococlub; public class MyApp{ public static void main(String[] args){ org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(MyApp.class); logger.error("An Error is Occured!"); logger.info("It is an information"); } } 早速、コンパイル。 javac -encoding UTF-8 -d bin --class-path=lib\log4j-api-2.3.2.jar src\main\java\cococlub\MyApp.java さて実行 set classpath= set classpath=bin set classpath=%classpath%;lib\log4j-api-2.3.2.jar;lib\log4j-core-2.3.2.jar java -cp %classpath% cococlub.MyApp ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. 22:41:14.331 [main] ERROR cococlub.MyApp - An Error is Occured! きちんとErrorレベルのログだけが出されていますね。すばらしい。 import を使おうよ さて、今まで作ってたこのコード。ちょっときついですよね。 package cococlub; public class MyApp{ public static void main(String[] args){ org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(MyApp.class); logger.error("An Error is Occured!"); logger.info("It is an information"); } } なにがって、org.apache.logging.log4j.Loggerとorg.apache.logging.log4j.LoggerをFQDN で指定するのはつらいです。1か所ならまだしも、複数個所でてきたら目もあてられません。こういう「なんども書くだろう長い表記」というのは普通に省略する方法があります。javaではimport文です。importしたクラスは、クラス名だけで呼び出しできるというありがたー----い仕様がjavaにはあるのです。使わない手はありません。 package cococlub; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; public class MyApp{ public static void main(String[] args){ Logger logger = LogManager.getLogger(MyApp.class); logger.error("An Error is Occured!"); logger.info("It is an information"); } } あ、かなりすっきり。これのコンパイルと実行は今までと代わり映えしないので割愛します。 まとめ jarをもってきて利用する方法が分かったよ packageの使い方もわかったよ 実行時に依存するすべてのライブラリを指定するのは結構面倒だよ。 ライブラリ(Jar) を各サイトから集まるのも結構手間だと思うよ
- 投稿日:2022-01-31T02:02:25+09:00
ファイルの入力と出力のテストコード
最近、ファイルを取り込み加工して出力する単純なプログラムを作成しましたが。作業期間が限られていたこともあり、必要最低限のテストコードしか整備できませんでした。。 実装 App.java public class App { public static void main(String[] args) throws IOException { String input_str = file_read(); input_str += "\n追記!"; file_output(input_str); } public static String file_read() throws IOException { Path file = Paths.get("src/main/resources/input/test.txt"); return Files.readString(file, Charset.forName("UTF-8")); } public static void file_output(String value) throws IOException { File output_file = new File("src/main/resources/output/test.txt"); FileWriter filewriter = new FileWriter(output_file); filewriter.write(value); filewriter.close(); } } テストコード AppTest.java public class AppTest { @Test public void test_file_input() throws IOException { assertEquals( kusatest.App.file_read(), Files.readString(Paths.get("src/main/resources/input/test.txt")) ); } @Test public void test_file_output() throws IOException { for (File delete_file : new File("src/main/resources/output/").listFiles()) delete_file.delete(); kusatest.App.file_output("テストです"); assertEquals( "テストです", Files.readString(Paths.get("src/main/resources/output/test.txt")) ); } @Test public void test_app() throws IOException { for (File delete_file : new File("src/main/resources/output/").listFiles()) delete_file.delete(); kusatest.App.main(null); assertArrayEquals( Files.readAllBytes(Paths.get("src/main/resources/output/test.txt")), Files.readAllBytes(Paths.get("src/main/resources/output_check/test.txt")) ); } } 入力/出力/入力から出力の3パターンのテストコードです。社内外とファイル授受する場合は、対向システムとテストファイルを渡して事前に取り込めるか?確認することがあると思いますが、そこで取り込み確認できた場合は、それのファイルをテストの正常ケースとしてテストコードに含めています。少しずつ空き時間に整備していきたい。。