覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Class#newInstanceは非推奨になるようなので気を付けよう

Javaでリフレクションによるデフォルトコンストラクタ呼び出しでインスタンス生成する方法としてClass#newInstanceが挙げられると思います。

ただし、Class#newInstanceはJava9で非推奨(Deprecated)になるようです。(さらに以降のバージョンでAPI自体が削除になるんですかね・・・)

Class#newInstanceは、シグニチャ上に無いチェック例外がスローできてしまうため、それが問題で非推奨となったようです。
その辺の動きについて今回のエントリで確認してみます。


インスタンス化対象のクラス

今回は以下クラスのインスタンス化を行って動きを確認します。
このクラスは、デフォルトコンストラクタを実行するとFileNotFoundException(チェック例外)をスローするようになっています。

import java.io.FileNotFoundException;

public class SampleObject {

    public SampleObject() throws FileNotFoundException {
        throw new FileNotFoundException("target file not found.");
    }
}


Class#newInstanceでコード上に無い例外をスローしてみる

SampleObjectClass#newInstanceでインスタンス化するコードを書いて実行してみます。


■実行用のサンプルコード

public class ClazzNewInstanceTrial {

    public static void main(String[] args) {
        try {
            SampleObject obj = createSampleObject();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private static SampleObject createSampleObject() throws IllegalAccessException, InstantiationException {
        return SampleObject.class.newInstance();
    }
}

createSampleObjectメソッド内でClass#newInstanceを実行してSampleObjectのインスタンスを生成していますが、
createSampleObjectメソッドのシグニチャにはスローする例外としてIllegalAccessException, InstantiationExceptionのみが登場しており、
FileNotFoundExceptionは登場しません。


これを実行してみると以下の結果が出ます

■実行結果

Exception in thread "main" java.io.FileNotFoundException: target file not found.
    at sample.app.reflection.SampleObject.<init>(SampleObject.java:8)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.lang.Class.newInstance(Class.java:442)
    at sample.app.reflection.ClazzNewInstanceTrial.createSampleObject(ClazzNewInstanceTrial.java:16)
    at sample.app.reflection.ClazzNewInstanceTrial.main(ClazzNewInstanceTrial.java:7)

なんとまぁ、メソッドのシグニチャに登場していなかったのに、createSampleObjectメソッドがFileNotFoundExceptionをスローしています。
コード上に現れていないチェック例外がスローできてしまっています。


代替案の Constructor#newInstanceの場合

Class#newInstanceは使うなとなってどうすればいいかと言うと、Constructor#newInstanceによるインスタンス生成で代替すればいいようです。
(実際にはClass.getDeclaredConstructor().newInstance()を実行することになります)

SampleObjectのインスタンス化をしてみてClass#newInstanceとの例外に関する動きの違いを確認してみます


■実行用のサンプルコード

import java.lang.reflect.InvocationTargetException;

public class ConstructorNewInstance {

    public static void main(String[] args) {
        try {
            SampleObject obj = createSampleObject();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private static SampleObject createSampleObject()
            throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        return SampleObject.class.getDeclaredConstructor().newInstance();
    }

}


これを実行してみると以下の結果が出ます

■実行結果

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at sample.app.reflection.ConstructorNewInstance.createSampleObject(ConstructorNewInstance.java:23)
    at sample.app.reflection.ConstructorNewInstance.main(ConstructorNewInstance.java:9)
Caused by: java.io.FileNotFoundException: target file not found.
    at sample.app.reflection.SampleObject.<init>(SampleObject.java:8)
    ... 6 more

createSampleObjectメソッドからは、InvocationTargetExceptionでラップされたFileNotFoundExceptionがスローされて、それをcatchできています。
メソッドのシグニチャ通りの例外がスローされています。


蛇足

Java7から、java.lang.ReflectiveOperationExceptionがリフレクション関係の例外の親クラスとして定義されてますので
上記の実行サンプルのコードはReflectiveOperationExceptionを使ってもう少しシンプル記述できます。


■実行用のサンプルコード

public class ConstructorNewInstance2 {

    public static void main(String[] args) {
        try {
            SampleObject obj = createSampleObject();
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }

    private static SampleObject createSampleObject() throws ReflectiveOperationException {
        return SampleObject.class.getDeclaredConstructor().newInstance();
    }

}

■実行結果

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at sample.app.reflection.ConstructorNewInstance2.createSampleObject(ConstructorNewInstance2.java:14)
    at sample.app.reflection.ConstructorNewInstance2.main(ConstructorNewInstance2.java:7)
Caused by: java.io.FileNotFoundException: target file not found.
    at sample.app.reflection.SampleObject.<init>(SampleObject.java:8)
    ... 6 more

InvocationTargetExceptionでラップされたFileNotFoundExceptionがスローされて、それをReflectiveOperationExceptionでcatchできています


まとめ

Class#newInstanceConstructor#newInstanceの例外に関する動きの違いを確認しました。
Class.newInstance()を使っている箇所は、早めにClass.getDeclaredConstructor().newInstance() に置き換えましょう。



関連エントリ

MavenでOWASP Dependency CheckによるJavaライブラリの脆弱性をチェックする

OWASP Dependency Checkで使用しているJavaライブラリの脆弱性をチェックすることができます。

今回はMavenのpluginを使用します。


設定

pom.xmlに以下を追記します。

<plugins>
・・・
  <plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>2.0.0</version>
    <configuration>
        <assemblyAnalyzerEnabled>false</assemblyAnalyzerEnabled>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
  </plugin>
・・・
</plugins>


脆弱性のチェック

pom.xmlへの追記が終わった状態で以下コマンドをを実行します

mvn dependency-check:check


すると以下のような出力がされます

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-app 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- dependency-check-maven:2.0.0:check (default-cli) @ srcl ---
[INFO] Checking for updates
[INFO] starting getUpdatesNeeded() ...
[INFO] Download Started for NVD CVE - Modified
[INFO] Download Complete for NVD CVE - Modified  (5176 ms)
[INFO] Processing Started for NVD CVE - Modified
[INFO] Processing Complete for NVD CVE - Modified  (6004 ms)
[INFO] Begin database maintenance.
[INFO] End database maintenance.
[INFO] Check for updates complete (14381 ms)
[INFO] Analysis Started
[INFO] Finished Archive Analyzer (1 seconds)
[INFO] Finished File Name Analyzer (0 seconds)
[INFO] Finished Jar Analyzer (0 seconds)
[INFO] Finished Central Analyzer (4 seconds)
[INFO] Finished Dependency Merging Analyzer (0 seconds)
[INFO] Finished Version Filter Analyzer (0 seconds)
[INFO] Finished Hint Analyzer (0 seconds)
[INFO] Created CPE Index (1 seconds)
[INFO] Finished CPE Analyzer (1 seconds)
[INFO] Finished False Positive Analyzer (0 seconds)
[INFO] Finished Cpe Suppression Analyzer (0 seconds)
[INFO] Finished NVD CVE Analyzer (0 seconds)
[INFO] Finished Vulnerability Suppression Analyzer (0 seconds)
[INFO] Finished Dependency Bundling Analyzer (0 seconds)
[INFO] Analysis Complete (8 seconds)
[WARNING]

One or more dependencies were identified with known vulnerabilities in sample-app:

commons-beanutils-1.8.3.jar (commons-beanutils:commons-beanutils:1.8.3, cpe:/a:apache:commons_beanutils:1.8.3) : CVE-2014-0114


See the dependency-check report for more details.


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 27.974 s
[INFO] Finished at: 2017-07-06T18:33:09+09:00
[INFO] Final Memory: 25M/899M
[INFO] ------------------------------------------------------------------------

今回の結果ではcommons-beanutilsの1.8.3 にCVE-2014-0114の脆弱性があることが指摘されています。


というわけで、簡単にJavaライブラリの脆弱性チェックができました。



関連エントリ

RxJava - RxJavaでHelloWorld

RxJavaを使いながらリアクティブプログラミングを理解していきたい。

とりあえずは、まず何を置いてもHello World!


準備

pom.xmlに以下の依存関係を追加します。Reactive Streamsにも対応しているバージョン2.Xを使います

<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxjava</artifactId>
    <version>2.0.8</version>
</dependency>


サンプルの実行

■サンプルコード

以下は、ほぼ「RxJavaリアクティブプログラミング (CodeZine BOOKS) 」からの写経です。

Hello, World!-Xの文字列を順に発行して、購読側のonNextで受け取れることを確認しました

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.schedulers.Schedulers;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import java.util.Arrays;
import java.util.List;

public class HelloWorldRx {

    public static void main(String[] args) throws Exception {
        // 発行するデータ
        final List<String> helloList = Arrays.asList("Hello, World!-1", "Hello, World!-2", "Hello, World!-3");

        Flowable<String> flowable = Flowable.create(new FlowableOnSubscribe<String>() {

            @Override
            public void subscribe(FlowableEmitter<String> emitter) throws Exception {
                helloList.forEach(s -> emitter.onNext(s));

                emitter.onComplete();
            }
        }, BackpressureStrategy.BUFFER);


        flowable.observeOn(Schedulers.computation())
            .subscribe(new Subscriber<String>() {

                private Subscription subscription;

                @Override
                public void onSubscribe(Subscription subscription) {
                    this.subscription = subscription;
                    this.subscription.request(1);
                }

                @Override
                public void onNext(String data) {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + " onNext -> " + data);

                    this.subscription.request(1);
                }

                @Override
                public void onComplete() {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + " onComplete");
                }

                @Override
                public void onError(Throwable throwable) {
                    throwable.printStackTrace();
                }
         });

        Thread.sleep(500);
    }
}

■実行結果

RxComputationThreadPool-1 onNext -> Hello, World!-1
RxComputationThreadPool-1 onNext -> Hello, World!-2
RxComputationThreadPool-1 onNext -> Hello, World!-3
RxComputationThreadPool-1 onComplete


もっとシンプルに試すだけなら以下のコードとかでもいいかもしれないですね

■サンプルコード

import io.reactivex.Flowable;

public class HelloWorldRx2 {

    public static void main(String[] args) throws Exception {
        Flowable<String> flowable =
                Flowable.just("Hello, World!-1", "Hello, World!-2", "Hello, World!-3");

        flowable.subscribe(data -> System.out.println(data));

        Thread.sleep(500);
    }
}

■実行結果

Hello, World!-1
Hello, World!-2
Hello, World!-3


とりあえず、RxJavaを使ったHello Worldができました



関連書籍

RxJavaリアクティブプログラミング (CodeZine BOOKS)

RxJavaリアクティブプログラミング (CodeZine BOOKS)

Go言語 - HTMLテンプレートの使い方

GolangでのHTMLテンプレート記述方法や値の展開方法等について学ぶために、html/templateを試してみました。


変数をそのまま展開

コード内の変数をhtmlのテンプレートに展開する例です

■htmlテンプレート(template000.html.tpl)

<!DOCTYPE html>
<html>
<body>
    msg: {{.}} 
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler0(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパース
    t := template.Must(template.ParseFiles("templates/template000.html.tpl"))

    str := "Sample Message"

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template000.html.tpl", str); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page0", htmlHandler0)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144443p:plain


mapの値を展開

mapをhtmlのテンプレートに展開する例です

■htmlテンプレート(template001.html.tpl)

<!DOCTYPE html>
<html>
<body>
    key1: {{.key1}}, key2: {{.key2}}, key3: {{.key3}} 
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler1(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパース
    t := template.Must(template.ParseFiles("templates/template001.html.tpl"))

    m := map[string]int{
        "key1": 101,
        "key2": 202,
        "key3": 303,
        "key4": -404,
    }

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template001.html.tpl", m); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page1", htmlHandler1)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144531p:plain


構造体を展開

構造体をhtmlのテンプレートに展開する例です

■htmlテンプレート(template002.html.tpl)

<!DOCTYPE html>
<html>
<body>
    Name: {{ .Name }}, Age: {{ .Age }} 
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler2(w http.ResponseWriter, r *http.Request) {

    t := template.Must(template.ParseFiles("templates/template002.html.tpl"))

    type SampleData struct {
        Name string
        Age  int
    }

    data := SampleData{Name: "Taro", Age: 25}

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template002.html.tpl", data); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page2", htmlHandler2)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144541p:plain


関数を実行

htmlのテンプレート内で関数呼び出しを行う例です

■htmlテンプレート(template003.html.tpl)

<!DOCTYPE html>
<html>
<body>
    Func1 -> {{ samplefunc1 }}
<br/>
    Func2 -> {{.msg1 | samplefunc2}}
<br/>
    Func3 -> {{.msg2 | samplefunc3}}
</body>
</html>

■サンプルコード

package trial

import (
    "fmt"
    "html/template"
    "log"
    "net/http"
    "strings"
    "time"
)

func htmlHandler3(w http.ResponseWriter, r *http.Request) {

    funcMap := template.FuncMap{
        "samplefunc1": func() string { return time.Now().String() },
        "samplefunc2": func(src string) string { return fmt.Sprintf("[$$ %s $$]", src) },
        "samplefunc3": strings.ToUpper,
    }
    // テンプレートをパース
    t := template.Must(template.New("t").Funcs(funcMap).ParseFiles("templates/template003.html.tpl"))

    m := map[string]string{
        "msg1": "golang is programming language.",
        "msg2": "hello template app.",
    }

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template003.html.tpl", m); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page3", htmlHandler3)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144551p:plain


ループ

htmlのテンプレート内でループを行う例です

■htmlテンプレート(template004.html.tpl)

<!DOCTYPE html>
<html>
<body>
    <div>
        <h4>DataList</h4>
        {{range .}}
        <p>{{.}}</p>
        {{end}}
    </div>
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler4(w http.ResponseWriter, r *http.Request) {

    t := template.Must(template.ParseFiles("templates/template004.html.tpl"))

    strArray := []string{"aa", "bbb", "ccc", "dddd"}

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template004.html.tpl", strArray); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page4", htmlHandler4)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144607p:plain


リクエストパラメータをテンプレートにセットして表示

htmlのテンプレート内で受け取ったリクエストパラメータを反映する例です

■htmlテンプレート(template005.html.tpl)

<!DOCTYPE html>
<html>
<body>
<div>
  Request Param1 -> {{.Param1 |safehtml}}
</div>
<div>
  Request Param2 -> {{.Param2 |safehtml}}
</div>
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler5(w http.ResponseWriter, r *http.Request) {
    funcMap := template.FuncMap{
        "safehtml": func(text string) template.HTML { return template.HTML(text) },
    }
    t := template.Must(template.New("T").Funcs(funcMap).ParseFiles("templates/template005.html.tpl"))

    st := struct {
        Param1 string
        Param2 string
    }{
        Param1: r.FormValue("param1"),
        Param2: r.FormValue("param2"),
    }

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template005.html.tpl", st); err != nil {
        log.Fatal(err)
    }
}

func LoadTemplate() {
    http.HandleFunc("/page5", htmlHandler5)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144616p:plain



関連エントリ

Lombok - @Builderでデフォルト値を指定する

Lombok@Builderアノテーションは何かと便利なのですが、プロパティへ値をセットするためのBuilderのメソッドを呼ばないと対応するフィールドが初期値(数値なら0, booleanならfalse, オブジェクトならnull)になってしまいます。

このあたりの動きをv1.16.16で増えた@Builder.Defaultで改善できるようになっています。


Builderで値をセットしなかった時の動きの確認

動作確認用にPerson1というクラスを用意して@Builderを付与します。
Person1をBuilderを通じて2回生成します。1回目は全プロパティをセットしますが2回目はnameプロパティに値をセットしません。

import lombok.Builder;
import lombok.Value;

@Builder
@Value
public class Person1 {

    private final long id;

    private final String name;

    private final String description;
}


■呼び出し側のコード

public final class Person1Client {

    public static void main(String[] args) {
        Person1 p1a = Person1.builder()
                .id(10L)
                .name("Taro")
                .description("name set person.")
                .build();

        System.out.println("p1a#toString: " + p1a);

        // nameメソッドを呼ばない
        Person1 p1b = Person1.builder()
                .id(20L)
                .description("name not set person.")
                .build();

        System.out.println("p1b#toString: " + p1b);
    }
}


■実行結果

結果としてPerson1生成の2回目の呼び出しではnameの値がnullになっています。

p1a#toString: Person1(id=10, name=Taro, description=name set person.)
p1b#toString: Person1(id=20, name=null, description=name not set person.)

対象オブジェクトの生成側が全プロパティをセットするのが必須ならいいですが、そうでないケースもあると思います。
そういう場合にはセットしなかったプロパティにはデフォルト値を入れておきたいところです。


デフォルト値をセットしたい時は

デフォルト値を入れておきたいという要望に応えるための方法として若干裏技的に以下リンク先のような方法が存在しています。

@Builderアノテーションによって生み出されるコードを逆手に取ったような手法になっています。


@Builder.Default によるデフォルト値セット

Lombokのv1.16.16から@Builderでのデフォルト値指定のための@Builder.Defaultが追加されました

デフォルト値をセットしたいフィールドに@Builder.Defaultアノテーションを付与して、フィールドにデフォルト値をセットします。

本サンプルではnameフィールドに "<UNKNOWN>"という値をデフォルト値としてセットします

import lombok.Builder;
import lombok.Value;

@Builder
@Value
public class Person2 {

    private final long id;

    @Builder.Default
    private final String name = "<UNKNOWN>";

    private final String description;
}


■呼び出し側のコード

public final class Person2Client {

    public static void main(String[] args) {
        Person2 p2a = Person2.builder()
                .id(10L)
                .name("Taro")
                .description("name set person.")
                .build();

        System.out.println("p1a#toString: " + p2a);


        // nameメソッドを呼ばない
        Person2 p2b = Person2.builder()
                .id(20L)
                .description("name not set person.")
                .build();

        System.out.println("p2b#toString: " + p2b);
    }
}


■実行結果

結果を見ると、Builderのnameメソッドを呼ばない場合はデフォルト値(<UNKNOWN>)がセットされていることが分かります

p1a#toString: Person2(id=10, name=Taro, description=name set person.)
p2b#toString: Person2(id=20, name=<UNKNOWN>, description=name not set person.)


補足

実は私がこのエントリ書いてる段階では、IntelliJのIDE上では、@Builder.Defaultを付与したフィールドへBuilderで値をセットするメソッドがコンパイルエラーになってしまいました。
ただし、ビルドや実行はできるので、Lombokのpluginが対応できていないだけなのかもしれません。

EclipseであればIDE上も何もエラー出ませんでした。



関連エントリ