- 投稿日:2019-11-15T16:27:04+09:00
IntelliJ IDEAでjava 13のswitchをためす
※ 2019/11/15 時点です。
IntelliJ IDEAまったくと言っていいほど触ったことが無いので、自分用にメモ。
環境
IntelliJ IDEA 2019.2.4 (Community Edition)
手順
New ProjectでProject SDKに
13
を選択する。https://twitter.com/java/status/1193898422849269760 の http://blog.codefx.org/java/switch-expressions/ を参考にサンプルコードを書く。
import java.io.FileNotFoundException; public class Main { static enum MY_BOOLEAN { TRUE, FALSE, FILE_NOT_FOUND, } public static void main(String[] args) throws Exception { MY_BOOLEAN ternaryBool = MY_BOOLEAN.TRUE; boolean result = switch (ternaryBool) { case TRUE -> true; case FALSE -> false; case FILE_NOT_FOUND -> throw new RuntimeException("This is ridiculous!", new FileNotFoundException()); // as we'll see in "Exhaustiveness", `default` is not necessary default -> throw new IllegalArgumentException("Seriously?! ?"); }; System.out.println(result); } }実行すると下記のようなエラーになる。
Information:java: Errors occurred while compiling module 'untitled' Information:javac 13.0.1 was used to compile java sources Information:2019/11/15 16:13 - Build completed with 1 error and 0 warnings in 3 s 286 ms Error:java: invalid source release 12 with --enable-preview (preview language features are only supported for release 13)Open Module Settings -> Project Settings -> Modules -> Language level を
13 (Preview)
に変更する。これで動く。language levelを変えてあげれば
--enable-preview
は無くても動くらしい。
- 投稿日:2019-11-15T16:18:51+09:00
[ java ] インターフェース
今回はインターフェースについて記す。
インターフェースとは
後からメソッドを実装して使用するためのもの。
例えば足し算や引き算というものは計算とくくることができる。この時、計算がインターフェース、足し算や引き算がインターフェースを実装するクラスということである。インターフェースを使ったプログラム
インターフェースは以下のように定義できる。
//interface インターフェース名{} interface Calc{}インターフェースでは
・フィールド:定数のみ宣言できる(暗黙的にpublic static finalで修飾される)
・メソッド :抽象メソッドのみ定義できる(暗黙的にpublic abstractで修飾される)
と決まっている。interface Calc{ int NUM1 = 1; //修飾子はpublic static finalが省略されている int NUM2 = 10; void calc(); //抽象メソッド }インターフェースはimplementsを使って実装できる。
インターフェース実装により足し算をするクラスがあるとすると以下のとおりである。//class クラス名 implements インターフェース名{} class Plus implements Calc{ public void calc(){ System.out.println( NUM1 + NUM2 ); } }まとめ
インターフェースはオブジェクト指向設計にもかかわってくる。開発で重要な考え方であるので、ぜひマスターして頂きたい。
以上。
- 投稿日:2019-11-15T15:53:35+09:00
オブジェクトを再帰的に文字列シリアライズするとき、出力したくないメンバーをアノテーションを使って設定できるようにする方法
オブジェクトの中身をまとめて文字列にして、ログに出力したいとかありますよね。
そのとき、このフィールドはログに出したくない!とかありませんか?
わざわざtoString()を実装するのは、ちょっとめんどくさいですよね。やりたいこと
- オブジェクトの内容を文字列シリアライズしたい
- オブジェクトのフィールドには、MapとかListとか他のオブジェクトも入っている
- オブジェクトの中で、任意のフィールドは文字列シリアライズ対象外にしたい
オブジェクトの内容
Cart.javapackage jp.co.pmtech.iwata; public class Cart { /** カートID */ private int id; /** カートの中の商品 */ private List<Syohin> syohinList; // getterとかsettertとか }Syohin.javapackage jp.co.pmtech.iwata; public class Syohin { /** 商品ID */ private int id; /** 商品名 */ private String name; /** 価格 */ private BigDecimal price; // getterとかsettertとか }App.javapublic final class App { public static void main(String[] args) { Syohin syohin1 = new Syohin(); syohin1.setId(1); syohin1.setName("ボールペン"); syohin1.setPrice(new BigDecimal(120)); Syohin syohin2 = new Syohin(); syohin2.setId(2); syohin2.setName("ティッシュ"); syohin2.setPrice(new BigDecimal(298)); List<Syohin> list = new ArrayList<>(); list.add(syohin1); list.add(syohin2); Cart cart = new Cart(); cart.setId(1); cart.setSyohinList(list); } }実装してみる
以下のように実装することで実現できます。
- 文字列化しないフィールドにつけるアノテーションクラスを作る。
- 対象のフィールドにアノテーションをつける。
- ReflectionToStringBuilderを拡張して、アノテーションがついたフィールドを無視するようにする。
- ToStringStyleを拡張して、再帰的に文字列化するようにする。
ちなみに、commons-lang3.2からRecursiveToStringStyleが増えていて、これを使えば再帰的に文字列化することはできます。
ただ、内部でReflectionToStringBuilderを呼んでいるため、出力対象のフィールドの制御ができません。
そのため、今回はToStringStyleを自分で拡張することにしました。
(RecursiveToStringStyleの詳細は、おまけの部分に後述してあります)アノテーションクラスの作成
LogIgnore.java@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface LogIgnore { }アノテーションの付与
今回は、商品の価格(Syohin#price)を出さないようにしてみます。
Syohin.javapublic class Syohin { /** 商品ID */ private int id; /** 商品名 */ private String name; /** 価格 */ @LogIgnore private BigDecimal price; }ReflectionToStringBuilderの拡張
ReflectionToStringBuilder#accept()で文字列化対象のフィールドを決めているようなので、こいつをオーバーライドします。
LoggingToStringBuilder.javapublic class LoggingToStringBuilder extends ReflectionToStringBuilder { public LoggingToStringBuilder(final Object object, final ToStringStyle style) { super(object, style); } public static String reflectionToString(final Object object, final ToStringStyle style) { LoggingToStringBuilder builder = new LoggingToStringBuilder(object, style) { @Override protected boolean accept(final Field field) { if (!super.accept(field)) { return false; } if (field.getAnnotation(LogIgnore.class) != null) { return false; } return true; } }; return builder.toString(); } }ToStringStyleの拡張
commons-lang3.2以前は、自前で作ってたみたいですね。
以下の記事を参考に、ToStringStyleを拡張したクラスを作ります。
(名前が偶然にもcommons-langとカブってるので、今回はLoggingToStringStyleというクラス名で作成しました。)
reflectionToString で再帰的出力 - A Memorandumクラスはコピペでも構いませんが、以下の箇所だけ修正してください。
LoggingToStringStyle.javaprotected void appendDetail(StringBuffer buffer, String fieldName, Object value) { for(String packaz : recursivePackags) { if (value.getClass().getCanonicalName().startsWith(packaz)) { // buffer.append(ToStringBuilder.reflectionToString(value, this)); // ↓ここを変える buffer.append(LoggingToStringBuilder.reflectionToString(value, this)); return; } } buffer.append(value); }動かしてみる
App.configString str = LoggingToStringBuilder.toString(cart, new LoggingToStringStyle("jp.co.pmtech.iwata")); System.out.println(str);結果jp.co.pmtech.iwata.Cart[id=1,syohinList=[jp.co.pmtech.iwata.Syohin[id=1,name=ボールペン],jp.co.pmtech.iwata.Syohin[id=2,name=ティッシュ]]]
CartとSyohinの中身がちゃんとでていて、priceも表示されていません。
おまけ:他の方法
JSON化
今回の目的はログへの出力なので、出力形式は特に気にしていません。
Jacksonを使ってJSON化したものを表示してみます。
JsonIgnoreアノテーションをつければ、JSONシリアライズの対象外になります。Syohin.javapublic class Syohin { /** 商品ID */ private int id; /** 商品名 */ private String name; /** 価格 */ @JsonIgnore private BigDecimal price; }App.javaObjectMapper mapper = new ObjectMapper(); String str = mapper.writeValueAsString(cart); System.out.println(str);結果{"id":1,"syohinList":[{"id":1,"name":"ボールペン"},{"id":2,"name":"ティッシュ"}]}
priceの値が出ないですね。
ただ1つ問題があって、SpringBootのRestControllerはJacksonでJSON化しているようなんですよね。
APIの返却にはpriceを含めたかったので、私はこの方法は使いませんでした。
他にJackson使っていなければこの方法でも良いと思います。commons-langのRecursiveToStringStyleで再帰出力
前述した通り、commons-langの3.2からRecursiveToStringStyleが増えています。
ちょっとやってみましょう。
mainの最後に下記コードを入れて実行します。App.javaString str = ReflectionToStringBuilder.toString(cart, new RecursiveToStringStyle()); System.out.println(str);結果jp.co.pmtech.iwata.Cart@610455d6[id=1,syohinList=java.util.ArrayList@2c7b84de{jp.co.pmtech.iwata.Syohin@5a07e868[id=1,name=ボールペン,price=java.math.BigDecimal@36baf30c[intVal=<null>,scale=0]],jp.co.pmtech.iwata.Syohin@76ed5528[id=2,name=ティッシュ,price=java.math.BigDecimal@24d46ca6[intVal=<null>,scale=0]]}]再帰的に文字列化してくれているようですが、オブジェクトIDが表示されていてちょっとうるさいです。
しかもBigDecimalの値出てないし・・・。おわりに
使いどころですが、恐らくログの吐き出しくらいしかないかと・・・
セキュリティ上、個人情報やパスワードなどをログに吐き出したくないとかとか。
あとは、めちゃめちゃ大きいオブジェクトで、ログに出すものを端折るとか??要件によっては、JSONだったりcommons-langのRecursiveToStringStyleでも良いと思います。
ソースコードはgithubにあげていますので、参考にしてください。
https://github.com/pmt-iwata/LoggingToStringBuilder
- 投稿日:2019-11-15T11:57:54+09:00
Javaにおける入力した5個の数字の内、最大値と最小値を求める(修正ver)
先日5個の数字から最大値と最小値を求めるプログラムを投稿しましたが、コメントでアドバイスをいただいたので修正したものを再投稿したいと思います。@LouiS0616 さんコメントありがとうございます...!!
ちなみに前回の記事のリンクをここに貼っておきます。https://qiita.com/GARA41679531/items/c81b57a56bfcb28f84a0
変更した点
前回のコードでは変数 min と max を 0 で初期化していたので、入力された数字がすべて正の数または負の数の時に最大値もしくは最小値のどちらかが 0 で出力されてしまいました。
そこを改善するために、1つ目に入力された数字を変数 min と max に代入し、そこから大小関係を調べるコードに書き直しました!コードの記述
package max_min; import java.util.*; import java.math.*; public class Max_Min { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ Scanner scanner=new Scanner(System.in); double max=0; double min=0; int i; double a; System.out.println("5個の数字の中から最大値と最小値を求めます。"); for(i=1;i<6;i++) { System.out.print(i+"つ目の数字:"); a=scanner.nextDouble(); if(i==1) { min=a; max=a; }else { if(a<=min) { min=a; }else if(a>=max) { max=a; } } } System.out.println("最大値は"+max+"です。"); System.out.println("最小値は"+min+"です。"); } }上記に載せたコードが実際に書き直したコードです。
具体的にどこを変えたのかというと、 for 構文を使って5回数字を入力するプログラムを作っているのですが、その中で if 構文を使って1つ目に入力した数字を変数 min と max に代入させるようにしています。
実行結果
今回はすべて負の数を入力してみましたが、最大値が 0 になることなくちゃんと表示することができました!!まとめ
自分では完璧と思っているプログラムでも改善点は隠れているのだなと改めて実感しました ! 笑
今回記述したコードの中にもまだまだ改善点があれば教えていただけると幸いです !
最後まで目を通していただいてありがとうございました !!
- 投稿日:2019-11-15T11:42:40+09:00
Java Gold SE 8 合格に際して
はじめに
この度、11月某日にJava SE 8 Programmer II(Gold)を受験し、無事合格致しました。
様々な合格体験記がこのQiitaにはございますし、私自身、Gold受験を思い立ったのが5月ごろということでお世辞にも効率よい勉強の仕方が出来たとは思いません。
しかし、時間をかけた分操作のプロセス、思想などを深く掘り下げて理解することが出来たと感じています。実際満点とはいきませんでしたが、問題に対してプロセスを追い、噛み砕きながら解いていくことが出来ました。
資格を取るにあたり、ただ問題集を闇雲に頼らず、己の知識として吸収することが出来たのは良い経験になりました。
本記事では、頻出とかは関係なく面白いと思った内容を辿っていこうと思います。
Stream Pipeline
Goldを受験する際避けて通れないStream APIですが、操作の流れが一見誤解されそうな仕組みをとっています。しかし、一度理解すればStream関連の問題にしっかり活きてくる内容だと思います。
以下に例を示します。
example.javapublic class example{ public static void main(String[] args){ Stream<String> fruit = Stream.of("banana", "grape", "orange","apple"); .filter(s -> s.length() > 5) .peek(System.out::print) .map(String::toUpperCase) .peek(System.out::print); long l = fruit.count(); } }終端操作countが示されているため、fruitで定義された中間操作が遅延して実行されます。では、Streamの各要素はどのように処理されていくのでしょうか。
私は、学習初期では以下のような流れを想像していました。
即ち、一つの中間操作ごとにすべての要素が検証されると思っていました。これが正しければ、出力は
bananaorangeBANANAORANGE
になるはずです。一見正しいように思えます。しかし、実際は異なり、Stream.ofで定義された要素が一つずつ最後まで操作されてから次の要素に移る、という流れでした。
つまり、peekの実行順序は②→④→⑦→⑨となるため、出力はbananaBANANAorangeORANGE
です。実際にStreamを使う場合意識する必要は無いかもしれませんが、操作順序の流れは一度理解してしまえば見た目が多少変わっても動じずに紐解くことが出来ます。なお、終端操作countが無かった場合、Streamが中間操作のみの為何も出力されない点にも注意が必要です。終端操作が正しく記載されているかもよく問われます。(Optional絡みが特に多かった気がします)
muiti-catchとtry-with-resource
例外とアサーションの分野で数問出題されますが、他の項目より容易に覚えやすいので取りこぼしたくないところです。実務でも避けて通れないところ(だと思う)なので習得が確実に活きる分野だと思います。
まずSE7で導入されたmulti-catch。こちらは「|」で区切ることにより例外処理を纏めて記述できるようにした構文です。ここで注意する点は2点です。
1. 継承関係(ポリモーフィック)にある例外を併記することはできない
例えば、IOException
とException
を併記するとコンパイルエラーになります。2. 変数は一つ
併記する例外がたくさんあっても、変数は最後に一つです。複数記述するとコンパイルエラーになります。catch(IOException e1 | InterruptedException e2){} //コンパイルエラーまた、複数のcatch節がある場合、スーパークラスの例外の後にサブクラスの例外を記述することは出来ません(手前でキャッチされ検査できなくなるため)。
次に、これもSE7で導入されたtry-with-resource文。こちらは、SE 6以前ではfinally節でクローズ処理を記述していたもの(ex.
FileReader
)を自動クローズできる仕組みを提供する構文です。以下のように、tryの後ろ括弧内にリソースを定義することで仕組みが適用されます。
try(FileReader fr = new FileReader("sample1.txt"); FileWriter fw = new FileWriter("sample2.txt")){ //リソースの読み書き }catch(IOException e){ //例外処理 }ここで、クローズされる順番は定義した逆順となります。また、リソースを独自に定義している場合などでリソースが
AutoClosable
かClosable
を実装していない場合、コンパイルエラーとなります。I/Oで普通用いられるクラスはAutoClosable
を実装している故問題なく実行できるわけです。ForkJoinPool
ForkJoinPoolでは実行用メソッドが3種類定義されています。このうち、処理の同期をとるのはinvokeのみ。
また、非同期の場合戻り値によって実行メソッドが変わります。以下にまとめました。
メソッド名 戻り値 同期/非同期 継承クラス execute void 非同期 RecursiveAction invoke T 同期 RecursiveAction/RecursiveTask<V> submit ForkJoinTask<T> 非同期 RecursiveTask<V> 使っているメソッドを見て、invokeならば結果が一意に定まり、他なら不定になります。
Scrollable ResultSet
JDBCにおけるResultSetでは、カーソルの移動を自由に行える仕組みを定義することができます。
//スクロール可能なResultSetを使用するためのStatement定義 Statement createStatement(int resultType, int resultSetConcurrency) throws SQLException※デフォルトがスクロール不可のドライバとスクロール可のドライバが存在します。スクロール不可に出来ないものもあり、定数をスクロール不可にすると無視されます。(コンパイルエラーではない)
第一引数・第二引数に定義できる定数はResultSetインターフェースに定義されています。
- 第一引数
- TYPE_FORWARD_ONLY:順方向のみ移動
- TYPE_SCROLL_INSENSITIVE:スクロール可、DB非反映
- TYPE_SCROLL_SENSITIVE:スクロール可、DB反映
- 第二引数
- CONCUR_READ_ONLY:更新不可ResultSet
- CONCUR_UPDATABLE:更新可ResultSet
引数を取る際は2つ必要なため、1つのみの記述ではコンパイルエラーになります。
更新可能ResultSetにした場合、対象テーブルに主キーが設定されていればInsert、Update、Deleteの各操作が可能になりますが、
insertRow
/updateRow
/deleteRow
メソッドを呼び出さなければ実際にDBへ反映されません。おわりに
GoldはSilverと比べ、覚える量・理解度の深さをかなり求められたと感じます。あまりに多くて気が滅入ることもしばしばありました。しかし、マイペースで良いので時間を掛けてじっくり取り組めば十分取得可能な資格だと思います。受験料がネックですが…
私は参考文献にある黒本による学習で合格できました。試験のみ考えるならこの本と公式APIの確認で十分です。より深い理解・完全正答を目指すなら、紫本など検討してみてもいいかもしれません…。
チャレンジして損はないと思います。javaの習得を考えている方、ぜひSilver、Goldの獲得を目指しましょう!
参考文献
- 投稿日:2019-11-15T01:42:36+09:00
用語:インターフェースについて
【インターフェース/interface】とは
・境界面、接点、連絡役、橋渡し、などの意味を持つ
・何かと何かが接する部分、もしくはその間
・クラスに含まれるメソッドのひとつ
・メソッドの型と変数のみを定義している
・メソッドの具体的な処理内容は記述していない
・メソッドを利用する直前で処理内容を記述することが可能
・複数のインターフェースを継承し、新しいインターフェースを作ることも可能(多重継承というらしい)(例)インターフェースの宣言/実装
『宣言』
→ interface インターフェース名 {}『実装』
→ class クラス名 implements インターフェース名{}
【抽象クラス/abstract】とは
・abstract:抽象、の意
・抽象メソッドを1つ以上持つクラスのこと
・定義のみを宣言するメソッド(メソッド名・引数など)
・具体的な処理内容を記述しないメソッド
・メソッドのオーバーライドが可能
※オーバーライド:親クラスのメソッドをサブクラスで書き換え(上書き)すること
・直接のインスタンス化が不可
・多重継承が不可(例)抽象クラス/抽象メソッドの宣言
『抽象クラス』
→ abstract class クラス名{}『抽象メソッド』
→ abstract 戻り値の型 メソッド名(引数の型 引数名);
抽象クラスのメリットとして、
”抽象メソッドのオーバーライドによって、実装時に『挿入場所は決まっていないがいずれ必要になるメソッド』を予め宣言する。詳細の定義は後のクラスで定義する。という実装が可能”
・・・だそうだ。言葉だけだとふんわりとしか想像できないので、一旦この辺は課題とする。
- 投稿日:2019-11-15T01:42:36+09:00
用解説語:インターフェースについて
【インターフェース/interface】とは
・境界面、接点、連絡役、橋渡し、などの意味を持つ
・何かと何かが接する部分、もしくはその間
・クラスに含まれるメソッドのひとつ
・メソッドの型と変数のみを定義している
・メソッドの具体的な処理内容は記述していない
・メソッドを利用する直前で処理内容を記述することが可能
・複数のインターフェースを継承し、新しいインターフェースを作ることも可能(多重継承というらしい)(例)インターフェースの宣言/実装
『宣言』
→ interface インターフェース名 {}『実装』
→ class クラス名 implements インターフェース名{}
【抽象クラス/abstract】とは
・abstract:抽象、の意
・抽象メソッドを1つ以上持つクラスのこと
・定義のみを宣言するメソッド(メソッド名・引数など)
・具体的な処理内容を記述しないメソッド
・メソッドのオーバーライドが可能
※オーバーライド:親クラスのメソッドをサブクラスで書き換え(上書き)すること
・直接のインスタンス化が不可
・多重継承が不可(例)抽象クラス/抽象メソッドの宣言
『抽象クラス』
→ abstract class クラス名{}『抽象メソッド』
→ abstract 戻り値の型 メソッド名(引数の型 引数名);
抽象クラスのメリットとして、
”抽象メソッドのオーバーライドによって、実装時に『挿入場所は決まっていないがいずれ必要になるメソッド』を予め宣言する。詳細の定義は後のクラスで定義する。という実装が可能”
・・・だそうだ。言葉だけだとふんわりとしか想像できないので、一旦この辺は課題とする。
- 投稿日:2019-11-15T00:52:03+09:00
用語解説:アクセサ / アクセサメソッド、カプセル化
アクセサメソッドに入る前の補足
【カプセル化/ encapsulation】とは
・オブジェクト指向言語の特徴の一つ
・関連性のあるデータやプログラムを容器の中に収納すること
・容器 = オブジェクト
(※容器の名前はオブジェクト、と記載していました。修正いたします。)・フィールド、メソッドを収容する
・収容したものは、クラスの外から干渉できない
(※干渉できるものもあるようです。ご指摘ありがとうございます。)・オブジェクトの中に収容したフィールドやメソッドは、外から干渉できないように保護されている
・オブジェクトに入れて他から触れなくすることをカプセル化という(みんな箱入り娘?)
【アクセサ/アクセサメソッド】とは・フィールドのアクセスを仲介する目的で用意されるメソッド
・フィールドの値の更新や取り出しは、そのオブジェクト(容器)のメソッドを経由する
・全てのフィールド値は『set○○』『get○○』というメソッドがないとアクセスできない
・この『set○○』『get○○』のことをアクセサという【フィールド】とは
・classの構成要素の一つ
※classはフィールドとメソッドで成り立つ
・クラスフィールドとインスタンスフィールドの二つ
・staticあり:インスタンスフィールド。いくつインスタンスを生成しても1つしか作成されない
・staticなし:クラスフィールド。インスタンス生成毎に別途作成される【クラス】について
・フィールドとメソッドによって構成される
(例)public class test{ int a; // フィールド String b; // フィールド public int getA{ // メソッド return x; } public void setB(String b){ // メソッド this.b = b; } }
- 投稿日:2019-11-15T00:00:11+09:00
JavalinでWebアプリケーションを構築する
Javalinというフレームワークを試してみたので投稿します。
このフレームワークは、参考書や日本語のサイトなどの情報が少ないですが、DocsとTutorialがとてもわかりやすく、特にTutorialには、それぞれサンプルプログラムがGitHUBに投稿されているので、Intellijにそのままインポートしてカスタマイズできたりしますので、WEBアプリを作成するのにあまり困らないでしょう。
環境
カテゴリ 値 os windows 10 home 64bit Java 1.8 framework Javalin 3.6 開発環境 IntelliJ IDEA 2019.2 1. Hello World
Gradleプロジェクトを新規作成して、以下を実装するだけです。JavaがインストールされていればWeb Serverも立ち上がります。Tomcatも必要ありません。
HelloWorld.javaimport io.javalin.Javalin; public class HelloWorld { public static void main(String[] args) { Javalin app = Javalin.create().start(7000); app.get("/", ctx -> ctx.result("Hello World")); } }2. Tutorial(Basic website structure)
実践では、Tutorialのサンプルプログラムをダウンロードし、カスタマイズするようなアプローチがいいでしょう。
「Basic website structure」tipsy/javalin-website-exampleをダウンロードして、Intellijで開いてみます。project.tree└─src └─main ├─java │ └─app │ │ Main.java │ ├─book │ │ Book.java │ │ BookController.java │ │ BookDao.java │ ├─index │ │ IndexController.java │ ├─login │ │ LoginController.java │ ├─user │ │ User.java │ │ UserController.java │ │ UserDao.java │ └─util │ Filters.java │ HerokuUtil.java │ MessageBundle.java │ Path.java │ RequestUtil.java │ ViewUtil.java └─resources ├─localization │ messages_de.properties │ messages_en.properties ├─public │ │ main.css │ └─img │ english.png │ favicon.png │ german.png │ logo.png ├─velocity │ │ layout.vm │ │ notFound.vm │ ├─book │ │ all.vm │ │ one.vm │ ├─index │ │ index.vm │ └─login │ login.vm └─velocityconfig velocity_implicit.vmMainを実行するとそのまま動きます。あとはDAOなどのクラスをデータベースに変換するだけで、プロジェクトテンプレートを作成することができます。
3. Thymeleaf
さきほどインポートしたプロジェクトのテンプレートエンジンは「velocity」になっています。Thymeleafを使いたい場合は変更が必要です。「javalin」は、thymeleafもデフォルトでサポートしているようですが、dialectに対応していないようなので、カスタマイズしましょう。
htmlのルートは、「/public/templates」とします。以下の構成でファイルを作成していきます。
layout.htmlで、layout-dialectを使ってhtmlのレイアウトを共通化します。└─src ├─main │ ├─java │ │ └─app │ │ AppThymeleafRenderer.java │ │ Main.java │ └─resources │ ├─public │ │ └─css │ │ main.css │ └─templates │ │ layout.html │ └─example │ index.html(1) gradle.gradleに以下を追加します。
※Projectは、gradleで焼き直ししていますcompile group: 'org.thymeleaf', name: 'thymeleaf', version: '3.0.11.RELEASE' compile group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '2.4.1' compile "org.webjars:jquery:3.4.1"(2) ThymeleafRenderer
Thymeleafは、「layout-dialect」を使う場合などカスタマイズする場合独自のrendererを作成する必要があります。
AppThymeleafRenderer.javaimport io.javalin.http.Context; import io.javalin.plugin.rendering.FileRenderer; import nz.net.ultraq.thymeleaf.LayoutDialect; import org.jetbrains.annotations.NotNull; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.WebContext; import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; import org.thymeleaf.templateresolver.ITemplateResolver; import java.util.Map; public class AppThymeleafRenderer implements FileRenderer { private final TemplateEngine templateEngine; public AppThymeleafRenderer() { templateEngine = templateEngine(); } @Override public String render(@NotNull String filePath, @NotNull Map<String, Object> model, @NotNull Context ctx) { WebContext context = new WebContext(ctx.req, ctx.res, ctx.req.getServletContext(), ctx.req.getLocale()); context.setVariables(model); return templateEngine.process(filePath, context); } private TemplateEngine templateEngine() { TemplateEngine templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); // LayoutDialectを追加する。 templateEngine.addDialect(new LayoutDialect()); return templateEngine; } private ITemplateResolver templateResolver() { ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); // /templatesフォルダをルートにする。 templateResolver.setPrefix("/templates/"); return templateResolver; } }(3) Main.javaに追加
Mainメソッドで、「JavalinRenderer」に、先に作成した「AppThymeleafRenderer」クラスを追加します。
また、Routerでhtmlを返す場合は、「ctx.render」とします。Main.javapublic class Main { public static void main(String[] args) { //add JavalinRenderer.register(new AppThymeleafRenderer(), ".html"); //add end Javalin app = Javalin.create(config->{ config.enableWebjars(); config.addStaticFiles("/public"); }).start(7000); //add app.get("/template", ctx -> { Map<String, Object> model = new HashMap<>(); model.put("hello", "hello world"); ctx.render("/example/index.html", model); }); //add end(4) layout.html
layout.html<!DOCTYPE html> <html lang="ja" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <meta charset="UTF-8"> <title>Spring Boot Sample Site</title> <meta name="description" content="common-meta"> <script th:src="@{/webjars/jquery/3.4.1/jquery.min.js}"></script> <link rel="stylesheet" href="css/main.css"> </head> <body> <div class="container"> <div class="inner"> <div class="body header"> <div class="apptitle">Example</div> </div> <div class="body main"> <div layout:fragment="contents"></div> </div> <div class="body footer"> <footer style="text-align:center;">共通フッタ</footer> </div> </div> </div> </body> </html>index.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/layout.html}"> <head></head> <body> <div layout:fragment="contents"> <div th:text="${hello}">hello</div> </div> </body> </html>http://localhost:7000/templateにアクセスすると、layoutも正しく出力されてます。
4. Database
いろいろ悩んだ末、「Apache commons-dbutils」を使うことにしました。理由は単純にわかりやすいので。Connection Poolは、「HikariCP」、Databaseには「postgresql」を使用します。
build.gradlecompile "com.zaxxer:HikariCP:2.7.3" compile "org.postgresql:postgresql:42.2.8" compile "commons-dbutils:commons-dbutils:1.7"PGDataSource.javaimport com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; public class PGDataSource { private static HikariDataSource ds = null; public PGDataSource(){ HikariConfig hikariconfig = new HikariConfig(); hikariconfig.setUsername("testuser"); hikariconfig.setPassword("************************"); hikariconfig.setJdbcUrl("jdbc:postgresql://localhost:5432/javalindb"); hikariconfig.setMaximumPoolSize(2); ds=new HikariDataSource(hikariconfig); } public DataSource getDataSource(){ return ds; } public void close(){ if (!ds.isClosed()) ds.close(); } }User.javaimport lombok.Data; @Data public class User { private String username; private String password; public User(){}; public User(String username, String password){ this.username=username; this.password=password; }; }Main.javapublic class Main { //add private static PGDataSource ds; public static Connection getConnection() throws SQLException { Connection connection = ds.getDataSource().getConnection(); return connection; } //add end public static void main(String[] args) { JavalinRenderer.register(new AppThymeleafRenderer(), ".html"); //add ds = new PGDataSource(); //add end //・・・・・・・ //add app.get("/user", ctx -> { QueryRunner runner = new QueryRunner(); ResultSetHandler rsh = new BeanListHandler(User.class); List<User> users = (List<User>) runner.query(getConnection(), "select username from public.users",rsh); ctx.json(users); }); //add endこれだけわかれば、あとはDocsとTutorialを見ていけばWEBアプリを作成できます。
5. 参考サイト