20191115のJavaに関する記事は9件です。

IntelliJ IDEAでjava 13のswitchをためす

※ 2019/11/15 時点です。

IntelliJ IDEAまったくと言っていいほど触ったことが無いので、自分用にメモ。

環境

IntelliJ IDEA 2019.2.4 (Community Edition)

手順

New ProjectでProject SDKに13を選択する。

intelljeideaswith0001.jpg

https://twitter.com/java/status/1193898422849269760http://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)に変更する。

intelljeideaswith0002.jpg

これで動く。language levelを変えてあげれば--enable-previewは無くても動くらしい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ 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 );
    }
}

まとめ

インターフェースはオブジェクト指向設計にもかかわってくる。開発で重要な考え方であるので、ぜひマスターして頂きたい。
以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクトを再帰的に文字列シリアライズするとき、出力したくないメンバーをアノテーションを使って設定できるようにする方法

オブジェクトの中身をまとめて文字列にして、ログに出力したいとかありますよね。
そのとき、このフィールドはログに出したくない!とかありませんか?
わざわざtoString()を実装するのは、ちょっとめんどくさいですよね。

やりたいこと

  • オブジェクトの内容を文字列シリアライズしたい
  • オブジェクトのフィールドには、MapとかListとか他のオブジェクトも入っている
  • オブジェクトの中で、任意のフィールドは文字列シリアライズ対象外にしたい

オブジェクトの内容

Cart.java
package jp.co.pmtech.iwata;

public class Cart {
    /** カートID */
    private int id;
    /** カートの中の商品 */
    private List<Syohin> syohinList;

    // getterとかsettertとか
}
Syohin.java
package jp.co.pmtech.iwata;

public class Syohin {
    /** 商品ID */
    private int id;
    /** 商品名 */
    private String name;
    /** 価格 */
    private BigDecimal price;

    // getterとかsettertとか
}
App.java
public 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);
    }
}

実装してみる

以下のように実装することで実現できます。

  1. 文字列化しないフィールドにつけるアノテーションクラスを作る。
  2. 対象のフィールドにアノテーションをつける。
  3. ReflectionToStringBuilderを拡張して、アノテーションがついたフィールドを無視するようにする。
  4. ToStringStyleを拡張して、再帰的に文字列化するようにする。

ちなみに、commons-lang3.2からRecursiveToStringStyleが増えていて、これを使えば再帰的に文字列化することはできます。
ただ、内部でReflectionToStringBuilderを呼んでいるため、出力対象のフィールドの制御ができません。
そのため、今回はToStringStyleを自分で拡張することにしました。
(RecursiveToStringStyleの詳細は、おまけの部分に後述してあります)

アノテーションクラスの作成

LogIgnore.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogIgnore {
}

アノテーションの付与

今回は、商品の価格(Syohin#price)を出さないようにしてみます。

Syohin.java
public class Syohin {
    /** 商品ID */
    private int id;
    /** 商品名 */
    private String name;
    /** 価格 */
    @LogIgnore
    private BigDecimal price;
}

ReflectionToStringBuilderの拡張

ReflectionToStringBuilder#accept()で文字列化対象のフィールドを決めているようなので、こいつをオーバーライドします。

LoggingToStringBuilder.java
public 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.java
    protected 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.config
    String 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.java
public class Syohin {
    /** 商品ID */
    private int id;
    /** 商品名 */
    private String name;
    /** 価格 */
    @JsonIgnore
    private BigDecimal price;
}
App.java
    ObjectMapper 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.java
    String 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 に代入させるようにしています。

実行結果

image.png
今回はすべて負の数を入力してみましたが、最大値が 0 になることなくちゃんと表示することができました!!

まとめ

自分では完璧と思っているプログラムでも改善点は隠れているのだなと改めて実感しました ! 笑
今回記述したコードの中にもまだまだ改善点があれば教えていただけると幸いです !
最後まで目を通していただいてありがとうございました !!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Gold SE 8 合格に際して

はじめに

 この度、11月某日にJava SE 8 Programmer II(Gold)を受験し、無事合格致しました。

様々な合格体験記がこのQiitaにはございますし、私自身、Gold受験を思い立ったのが5月ごろということでお世辞にも効率よい勉強の仕方が出来たとは思いません。

しかし、時間をかけた分操作のプロセス、思想などを深く掘り下げて理解することが出来たと感じています。実際満点とはいきませんでしたが、問題に対してプロセスを追い、噛み砕きながら解いていくことが出来ました。

資格を取るにあたり、ただ問題集を闇雲に頼らず、己の知識として吸収することが出来たのは良い経験になりました。

本記事では、頻出とかは関係なく面白いと思った内容を辿っていこうと思います。

Stream Pipeline

Goldを受験する際避けて通れないStream APIですが、操作の流れが一見誤解されそうな仕組みをとっています。しかし、一度理解すればStream関連の問題にしっかり活きてくる内容だと思います。

 以下に例を示します。

example.java
public 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の各要素はどのように処理されていくのでしょうか。

私は、学習初期では以下のような流れを想像していました。

図1.png

即ち、一つの中間操作ごとにすべての要素が検証されると思っていました。これが正しければ、出力はbananaorangeBANANAORANGEになるはずです。一見正しいように思えます。

しかし、実際は異なり、Stream.ofで定義された要素が一つずつ最後まで操作されてから次の要素に移る、という流れでした。
図2.png
つまり、peekの実行順序は②→④→⑦→⑨となるため、出力はbananaBANANAorangeORANGEです。

実際にStreamを使う場合意識する必要は無いかもしれませんが、操作順序の流れは一度理解してしまえば見た目が多少変わっても動じずに紐解くことが出来ます。なお、終端操作countが無かった場合、Streamが中間操作のみの為何も出力されない点にも注意が必要です。終端操作が正しく記載されているかもよく問われます。(Optional絡みが特に多かった気がします)

muiti-catchとtry-with-resource

 例外とアサーションの分野で数問出題されますが、他の項目より容易に覚えやすいので取りこぼしたくないところです。実務でも避けて通れないところ(だと思う)なので習得が確実に活きる分野だと思います。

まずSE7で導入されたmulti-catch。こちらは「|」で区切ることにより例外処理を纏めて記述できるようにした構文です。ここで注意する点は2点です。

1. 継承関係(ポリモーフィック)にある例外を併記することはできない
例えば、IOExceptionExceptionを併記するとコンパイルエラーになります。

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){
  //例外処理
}

ここで、クローズされる順番は定義した逆順となります。また、リソースを独自に定義している場合などでリソースがAutoClosableClosableを実装していない場合、コンパイルエラーとなります。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の獲得を目指しましょう!

参考文献

徹底攻略 Java SE 8 Gold 問題集[1Z0-809]対応

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

用語:インターフェースについて

【インターフェース/interface】とは
 ・境界面、接点、連絡役、橋渡し、などの意味を持つ
 ・何かと何かが接する部分、もしくはその間
 ・クラスに含まれるメソッドのひとつ
 ・メソッドの型と変数のみを定義している
 ・メソッドの具体的な処理内容は記述していない
 ・メソッドを利用する直前で処理内容を記述することが可能
 ・複数のインターフェースを継承し、新しいインターフェースを作ることも可能(多重継承というらしい)

(例)インターフェースの宣言/実装

『宣言』
→ interface インターフェース名 {}

『実装』

→ class クラス名 implements インターフェース名{}

【抽象クラス/abstract】とは
 ・abstract:抽象、の意
 ・抽象メソッドを1つ以上持つクラスのこと
 ・定義のみを宣言するメソッド(メソッド名・引数など)
 ・具体的な処理内容を記述しないメソッド
 ・メソッドのオーバーライドが可能
  ※オーバーライド:親クラスのメソッドをサブクラスで書き換え(上書き)すること
 ・直接のインスタンス化が不可
 ・多重継承が不可

(例)抽象クラス/抽象メソッドの宣言

『抽象クラス』
→ abstract class クラス名{}

『抽象メソッド』

→ abstract 戻り値の型 メソッド名(引数の型 引数名);

抽象クラスのメリットとして、

”抽象メソッドのオーバーライドによって、実装時に『挿入場所は決まっていないがいずれ必要になるメソッド』を予め宣言する。詳細の定義は後のクラスで定義する。という実装が可能”

・・・だそうだ。言葉だけだとふんわりとしか想像できないので、一旦この辺は課題とする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

用解説語:インターフェースについて

【インターフェース/interface】とは
 ・境界面、接点、連絡役、橋渡し、などの意味を持つ
 ・何かと何かが接する部分、もしくはその間
 ・クラスに含まれるメソッドのひとつ
 ・メソッドの型と変数のみを定義している
 ・メソッドの具体的な処理内容は記述していない
 ・メソッドを利用する直前で処理内容を記述することが可能
 ・複数のインターフェースを継承し、新しいインターフェースを作ることも可能(多重継承というらしい)

(例)インターフェースの宣言/実装

『宣言』
→ interface インターフェース名 {}

『実装』
→ class クラス名 implements インターフェース名{}


【抽象クラス/abstract】とは
 ・abstract:抽象、の意
 ・抽象メソッドを1つ以上持つクラスのこと
 ・定義のみを宣言するメソッド(メソッド名・引数など)
 ・具体的な処理内容を記述しないメソッド
 ・メソッドのオーバーライドが可能
  ※オーバーライド:親クラスのメソッドをサブクラスで書き換え(上書き)すること
 ・直接のインスタンス化が不可
 ・多重継承が不可

(例)抽象クラス/抽象メソッドの宣言

『抽象クラス』
→ abstract class クラス名{}

『抽象メソッド』

→ abstract 戻り値の型 メソッド名(引数の型 引数名);

抽象クラスのメリットとして、

”抽象メソッドのオーバーライドによって、実装時に『挿入場所は決まっていないがいずれ必要になるメソッド』を予め宣言する。詳細の定義は後のクラスで定義する。という実装が可能”

・・・だそうだ。言葉だけだとふんわりとしか想像できないので、一旦この辺は課題とする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

用語解説:アクセサ / アクセサメソッド、カプセル化

アクセサメソッドに入る前の補足

【カプセル化/ 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;
 }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.java
import 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.vm

Mainを実行するとそのまま動きます。あとは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.java
import 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.java
public 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も正しく出力されてます。

image.png

4. Database

いろいろ悩んだ末、「Apache commons-dbutils」を使うことにしました。理由は単純にわかりやすいので。Connection Poolは、「HikariCP」、Databaseには「postgresql」を使用します。

build.gradle
    compile "com.zaxxer:HikariCP:2.7.3"
    compile "org.postgresql:postgresql:42.2.8"
    compile "commons-dbutils:commons-dbutils:1.7"
PGDataSource.java
import 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.java
import 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.java
public 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

image.png

これだけわかれば、あとはDocsとTutorialを見ていけばWEBアプリを作成できます。

5. 参考サイト

Javalinを調べたときのメモ
nodchipの日記-Apache Commons DbUtils

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む