20200318のJavaに関する記事は11件です。

CentOS7にjava(jdk14)をインストール

CentOS7にjava(jdk14)をインストールした手順を紹介します。
今回はrpmファイルを取得しインストールしました。

jdk1.4のダウンロード

以下のコマンドでjdk-14_linux-x64_bin.rpmをダウンロードします。
curl -OL -b "oraclelicense=accept-securebackup-cookie" "https://download.oracle.com/otn-pub/java/jdk/14+36/076bab302c7b4508975440c56f6cc26a/jdk-14_linux-x64_bin.rpm"

実行結果
[root@CENTOS7 ~]# curl -OL -b "oraclelicense=accept-securebackup-cookie" "https://download.oracle.com/otn-pub/java/jdk/14+36/076bab302c7b4508975440c56f6cc26a/jdk-14_linux-x64_bin.rpm"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100   527  100   527    0     0    774      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  165M  100  165M    0     0   232k      0  0:12:06  0:12:06 --:--:--  227k
[root@CENTOS7 ~]#

ダウンロードしたファイルのハッシュ値を確認

Java SE 14 Binaries Checksumjdk-14_linux-x64_bin.rpmのハッシュ値を確認します。
ハッシュ値はsha256: c470c99df36a33274832828475766da3e054e0a12db454c1d7c97ae909a55ecbとなっているので、以下のコマンドでダウンロードしたファイルのハッシュ値を取得し、一致していることを確認します。

sha256sum jdk-14_linux-x64_bin.rpm

実行結果
[root@CENTOS7 ~]# sha256sum jdk-14_linux-x64_bin.rpm
c470c99df36a33274832828475766da3e054e0a12db454c1d7c97ae909a55ecb  jdk-14_linux-x64_bin.rpm
[root@CENTOS7 ~]#

jdk14のインストール

以下のコマンドでダウンロードしたjdk-14_linux-x64_bin.rpmをインストールします。

rpm -ivh jdk-14_linux-x64_bin.rpm

実行結果
[root@CENTOS7 ~]# rpm -ivh jdk-14_linux-x64_bin.rpm
警告: jdk-14_linux-x64_bin.rpm: ヘッダー V3 RSA/SHA256 Signature、鍵 ID ec551f03: NOKEY
準備しています...              ################################# [100%]
更新中 / インストール中...
   1:jdk-14-2000:14-ga                ################################# [100%]
[root@CENTOS7 ~]#

jdk14インストールの確認

以下のコマンドでjdk14がインストールされたことを確認します。

java -version
which java

実行結果
[root@CENTOS7 ~]# java -version
java version "14" 2020-03-17
Java(TM) SE Runtime Environment (build 14+36-1461)
Java HotSpot(TM) 64-Bit Server VM (build 14+36-1461, mixed mode, sharing)
[root@CENTOS7 ~]# which java
/usr/bin/java
[root@CENTOS7 ~]#

参考

Installation of the JDK on Linux Platforms
Java SE 14 Binaries Checksum


以上

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

CentOS7にjava(Oracle JDK14)をインストール

CentOS7にjava(jdk14)をインストールした手順を紹介します。
今回はrpmファイルを取得しインストールしました。

Oracle JDK14 のダウンロード

以下のコマンドでjdk-14_linux-x64_bin.rpmをダウンロードします。
curl -OL -b "oraclelicense=accept-securebackup-cookie" "https://download.oracle.com/otn-pub/java/jdk/14+36/076bab302c7b4508975440c56f6cc26a/jdk-14_linux-x64_bin.rpm"

実行結果
[root@CENTOS7 ~]# curl -OL -b "oraclelicense=accept-securebackup-cookie" "https://download.oracle.com/otn-pub/java/jdk/14+36/076bab302c7b4508975440c56f6cc26a/jdk-14_linux-x64_bin.rpm"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100   527  100   527    0     0    774      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  165M  100  165M    0     0   232k      0  0:12:06  0:12:06 --:--:--  227k
[root@CENTOS7 ~]#

ダウンロードしたファイルのハッシュ値を確認

Java SE 14 Binaries Checksumjdk-14_linux-x64_bin.rpmのハッシュ値を確認します。
ハッシュ値はsha256: c470c99df36a33274832828475766da3e054e0a12db454c1d7c97ae909a55ecbとなっているので、以下のコマンドでダウンロードしたファイルのハッシュ値を取得し、一致していることを確認します。

sha256sum jdk-14_linux-x64_bin.rpm

実行結果
[root@CENTOS7 ~]# sha256sum jdk-14_linux-x64_bin.rpm
c470c99df36a33274832828475766da3e054e0a12db454c1d7c97ae909a55ecb  jdk-14_linux-x64_bin.rpm
[root@CENTOS7 ~]#

Oracle JDK14 のインストール

以下のコマンドでダウンロードしたjdk-14_linux-x64_bin.rpmをインストールします。

rpm -ivh jdk-14_linux-x64_bin.rpm

実行結果
[root@CENTOS7 ~]# rpm -ivh jdk-14_linux-x64_bin.rpm
警告: jdk-14_linux-x64_bin.rpm: ヘッダー V3 RSA/SHA256 Signature、鍵 ID ec551f03: NOKEY
準備しています...              ################################# [100%]
更新中 / インストール中...
   1:jdk-14-2000:14-ga                ################################# [100%]
[root@CENTOS7 ~]#

Oracle JDK14 インストールの確認

以下のコマンドでjdk14がインストールされたことを確認します。

java -version
which java

実行結果
[root@CENTOS7 ~]# java -version
java version "14" 2020-03-17
Java(TM) SE Runtime Environment (build 14+36-1461)
Java HotSpot(TM) 64-Bit Server VM (build 14+36-1461, mixed mode, sharing)
[root@CENTOS7 ~]# which java
/usr/bin/java
[root@CENTOS7 ~]#

参考

Installation of the JDK on Linux Platforms
Java SE 14 Binaries Checksum
Java SE Development Kit 14 - Downloads


以上

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

Java14が出たので、とりあえずrecordを試してみた

概要

Java14が出ました。変更点、新機能は色々ありますが、気になるのはやはりプレビューとして入ったrecordですよね?ね?というわけで早速試してみました。

注意点

recordはプレビューとして入った機能なので、使うにはおまじないが必要です。

java --enable-preview --source 14 RecordSample.java

また、ここに書いた内容は将来変更される可能性があります。(プレビューですからね)

内容

recordの基本的な使い方

recordを使うと、定義時に指定したフィールドを取得するメソッド(getter)が自動的に生成されます。

public class RecordSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);

        System.out.println(person.name());    // Mario
        System.out.println(person.age());     // 26
    }
}

// recordの定義
record Person(String name, int age) {}

recordの定義が非常に簡素で済むのがポイントですね。あと、recordを使う側のコードは従来のクラスと全く同じように書けます。
またgetterはgetName()とかではなく、フィールドと同じ名前のメソッドになります。setterは生成されないとのことです。setterも生成するかどうか選べたほうがいい気もしますが、まあ一般的にオブジェクトは不変のほうがいいですから、これはこれでいいのかもしれません。

また、recordを使うとObjectクラスで定義されているtoString()、equals()、hashCode()が自動的にオーバーライドされます。それぞれの例を見ていきます。

toStirng()の例

public class ToStringSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);

        System.out.println(person.toString());     // Person[name=Mario, age=26]
    }
}

// recordの定義
record Person(String name, int age) {}

いい感じですね。これを直接使うことはあまりないでしょうけど、デバッグ時に便利です。

equals()の例

public class EqualsSample {
    public static void main(String[] args) {
        var person1 = new Person("Mario", 26);
        var person2 = new Person("Mario", 26);

        System.out.println(person1.equals(person2));  // true

        person1 = new Person("Mario1", 26);
        person2 = new Person("Mario2", 26);

        System.out.println(person1.equals(person2));  // false

        person1 = new Person("Mario", 26);
        person2 = new Person("Mario", 27);

        System.out.println(person1.equals(person2));  // false
    }
}

// recordの定義
record Person(String name, int age) {}

同じ値を持つ別オブジェクトを比較してtrueとなる(一番上の例)ので、Objectクラスの元の挙動ではなく、ちゃんとフィールドの値を比較してくれていることがわかりますね。一応、特定のフィールドだけ値が違うのも試していますが、もちろんfalseになります。

hashCode()の例

public class HashCodeSample {
    public static void main(String[] args) {
        var person1 = new Person("Mario", 26);
        var person2 = new Person("Mario", 26);

        System.out.println(person1.hashCode());  // -1997440586
        System.out.println(person2.hashCode());  // -1997440586
    }
}

// recordの定義
record Person(String name, int age) {}

これも同じ値の別オブジェクトで同じ値になりました。ちゃんとオーバーライドされていますね。

コンストラクタの例

public class ConstructorSample {
    public static void main(String[] args) {
        new Person("Mario", -1);
        // Exception in thread "main" java.lang.IllegalArgumentException
        // at Person.<init>(ConstructorSample.java:11)
        // at ConstructorSample.main(ConstructorSample.java:3)
    }
}

// recordの定義
record Person(String name, int age) {
    public Person {
        if(age < 0) {
            throw new IllegalArgumentException();
        }
    }
}

コンストラクタも定義できるようです。カッコはつかないのですね。まあ引数を書くとなると上と全く同じことを書かないといけなくなるし、かといって引数なしでカッコがあると引数なしコンストラクタが存在するかのように見えてしまうし、ということでカッコなし表記になったのでしょう。良いと思います。
あと、アクセス修飾子はpublicじゃないとコンパイルエラーになりました。

別のフィールドやメソッドを定義する例

public class OtherMemberSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);
        System.out.println(person.getGender());  // male
    }
}

// recordの定義
record Person(String name, int age) {
    private static String gender = "male";

    public String getGender() {
        return gender;
    }
}

そんなことをしたいことがあるかどうかはわかりませんが、他のフィールドやメソッドを定義できるかやってみました。フィールドはstaticでないといけないようですが、一応できるようです。

自動的にオーバーライドされるメソッドを明示的に定義しちゃった例

public class ExplicitOverrideSample {
    public static void main(String[] args) {
        var person = new Person("Mario", 26);
        System.out.println(person.toString());      // overrided

        System.out.println(person.equals(person));  // false

        System.out.println(person.hashCode());      // -1
    }
}

// recordの定義
record Person(String name, int age) {
    @Override
    public String toString() {
        return "overrided";
    }

    @Override
    public boolean equals(Object o) {
        return false;
    }

    @Override
    public int hashCode() {
        return -1;
    }
}

これも誰がそんなことするんだよという例ですが、やってみました。どうやら明示的に書いた方が有効になるみたいですね。

感想

早く正式な機能になってください。

参考リンク

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

AppVeyorでMavenプロジェクトのCI

以下の記事で作ってきたMaven+Javaのプロジェクトを、CIで回そうと思って調査したので覚書です。
https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b

AppVeyor

こちらから登録できます。
https://www.appveyor.com/

特徴

基本的には、Windows上というか、VisualStudioでビルドすることが主目的のようです。

GithubやGitLab, Bitbucket以外のGitにも繋ぐことができます。
(※ただし、ちょっとした制約が発生します。後述)
また、SSHにGitで繋ぐ必要があったのですが、これもAppVeyorなら対応しています。

キャッシュにも対応しています。

OSイメージにはmacOS(Catalina, Majave)やUbuntuもありますが、Mavenビルド用のテンプレートとかそういったものがなく、いろいろと苦労しました。

料金

オープンソースなら無料。
それ以外は、基本的に月額課金です。
稼働が高そうで、でも期限が切られているプロジェクトならば、コスト見積もりがしやすいので良いかもしれません。チームメンバー数については特に言及がないので、制限はないかと思います。
https://www.appveyor.com/docs/team-setup/

実績

Windowsで使い勝手の良いテキストエディタといえばサクラエディタが有名ですが、AppVeyorを使われているようです。(Azure Pipelinesと併用のようです)

設定

基本的にには、プロジェクトのルート直下にappveyor.ymlというファイルを置くことでCIジョブを定義していきます。

1.MSビルド設定を変更する

まず最初にやることは、ビルドがMSビルド(VisualStudio向け)になっているので、ブラウザ上からその設定を解除する必要があります。

  • プロジェクトページを開き、[Settings]タブの[Build]を選ぶ
appveyor_msbuild_off.png
  • [MSBuild]の隣の、[Script]を選ぶ
appveyor_script_select.png
  • [Save]をクリック

これで準備が終了です。

2.appveyor.ymlを作成する

とりあえず、最終的にビルドとテストがうまく行ったappveyor.ymlはこちらです。

appveyor.yml
# Branches
branches:
  only:
  - feature/for_ci_test
image: ubuntu

install: 
- sh: sudo add-apt-repository --yes ppa:rpardini/adoptopenjdk
- sh: sudo apt-get update
- sh: sudo apt-get install -y adoptopenjdk-11-jdk-hotspot-installer
- sh: sudo apt install adoptopenjdk-11-jdk-hotspot-set-default
#/usr/lib/jvm/adoptopenjdk-11-jdk-hotspot
- sh: export JAVA_HOME=/usr/lib/jvm/adoptopenjdk-11-jdk-hotspot
- sh: echo $JAVA_HOME
- sh: export PATH=${PATH}

before_build:
- mvn -v

build_script:
- mvn clean package -DskipTests

test_script:
- mvn install verify

on_finish:
  - sh: |
      find "$APPVEYOR_BUILD_FOLDER" -type f -name 'TEST*.xml' -print0 | xargs -0 -I '{}' curl -F 'file=@{}' "https://ci.appveyor.com/api/testresults/junit/$APPVEYOR_JOB_ID"

artifacts:
  - path: "**/target/*.?ar"

version: "1.1.{build}"

以下のような内容になっています、

  • branchesセクション
    • feature/for_ci_testブランチでのみ実行
  • OS
    • ubuntu
  • installセクション
    • adoptopenjdk-11をインストールし、パスを設定
    • stack: jdk 11としてもJDK11を設定できるが、乗っているJDKのバージョンが古くバグが解消されていないため、別途インストールが必要でした
    • パスを無理やり設定しているのは、他のバージョンを切り替える方法がうまく行かなかったからです。
  • before_buildセクション
    • Mavenのバージョンを確認
  • build_scriptセクション
    • mvn ビルド(JUnitテストをスキップ)
  • test_scriptセクション
    • mvn コマンド(テストあり)
  • on_finishセクション
    • JUnitのテストレポートをレポートページにアップロード
  • artifactsセクション
    • *.jar*.warをアーティファクトとして保存
  • versionセクション
    • ビルドバージョンの設定

これを、プロジェクトのルート直下において、コミット、pushすると、CIが走り始めます。

キャッシュができるので、Javaのフォルダを対象にしておいてもいいかもしれませんね。
その場合、キャッシュの有無でjdkをインストールするかどうかを決めるようにshコマンド部分を書き換える必要があります。

Gitリポジトリサービス以外のGitに繋ぐ場合の注意

Githubリポジトリでは、上記のappveyor.ymlを置いたらそれを使ってビルドしてくれたのですが、GithubやGitLab,Bitbucketなどのリポジトリサービス以外のGitに繋ぐ場合(例えば、自社で立てているGitサーバーなど)は、プロジェクトに置いてあるappveyor.yml
は全く見てくれないようでした。

一応、同じような設定を、ブラウザ上から設定できるのですが、ページがまたがってあちこちで設定しなければならなかったり、かゆいところには手が届かなく、かなり手間でしたので、使用を検討する場合は注意が必要です。

感想

もとがVisualStudioでのビルド向けということで、やはりC#などのプロジェクト向けで、それ以外にはあまり注力されていない印象でした。
素直に、それらのテンプレートが豊富なCIサービスを検討したほうが良いと思いました。

それと、自分のサンプルプロジェクト(JerseySample)はビルド、テストともに通ったのですが、同じ環境で動く別のプロジェクトは、以下のような状態で、結局通すことができませんでした。

OSイメージ 現象
Windows上 Mavenビルド中にクラッシュするのかExit 1してしまう
macOS上 Mavenビルド中に応答しなくなるのかビルドが永遠に終わらない状態になってしまう
Ubuntu上 テストの途中でクラッシュするのかExit 1してしまう(※手元のUbuntu上ではビルドもテストも正常終了する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Bootでファイルアップロードした際ファイルサイズの最大値を超えたときにエラー処理を行う

Spring Bootでファイルをアップロードする際にファイルサイズの最大値を設定することができる。
設定するのはapplication.propertesの以下のパラメータ

spring.servlet.multipart.enabled=true
# ファイル1つの最大サイズ
spring.servlet.multipart.max-file-size=10MB
# 複数ファイル全体の最大サイズ
spring.servlet.multipart.max-request-size=50MB

ここで設定した以上のサイズのファイルをアップロードしようとするとエラーが表示されるが、このエラー処理をカスタマイズしたいときの例を以下に記述する。

1.HandlerExceptionResolverの子クラス作成
プロジェクトの任意の箇所に以下のクラスを作成する。これによりファイルサイズ異常の例外を取得することができる。

@Component
public class ExceptionResolver implements HandlerExceptionResolver {
  @Override
  public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
    ModelAndView modelAndView = new ModelAndView();
    if (e instanceof MultipartException && e.getCause() instanceof IllegalStateException && e.getCause().getCause() instanceof FileSizeLimitExceededException) {
      //表示したいメッセージなど
      modelAndView.addObject("message", "ファイルサイズ超過");
    }
    //遷移したい画面を指定
    modelAndView.setViewName("error");
    return modelAndView;
  }
}

2.内蔵TomcatのMaxSwallowSizeの設定
1.だけではうまく動作しないため内蔵TomcatのMaxSwallowSizeに-1を設定する。
以下のメソッドはmainメソッドのあるXXXXApplication.java内に記述する。

 @Bean
  public TomcatServletWebServerFactory containerFactory() {
    return new TomcatServletWebServerFactory() {
      protected void customizeConnector(Connector connector) {
        super.customizeConnector(connector);
        if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) {

          ((AbstractHttp11Protocol<?>) connector.getProtocolHandler()).setMaxSwallowSize(-1);
        }
      }
    };
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ボタンが急に効かなくなった

すみません。
mas masと申します。
初めての質問で緊張します。
登録ボタンを押下したら画面遷移しなくなった。

javascriptでonclickを使用する場合、javascriptは効きますがjavaが効かなくなりました。ctrl + F5で更新?をしたとたんに動作しなくなりました。
javascriptは有効になっています。

環境:
pl : java,javascript
ml : CSS,HTML
db : MySQL
OS : Windows10
FW : Struts

下記がソースコードになります。

jsp.Register.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean"%>

<html:html>
<head>
<title>Welcome Register</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="js/alert.js"></script>
</head>
<body>
    <h1>登録画面</h1>
    <html:form action="/Register">
        <%-- 入力項目 --%>
        <p>userid:</p>
        <html:text property="userid" />
        <br>
        <p>password:</p>
        <html:text property="password" />
        <br>
        <br>
        <p>name:</p>
        <html:text property="name" />
        <br>
        <br>
        <p>adress:</p>
        <html:text property="adress" value="" />
        <br>
        <br>
        <p>age:</p>
        <html:text property="age" />
        <br>

        <html:submit property="submit" value="登録" onclick="return clickBtn1();"/>
    </html:form>
    <a href="http://localhost:8022/SiteM/main.jsp">メイン画面へ戻る</a>
</body>
</html:html>
javascript.alert.js
function clickBtn1(){
    /*
     * jsの入力を取得する方法は、「struts-config.xml」.「property」.value
     * */
    var userid = RegistForm.userid.value;
    var password = RegistForm.password.value;
    var name = RegistForm.name.value;
    var adress = RegistForm.adress.value;
    var age = RegistForm.age.value;
    //入力空チェック
    if (userid == "" ){
        alert("userid入力してまへんで");
        return false;
    }else if(password == ""){
        alert("password入力してまへんで");
        return false;
    }else if(name == ""){
        alert("name入力してまへんで");
        return false;
    }else if(adress == ""){
        alert("adress入力してまへんで");
        return false;
    }else if(age == ""){
        alert("age入力してまへんで");
        return false;
    }else if(isNaN(age)){
        alert("数値じゃないで");
        return false
    }
    //メールチェック
    var mail_regex1 = new RegExp( '(?:[-!#-\'*+/-9=?A-Z^-~]+\.?(?:\.[-!#-\'*+/-9=?A-Z^-~]+)*|"(?:[!#-\[\]-~]|\\\\[\x09 -~])*")@[-!#-\'*+/-9=?A-Z^-~]+(?:\.[-!#-\'*+/-9=?A-Z^-~]+)*' );
    var mail_regex2 = new RegExp( '^[^\@]+\@[^\@]+$' );
    if( adress.match( mail_regex1 ) && adress.match( mail_regex2 ) ) {
        return false;
    } else {
        alert("メールアドレスの内容を確認の上\n入力して下さい。");
        return false;
    }
    return true;
}
function clickBtn(){
    /*
     * jsの入力を取得する方法は、「struts-config.xml」.「property」.value
     *
     *
     * */
    var userid = DeleteForm.userid.value;
    var password = DeleteForm.password.value;

    //入力空チェック
    if (userid == "" || password == ""){
        alert("空白やで");
        return false;
    }
    return true;
}

function clickBtn2(){
    /*
     * jsの入力を取得する方法は、「struts-config.xml」.「property」.value
     * */
    var userid = LoginForm.userid.value;
    var password = LoginForm.password.value;

    //入力空チェック
    if (userid == "" || password == ""){
        alert("空白やで");
        return false;
    }
    return true;
}
function clickBtn3(){
    /*
     * jsの入力を取得する方法は、「struts-config.xml」.「property」.value
     *
     *
     * */
    var aduser = AdminForm.aduser.value;
    var adpass = AdminForm.adpass.value;

    //入力空チェック
    if (aduser == "" || adpass == ""){
        alert("空白やで");
        return false;
    }
    return true;
}
function clickBtn4(){
    /*
     * jsの入力を取得する方法は、「struts-config.xml」.「property」.value
     *
     *
     * */
    var password = UpdateForm.password.value;
    var name = UpdateForm.name.value;
    var adress = UpdateForm.adress.value;
    var age = UpdateForm.age.value;
    //入力空チェック
    if(password == ""){
        alert("password入力してまへんで");
        return false;
    }else if(name == ""){
        alert("name入力してまへんで");
        return false;
    }else if(adress == ""){
        alert("adress入力してまへんで");
        return false;
    }else if(age == ""){
        alert("age入力してまへんで");
        return false;
    }
    //メールチェック
    var mail_regex1 = new RegExp( '(?:[-!#-\'*+/-9=?A-Z^-~]+\.?(?:\.[-!#-\'*+/-9=?A-Z^-~]+)*|"(?:[!#-\[\]-~]|\\\\[\x09 -~])*")@[-!#-\'*+/-9=?A-Z^-~]+(?:\.[-!#-\'*+/-9=?A-Z^-~]+)*' );
    var mail_regex2 = new RegExp( '^[^\@]+\@[^\@]+$' );
    if( adress.match( mail_regex1 ) && adress.match( mail_regex2 ) ) {
        // 全角チェック
        if( adress.match( /[^a-zA-Z0-9\!\"\#\$\%\&\'\(\)\=\~\|\-\^\\\@\[\;\:\]\,\.\/\\\<\>\?\_\`\{\+\*\} ]/ ) ) {
            return false;
        }

        // 末尾TLDチェック(〜.co,jpなどの末尾ミスチェック用)
        if( !mail.match( /\.[a-z]+$/ ) ) {
            return false;
        }

    } else {
        alert("メールアドレスの内容を確認の上\n入力して下さい。");
        return false;
    }
    return true;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MicroProfile Fault Tolerance について

マイクロサービスでサービスを構築する際、耐障害性を考慮して実装する事がとても重要です。 リトライ・ポリシー、バルクヘッド、サーキットブレーカーなどは、マイクロサービスのデザイン・パターンにも定義される、とても重要な概念です。

MicroProfile の Fault Tolerance はこうした、耐障害性のあるサービスを構築するために必要な機能を提供しています。実装は CDI によるアノテーション・ベースで容易に開発ができ、CDI のインターセプターを利用して動作しています (クラスは CDI の Bean として実装しなければなりません)。
これにより、ビジネス・ロジックと Fault Torerance 用の冗長的なコードを分離し、かんたんに実装できるようになっています。

MicroProfile の Fault Tolerance のポリシーは外部設定に外だしして管理することが可能になっており、MicroProfile Config を利用してポリシー管理を行うこともできます。

Fault Tolerance の仕様に含まれる主な機能

Fault Tolerance で提供する機能 使用するアノテーションと概要説明
1. タイムアウト: @Timeout アノテーションを利用。 処理に要する最大時間を定義します
2. リトライ: @Retryアノテーションを利用。 処理に失敗した際のリトライ(再試行)の動作を設定します
3. フォールバック: @Fallback アノテーションを利用。 処理に失敗した際の代替の方法を提供(別メソッドの呼び出し)します
4. バルクヘッド(隔壁): @Bulkhead アノテーションを利用。同時実行数を制限します。これにより、高負荷時に単一の処理に負荷が集中してレスポンスが低下し、これを起因としたシステム全体への連鎖的な障害を防ぎます
5. サーキット・ブレーカー: @CircuitBreaker アノテーションを利用。処理が繰り返して失敗する場合、その処理呼び出しを自動的に即時に失敗しるようにします
6. 非同期: @Asynchronous アノテーションを利用。 処理を非同期にします

基本的に、上記のいずれかのポリシーを適用したい場合(複数の指定も可)、実装するクラス、もしくはメソッドにアノテーションを付加するだけで設定できます。


1. タイムアウト (@Timeout) ポリシー

タイムアウトを設定する事により、処理の完了を待ち続けるのを防ぎます。 仮にタイムアウトを設定しない場合、ネットワーク障害や、接続先が高負荷でレスポンスをただちに返せないような場合、呼び出し元の接続プールのワーカー・スレッドが枯渇するなど、呼び出し元にも負荷をかけてしまいかねません。

そこで、複数のマイクロサービスを実装する際、もしくは外部サービスを呼び出すような場合、各サービス間の連携においてタイムアウトを設定します。

@Timeout(400) // 接続タイムアウト値 400ms (0.4 sec)
public Connection getConnectionForServiceA() {
   Connection conn = connectionService();
   return conn;
}

@Timeout のアノテーションはクラス、もしくはメソッドレベルで付加できます。タイムアウト値に達した場合、TimeoutException が送出されます。

2. リトライ (@Retry) ポリシー

軽いネットワーク障害や、接続先からの返信が返ってこないような場合、@Retry アノテーションを使用して、処理呼び出しを再試行できます。

リトライポリシーでは以下を構成できます。

パラメータ 説明
maxRetries: 最大のリトライ回数
delay: リトライ間隔
delayUnit: delay のユニット
maxDuration: 再試行を実行する最大期間
durationUnit: duration ユニット
jitter: 再試行遅延のランダムな変化 (クロック信号のタイミング(もしくは周期)のズレ)
jitterDelayUnit: jitter ユニット
retryOn: 再試行する失敗 (Exception, Error) を指定
abortOn: 中止する失敗 (Exception, Error) を指定

@Retry アノテーションはクラスレベル、もしくはメソッドレベルで付加可能で、クラスに付加した場合、クラス内に存在する全メソッドに適用されます。メソッドに付加した場合、指定したメソッドだけが対象になります。クラスでアノテーションを付加し、メソッドにも付加した場合は、メソッドで指定した設定が有効になります。

  1. 正常に処理が終了した場合、結果を正常に返します。
  2. 送出された例外を abortOn で指定した場合、スローされた例外を再送出します
  3. 送出された例外を retryOn で指定した場合、メソッド呼び出しが再試行されます
  4. それ以外の場合、送出された例外を再送出します

また、他の Fault Tolerance のアノテーションと共に併用できます。

    /**
     * serviceA() メソッド呼び出しで、例外が送出された場合に、
     * 例外が IOException でない場合は再試行します。
     */
    @Retry(retryOn = Exception.class, abortOn = IOException.class)
    public void invokeService() {
        callServiceA();
    }

    /**
     * 最大再試行回数は90、再試行を実行する最大期間は 1000 ミリ秒に設定
     * 再試行の最大期間に達すると、最大再試行回数に達していない場合でも、再試行は実行されない。
     */
    @Retry(maxRetries = 90, maxDuration= 1000)
    public void serviceB() {
        callServiceB();
    }

    /**
    * クロック周波数のズレ(jitter)を 400ms と仮定した場合、-400ms 〜 400ms つまり
    * 0 (delay - jitter) 〜 800ms (delay + jitter )の差で再試行が行われることが予想されます。
    * 最大遅延が発生した場合を想定し、3200/800=4 で、最低試行回数は4回以上、
    * 最大でも 10 回を超えない試行回数を設定します
    */
    @Retry(delay = 400, maxDuration= 3200, jitter= 400, maxRetries = 10)
    public Connection serviceA() {
        return getConnectionForServiceA();
    }

3. フォールバック (@Fallback) ポリシー

@Fallback アノテーションはメソッドレベルで指定できます。アノテーションが付加されたメソッドで例外が発生し終了した場合、フォールバックメソッドで指定したメソッドが呼び出されます。

@Fallback アノテーションは、単体もしくは他の Fault Tolerance アノテーションと一緒に使用できます。 他のアノテーションと併用した場合、フォールバックは、他のすべての Fault Tolerance 処理が行われた後に呼び出されます。

たとえば、@Retry が定義されている場合、リトライが最大試行回数を超えた場合にフォールバックの処理が実行されます。
また、@CircuitBreaker が共に定義されている場合、メソッド呼び出しが失敗した場合に直ちに呼び出されます。そしてサーキットがオープンしている場合は常に、フォールバック・メソッドが呼び出されます。

3.1 FallbackHandler の実装によるフォールバック処理の実装例

FallbackHandler インタフェースを実装した FallbackHandler のクラス (ServiceInvocationAFallbackHandler) を定義します。そして handle メソッド内で代替の処理を実装します。

ここでは、MicroProfile Config を利用して app.serviceinvokeA.FallbackReplyMessage のプロパティ、もしくは環境変数などで定義した文字列を返信するように実装しています。

@Dependent
public class ServiceInvocationAFallbackHandler implements FallbackHandler<String> {

    @ConfigProperty(name="app.serviceinvokeA.FallbackReplyMessage", defaultValue = "Unconfigured Default Reply")
    private String replyString;

    @Override
    public String handle(ExecutionContext ec) {
        return replyString;
    }
}

下記の、BusinessLogicServiceBean#invokeServiceA() メソッドが呼び出されると、ここでは内部的に RuntimeException が発生しますが 3 回処理の再試行します、全ての再試行に失敗したのち、ServiceInvocationAFallbackHandler#handle() が呼び出されます。

@RequestScoped
public class BusinessLogicServiceBean {

    // FallbackHandler の実装クラスを指定し @Fallback アノテーションを付加
    // 最大のリトライ回数 (3回)を超えた場合、FallbackHandler の handle() メソッドが呼ばれる
    @Retry(maxRetries = 3)
    @Fallback(ServiceInvocationAFallbackHandler.class)
    public String invokeServiceA() {
        throw new RuntimeException("Connection failed");
        return null;
    }   
}

3.2 fallbackMethod を指定したフォールバック処理の実装例

@Fallback アノテーション内で直接、代替で呼び出すメソッド名を記述します。

ここでは、fallbackForServiceB() メソッドを代替メソッドとして定義しています。

@RequestScoped
public class BusinessLogicServiceBean {

    @Retry(maxRetries = 3)
    @Fallback(fallbackMethod= "fallbackForServiceB")
    public String invokeServiceB() {
        counterForInvokingServiceB++;
       return nameService();
    }


    @ConfigProperty(name="app.serviceinvokeB.FallbackReplyMessage", defaultValue = "Unconfigured Default Reply")
    private String replyString;

    private String fallbackForInvokeServiceB() {
        return replyString;
    }

4. バルクヘッド (@Bulkhead) ポリシー

バルクヘッド・パターンは、システムの一部の障害がシステム全体に伝播し、システム全体がダウンするのを防ぐために利用します。MicroProfile の実装では、インスタンスにアクセスする同時リクエスト数を制限します。

Bulkheadパターンは、大量に呼び出される可能性のあるコンポーネントや、高負荷時にレスポンス低下を招くようなサービスに対して適用すると効果的です。

@Bulkhead アノテーションはクラスレベル、もしくはメソッドレベルで付加可能で、クラスに付加した場合、クラス内に存在する全メソッドに適用されます。メソッドに付加した場合、指定したメソッドだけが対象になります。クラスでアノテーションを付加し、メソッドにも付加した場合は、メソッドで指定した設定が有効になります。

バルクヘッドには下記の2種類の方法で設定可能です。

  1. スレッド・プールの分離 : (@Asynchronous アノテーションと併用した場合)
    スレッド・プール内の待機中のキューサイズで最大同時リクエスト数を設定します。
  2. セマフォの分離:(@Asynchronous アノテーションと併用しない場合) 同時リクエスト数の設定のみが許可されます。

4.1 スレッド・プールによる分離例

@Asynchronous アノテーションと併用した場合、スレッド・プールの分離が適用されます。下記の例では、最大で5つの同時リクエストが許可され、8つのリクエストが待機キューで保持されます。

// 最大5つの同時リクエストが許可され、最大 8つのリクエストが待機キューで許可される
@Asynchronous
@Bulkhead(value = 5, waitingTaskQueue = 8)
public Future<Connection> invokeServiceA() {
   Connection conn = null;
   counterForInvokingServiceA++;
   conn = connectionService();
   return CompletableFuture.completedFuture(conn);
}

4.2 セマフォによる分離例

@Asynchronous アノテーションを併用しない場合は、単に同時リクエスト数を定義します。

@Bulkhead(5) // 最大5つの同時要求が許可されます
public Connection invokeServiceA() {
   Connection conn = null;
   counterForInvokingServiceA++;
   conn = connectionService();
   return conn;
}

5. サーキット・ブレーカー (@CircuitBreaker) ポリシー

サーキット・ブレーカーは、障害のあるサービスに対して繰り返しの呼び出しを防いで、障害のあるサービスもしくは API 呼び出しで直ちに失敗するようにします。サービス呼び出しが頻繁に失敗する場合、サーキットブレーカーがオープンし、一定の時間が経過するまでそのサービスへの呼び出しは試行されません。

@CircuitBreaker アノテーションはクラスレベル、もしくはメソッドレベルで付加可能で、クラスに付加した場合、クラス内に存在する全メソッドに適用されます。メソッドに付加した場合、指定したメソッドだけが対象になります。クラスでアノテーションを付加し、メソッドにも付加した場合は、メソッドで指定した設定が有効になります。

サーキットブレーカーの3つの状態

クローズド: (通常時)

通常サーキットブレーカーは閉じています。サーキットブレーカーは、各呼び出しが成功したか失敗したかを記録しており最新の結果を追跡します。障害の割合が failureRatio を超えると、サーキット・ブレーカーがオープンします。

オープン: (障害発生時)

サーキットブレーカーが開いている場合、サーキットブレーカーで動作しているサービスへの呼び出しは、CircuitBreakerOpenException で直ちに失敗します。しばらくした後(設定可能)、サーキットブレーカーはハーフ・オープン状態に移行します。

ハーフ・オープン: (障害復旧の確認中)

ハーフオープン状態では、サービス呼び出しの試行が始まります(設定可能な数)。仮にいずれかの呼び出しで障害が発生した場合、再度サーキットブレーカはオープン状態に戻ります。すべての試行が成功した場合、サーキットブレーカーはクローズド状態に移行します。

サーキット・ブレーカの実装例1

@CircuitBreaker(successThreshold = 10, requestVolumeThreshold = 4, failureRatio=0.5, delay = 1000)
public Connection serviceA() {
   Connection conn = null;
   counterForInvokingServiceA++;
   conn = connectionService();
   return conn;
}
パラメータ 説明
requestVolumeThreshold: サーキットブレーカーが「クローズ」のときに使用するローリングウィンドウ(障害比率を計算するための分母の数)のサイズ
failureRatio: サーキットブレーカーを「オープン」にするための、ローリングウィンドウ内の障害比率
successThreshold: サーキットブレーカーが「ハーフ・オープン」の時、クローズドに移行するための試行回数
delayおよびdelayUnit: サーキットブレーカーを「オープン」にしつづける時間

上記では、requestVolumeThreshold で指定したローリングウィンドウ数である 4 回の連続した呼び出し中に 2 回(4 x 0.5)の障害が発生すると、サーキットが「オープン」します。 サーキットは 1,000 ミリ秒間「オープン」のままになり、その後「ハーフ・オープン」に移ります。 「ハーフ・オープン」で 10 回呼び出しが成功すると、サーキットは再び「クローズ」になります。

リクエスト1-成功
リクエスト2-失敗
リクエスト3-成功
リクエスト4-成功
リクエスト5-失敗
リクエスト6-CircuitBreakerOpenException

上記のリクエストの場合、最後の4つのリクエストのうち2つが失敗し、failureRatio が 0.5 に達するため、「リクエスト5」 でサーキットが 「オープン」 になり CircuitBreakerOpenException が送出されます。

成功/失敗とみなす例外定義を追加

failOn パラメーターと skipOn パラメーターは、サーキットブレーカーを「オープン」にするか否かを決定するため、どの例外を失敗と見なすかを定義するために使用します。

@CircuitBreaker(successThreshold = 10, requestVolumeThreshold = 4, failureRatio=0.5, delay = 1000,failOn = {ExceptionA.class, ExceptionB.class}, skipOn = ExceptionBSub.class))
public Connection serviceA() {
   Connection conn = null;
   counterForInvokingServiceA++;
   conn = connectionService();
   return conn;
}

failOn に指定した例外が発生した場合は、失敗とみなします
skipOn に指定した例外が発生した場合は、成功とみなします

6. 非同期 (@Asynchronous) ポリシー

Fault Tolerance の主な機能は Architecture に記述されているように、上記 1-5 までにあげた機能です。そこで、非同期処理は直接 Fault Tolerance と関連するわけではありません。しかし、分散処理において非同期処理はとても重要で Fault Tolerance の各種機能と組み合わせる事により、より有効的に働くため仕様内に取り込まれました。

引用元:Architecture

As mentioned above, the Fault Tolerance specification is to focus on the following aspects:

  • Timeout: Define a duration for timeout
  • Retry: Define a criteria on when to retry
  • Fallback: provide an alternative solution for a failed execution.
  • CircuitBreaker: offer a way of fail fast by automatically failing execution to prevent the system overloading and indefinite wait or timeout by the clients.
  • Bulkhead: isolate failures in part of the system while the rest part of the system can still function.

@Asynchronousアノテーションはクラスレベル、もしくはメソッドレベルで付加可能で、クラスに付加した場合、クラス内に存在する全メソッドに適用されます。メソッドに付加した場合、指定したメソッドだけが対象になります。クラスでアノテーションを付加し、メソッドにも付加した場合は、メソッドで指定した設定が有効になります。

@Asynchronous アノテーションが付加されたメソッドが呼び出されると、すぐに Future もしくは CompletionStage を返します。残りのメソッド本体の処理は別スレッドで実行されます。非同期処理が完了するまで、返却された Future もしくは CompletionStage は正しい値を持ちません。仮に処理中に例外が発生した場合は、Future または CompletionStage はその例外で終了します。
処理が正常に完了した場合、Future もしくは CompletionStage は戻り値(それ自体がFutureまたはCompletionStage)を返します。

@Asynchronous
public CompletionStage <Connection> serviceA(){
   Connection conn = null;
   counterForInvokingServiceA ++;
   conn = connectionService();
   return CompletableFuture.completedFutureconn;
}

上記の例では、serviceA メソッドへの呼び出しが非同期処理になります。serviceA の呼び出しはCompletionStage を返し、メソッド本体の実行は別スレッドで実行されます。

注意:

CDI の RequestScope から @Asynchronous を呼び出す場合、非同期メソッド呼び出し中 RequestScope がアクティブでなければなりません。@Asynchronousアノテーションが付加されたメソッドは、java.util.concurrentパッケージの Future もしくは CompletionStage を返す必要があります。そうでない場合、FaultToleranceDefinitionExceptionが発生します。

ソースコードに記載した設定値の上書き方法

各節で確認したように、Fault Tolerance のポリシーは一部を除いてほとんどの場合、アノテーションを使用して適用できます。
ソースコードの実装後、仮にアノテーションで実装した値を変更したい場合は、MicroProfile Config を使用して設定値を上書きすることもできます。

アノテーション内のパラメーターは、次の命名規則を使用して、設定プロパティーで上書きできます:

<classname>/<methodname>/<annotation>/<parameter>

たとえば、ある特定のメソッドで指定した Timeout や Retry のアノテーションで指定したパラメータを外部で上書き設定したい場合、MicroProfile Config で下記のように記述します。

com.yoshio3.FaultToleranceService.resilient.ResilienceController/checkTimeout/Timeout/value=2000
com.yoshio3.FaultToleranceService.resilient.ResilienceController/checkTimeout/Retry/maxDuration=3000

仮に、クラス全体に適用したい場合は下記のように、メソッド名の部分を削除してクラス全体に適用することもできます。

com.yoshio3.FaultToleranceService.resilient.ResilienceController/Timeout/value=2000
com.yoshio3.FaultToleranceService.resilient.ResilienceController/Retry/maxDuration=3000

そして、プロジェクト内の全コードに対して同一ルールを適用したい場合は、アノテーションとパラメータ設定だけを記載することもできます。

Timeout/value=2000
Retry/maxDuration=3000

さいごに

ここでは、MicroProfile Fault Tolerance を利用して耐障害性を高めるアプリケーションを構築するためのコードを確認しました。次は、Fault Tolerance を利用したアプリケーションを実際に構築し、Azure 上で耐障害性を持つ複数のサービスを連携をしてみたいと思います。

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

Azure Web App for Containers 環境で MicroProfile Config 動作検証

1. Azure Web App for Containers 環境で MicroProfile Config 動作検証

アプリケーションは、外部システムとの連携のための接続情報 (DB や、外部の HTTP エンドポイント)や、開発環境、テスト環境、本番環境などの環境設定の差を、プログラム・ソースコードから切り離し、外部の設定ファイル等に書き出すことで容易に接続先や設定を切り替えることができます。
また、外部の設定ファイルなどに書き出しておくことで、接続先を切り替えるためにアプリケーションのソースコードの編集やビルドは不要で、同一のソースコードや実行ライブラリを利用できます。

クラウド・ネィティブなアプリケーションを構築していくために、設定情報の外だしはとても重要です。
参照:The Twelve FactorsIII. 設定 : 設定を環境変数に格納する」

Twelve-Factorは 設定をコードから厳密に分離すること を要求する。

MicroProfile Config を利用すると、設定情報を下記のようなさまざまな場所から取得できます。
これらの設定場所を ConfigSources と呼び、 同じプロパティが複数の ConfigSource で定義されている場合、ポリシーを適用しどの値が有効かを指定します。

  • Java VM のシステム ・プロパティから
  • OS の環境変数
  • 外部構成ファイル (.properties, .xml)から
  • LDAP, DB, Key-Value ストア などの外部データそ=す

また状況によっては、一部のデータソースを動的に切り替えたい場合があります。そして変更した値は、アプリケーションを再起動することなく、プログラム上から更新した内容を利用する必要があります。こうしたニーズに応えるため、MicroProfile Config では、構成した値を変更直後から利用できるようになっています。

MicroProfile Config の実装について

Microprofile Config は API のみを規定しており実装は含まれていません。
MicroProfile Config の実装は、各 MicroProfile の実装プロバイダから個別に提供されています。

MicroProfile Config の概要

MicroProfile Config は数少ない API から構成されています。

MicroProfile Config API 1.4 の一覧

ConfigSource の優先順位

Configは、登録されたorg.eclipse.microprofile.config.spi.ConfigSourceから収集された情報で構成されます。 これらのConfigSourceは、順序に従ってソートされます。 これにより、外部から重要度の低い設定を上書きできます。

デフォルトでは、3つのデフォルトConfigSourceがあります。

  • System.getProperties()(優先順位 = 400)
  • System.getenv()(優先順位= 300)
  • ClassPath上の META-INF/microprofile-config.properties ファイル(デフォルト優先順位 = 100、各ファイル中に config_ordinal プロパティを設定して個別に優先順位を設定可能)

デフォルト値は、アプリケーションのパッケージ時にファイル内で指定でき、値はデプロイメントごとに後から上書きできます。 「優先順位は値が大きいほど優先されます。」

設定情報の取得例

MicroProfile Config 仕様では、設定値を読み取るために 2種類の方法を用意しています。

  • プログラム的な設定情報の取得
  • アノテーションを利用した設定情報の取得

1. プログラム的な設定情報の取得

プログラム的に Config インスタンスを取得し設定情報を取得するサンプルを下記に示します。

public class  MyAppWithGetConfigFromProgram {

    public Response invokeMicroserviceWithConfig() {
        // Config インスタンスの取得
        Config config = ConfigProvider.getConfig();
        // マイクロサービス A の URL を取得
        String microserviceA = config.getValue("URL_OF_MICROSERVICE_A", String.class);
        // マイクロサービス A の呼び出し
        return invokeMicroservice(microserviceA);
    }
}

設定情報を取得するためには、最初に Config インスタンスを取得しなければなりません。
プログラム的に Config インスタンスを取得するためには、ConfigProvider#getConfig() を呼び出して取得できます。

(Config クラスのインスタンスは、生成されたのちコンテキストクラスローダーに登録されます。)

2. アノテーションを利用した設定情報の取得 (推奨)

アノテーションを利用し Config インスタンスを取得し、@ConfigProperty で設定情報を取得するサンプルを下記に示します。

@ApplicationScoped
public class MyAppWithGetConfigFromAnnotation {

    @Inject
    private Config config;

    //The property myprj.some.url must exist in one of the configsources, otherwise a
    //DeploymentException will be thrown.
    @Inject
    @ConfigProperty(name="myprj.some.url")
    private String someUrl;

    //The following code injects an Optional value of myprj.some.port property.
    //Contrary to natively injecting the configured value, this will not lead to a
    //DeploymentException if the value is missing.
    @Inject
    @ConfigProperty(name="myprj.some.port")
    private Optional<Integer> somePort;
}

MicroProfile Config サンプル・アプリケーション

1. MicroProfile Config サンプル・プロジェクトの作成

MicroProfile Starter にアクセスし、MicroProfile のプロジェクトを作成します。

(DOWNLOAD) のリンクを押下すると MPConfigSample.zip ファイルがダウンロードできます。ファイルを展開すると下記のようなファイル・ディレクトリ構成が自動的に生成されています。

.
├── pom.xml
├── readme.md
└── src
    └── main
        ├── java
        │   └── com
        │       └── yoshio3
        │           └── MPConfigSample
        │               ├── HelloController.java
        │               ├── MPConfigSampleRestApplication.java
        │               └── config
        │                   └── ConfigTestController.java
        ├── resources
        │   └── META-INF
        │       └── microprofile-config.properties
        └── webapp
            ├── WEB-INF
            │   └── beans.xml
            └── index.html
11 directories, 8 files

そして、MicroProfile Config のサンプルコードが ConfigTestController.java に下記のように記載されています。

package com.yoshio3.MPConfigSample.config;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/config")
@RequestScoped
public class ConfigTestController {

    @Inject
    @ConfigProperty(name = "injected.value")
    private String injectedValue;

    @Path("/injected")
    @GET
    public String getInjectedConfigValue() {
        return "Config value as Injected by CDI " + injectedValue;
    }

    @Path("/lookup")
    @GET
    public String getLookupConfigValue() {
        Config config = ConfigProvider.getConfig();
        String value = config.getValue("value", String.class);
        return "Config value from ConfigProvider " + value;
    }
}

上記のコードでは、プロパティに記載された値を HTTP のレスポンスとして返す簡単なコードです。

下記のように HTTP の GET メソッドで呼び出すと return 文で記載される文字列が返ってきます。

$ curl -X GET http://localhost:8080/data/config/injected
$ curl -X GET http://localhost:8080/data/config/lookup

実際の設定内容は、META_INF ディレクトリ配下の microprofile-config.properties ファイルに記載されています。

# プロパティ・ファイルの場所
└── src
    └── main
        ├── resources
        │   └── META-INF
        │       └── microprofile-config.properties

デフォルトで下記のプロパティが設定されています。

# プロパティ・ファイルに設定された値
injected.value=Injected value
value=lookup value

2. サンプル・プロジェクトのビルドと実行

MicroProfile Config の動作確認を行うため、プロジェクトをビルドし、アプリケーションを起動します。

# プロジェクトのビルド
$ mvn clean package

# アプリケーションの実行
$ java -jar target/MPConfigSample-microbundle.jar 

......
Payara Micro URLs:
http://192.168.100.7:8080/

'ROOT' REST Endpoints:
GET     /data/application.wadl
GET     /data/config/injected
GET     /data/config/lookup
GET     /data/hello
GET     /openapi/
GET     /openapi/application.wadl
]]
[2020-03-10T22:19:06.610+0900] [] [情報] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583846346610] [levelValue: 800] Payara Micro  5.194 #badassmicrofish (build 327) ready in 32,755 (ms)
[2020-03-10T22:19:33.646+0900] [] [情報] [] [javax.enterprise.system.container.web.com.sun.web.security] [tid: _ThreadID=29 _ThreadName=http-thread-pool::http-listener(1)] [timeMillis: 1583846373646] [levelValue: 800] Context path from ServletContext:  differs from path from bundle: /

上記のようにアプリケーションが起動したのち、curl コマンドを実行し動作確認を行います。
正しく動作している場合、下記のようにプロパティ・ファイルから取得した設定 (Injected value, value) の文字列が表示されます。

# アノテーションで実装されたエンドポイントへの呼び出し
$ curl localhost:8080/data/config/injected
Config value as Injected by CDI Injected value

# プログラムで実装されたエンドポイントへの呼び出し
$ curl localhost:8080/data/config/lookup
Config value from ConfigProvider lookup value

MicroProfile ではプロパティ・ファイルの設定値をシステム・プロパティで上書き設定することができます。そこで環境変数を設定し、環境変数の値を Java のシステム・プロパティに代入して実行します。すると "microprofile-config.properties" ファイルに設定した値を上書きし、環境変数に設定した値が表示されている事を確認できます。 

# 環境変数の設定 [.(ドット)を _(アンダーバー)に置き換えて設定]
$ export injected_value="Environment Value"

# 環境変数を Java のシステム・プロパティに設定してアプリを実行
$ java -D"$injected_value" -jar target/MPConfigSample-microbundle.jar

# アプリケーションの動作確認
$ curl http://localhost:8080/data/config/injected
Config value as Injected by CDI Environment Value

ご注意: properties ファイル中では . (ドット)表記で記載していますが、環境変数は OS によっては . (ドット)表記が使えません。そこで、環境変数の設定では . (ドット)表記箇所を _ (アンダーバー)に置き換えて設定してください。実装内部で自動的に変換をしています。

3. ローカルの Docker 環境での実行

ローカルの環境でアプリケーションの動作確認ができたので、次にローカルの Docker 環境で MicroProfile を動作させます。Payara Micro の Docker コンテナのイメージを作成するため、下記のような Dockerfile を作成してください。

FROM payara/micro:5.201

USER payara
WORKDIR ${PAYARA_HOME}

# Deploy Artifact
COPY ./target/MPConfigSample.war $DEPLOY_DIR

CMD ["--nocluster","--deploymentDir", "/opt/payara/deployments", "--contextroot", "app"]

次に、この Dockerfile を利用してコンテナのイメージを作成します。docker build コマンドを実行しコンテナのイメージを作成してください。

$ docker build -t tyoshio2002/payara-config-sample:1.0 .

# コマンド実行時のコンソール出力例
Sending build context to Docker daemon  151.2MB
Step 1/5 : FROM payara/micro:5.201
5.201: Pulling from payara/micro
050382585609: Already exists 
59f5185426ac: Already exists 
4d95208cd9c0: Pull complete 
c1409397cf71: Pull complete 
Digest: sha256:3ff92627d0d9b67454ee241cc7d5f2e485e46db81a886c87cf16035df7c80cc8
Status: Downloaded newer image for payara/micro:5.201
 ---> a11a548b0a25
Step 2/5 : USER payara
 ---> Running in cb755e484e79
Removing intermediate container cb755e484e79
 ---> 564283252ae4
Step 3/5 : WORKDIR ${PAYARA_HOME}
 ---> Running in f26dd5cd172c
Removing intermediate container f26dd5cd172c
 ---> f2bf88b18a77
Step 4/5 : COPY ./target/MPConfigSample.war $DEPLOY_DIR
 ---> 1b54373fe95a
Step 5/5 : CMD ["--nocluster","--deploymentDir", "/opt/payara/deployments", "--contextroot", "app"]
 ---> Running in 3eb731eb77c3
Removing intermediate container 3eb731eb77c3
 ---> 1d11549e99b8
Successfully built 1d11549e99b8
Successfully tagged tyoshio2002/payara-config-sample:1.0

コンテナのイメージが作成できたのち、コンテナを起動します。下記のコマンドを実行してコンテナを起動してください。

$ docker run -p 8080:8080 -e injected_value=hogehoge -it tyoshio2002/payara-config-sample:1.0

# コマンド実行時のコンソール出力例
..... (中略)
[2020-03-11T07:46:59.119+0000] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583912819119] [levelValue: 800] [[ 
{
    "Instance Configuration": {
        "Host": "3877abb54d57",
        "Http Port(s)": "8080",
        "Https Port(s)": "",
        "Instance Name": "payara-micro",
        "Instance Group": "no-cluster",
        "Deployed": [
            {
                "Name": "MPConfigSample",
                "Type": "war",
                "Context Root": "/app"
            }
        ]
    }
}]]
[2020-03-11T07:46:59.131+0000] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583912819131] [levelValue: 800] [[
Payara Micro URLs:
http://3877abb54d57:8080/app
'MPConfigSample' REST Endpoints:
GET /app/data/application.wadl
GET /app/data/config/injected
GET /app/data/config/lookup
GET /app/data/hello
]]
[2020-03-11T07:46:59.131+0000] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583912819131] [levelValue: 800] Payara Micro  5.201 #badassmicrofish (build 512) ready in 31,286 (ms)

作成したコンテナの起動が完了したので、コンテナ上で動作するアプリケーションに対して接続をします。

今回は、起動時にコンテナ内部の 8080番ポートを、ローカルの 8080番ポートにマッピングしていますので、ローカル環境の 8080 番ポートにアクセスすることで、コンテナのアプリケーションに接続できます。
下記のコマンドを実行してください。

$ curl http://localhost:8080/app/data/config/injected
Config value as Injected by CDI hogehoge

コンテナの起動時に引数として環境変数 (-e injected_value=hogehoge) を与えているため、起動時に入力した文字列が表示されます。

4. Azure Web App for Containers 環境での実行

ローカルの Docker 環境で動作確認ができたので、Web App for Containers 環境で動作確認を行います。下記の手順に従い動作確認を行います。

  1. Azure Container Registry 用のリソース・グループを作成
  2. Azure Container Registry を作成
  3. Azure Container Registry のパスワードの確認
  4. Azure Container Registry にログインしイメージを Push
  5. Azure Container Registry に Push したイメージの確認
  6. Web App for Containers 用のリソース・グループを作成
  7. Web App for Containers 用の AppService プランの作成
  8. コンテナ・イメージを指定し Web App for Containers を作成
  9. デプロイしたアプリケーションの動作確認
  10. Web App for Containers のアプリケーション設定の追加
  11. 設定変更後のアプリケーションの動作確認

4.1. Azure Container Registry 用のリソース・グループを作成

まずは、Azure Container Registry を作成し、ローカルで作成した Docker コンテナのイメージをアップロードします。そこで、Azure Container Registry を作成するためのリソース・グループを作成します。

$ az group create --name WebApp-Containers --location "Japan East"
{
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp-Containers",
  "location": "japaneast",
  "managedBy": null,
  "name": "WebApp-Containers",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}

4.2. Azure Container Registry を作成

次に、Azure Container Registry を作成します。

  • --name にコンテナ・レジストリ名を指定します
  • --resource-group に上記で作成したリソース・グループ名を指定します
  • --sku は "Basic", "Standard", "Premium" の何れかを指定します
  • --admin-enabled true に設定する事で、コンテナ・レジストリに docker コマンドでアクセスできるようにします
$ az acr create --name containerreg4yoshio --resource-group WebApp-Containers --sku Basic --admin-enabled true
{
  "adminUserEnabled": true,
  "creationDate": "2020-03-12T02:27:59.357654+00:00",
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp-Containers/providers/Microsoft.ContainerRegistry/registries/containerreg4yoshio",
  "location": "japaneast",
  "loginServer": "containerreg4yoshio.azurecr.io",
  "name": "containerreg4yoshio",
  "networkRuleSet": null,
  "policies": {
    "quarantinePolicy": {
      "status": "disabled"
    },
    "retentionPolicy": {
      "days": 7,
      "lastUpdatedTime": "2020-03-12T02:28:01.654662+00:00",
      "status": "disabled"
    },
    "trustPolicy": {
      "status": "disabled",
      "type": "Notary"
    }
  },
  "provisioningState": "Succeeded",
  "resourceGroup": "WebApp-Containers",
  "sku": {
    "name": "Basic",
    "tier": "Basic"
  },
  "status": null,
  "storageAccount": null,
  "tags": {},
  "type": "Microsoft.ContainerRegistry/registries"
}

4.3. Azure Container Registry のパスワードの確認

次に、Azure Container Registry に接続するためのパスワードを確認します。

  • --name にコンテナ・レジストリ名を指定します
  • --resource-group に上記で作成したリソース・グループ名を指定します
$ az acr credential show --name containerreg4yoshio --resource-group WebApp-Containers
{
  "passwords": [
    {
      "name": "password",
      "value": "4zaIiLk*************+H1XO4AlYFvN"
    },
    {
      "name": "password2",
      "value": "fT03XPs*************Oq2cAZiVHV+L"
    }
  ],
  "username": "containerreg4yoshio"
}

4.4. Azure Container Registry にログインしイメージを Push

次に、docker login コマンドを実行し Azure Container Registry に接続します。

(パスワードは上記で取得したパスワードを入力してください。)

ログインが完了すると、docker tag コマンドでイメージのタグ付けを行います。ローカルで作成した Docker コンテナのイメージ名に、コンテナ・レジストリの "loginServer" 名 ( 例:"containerreg4yoshio.azurecr.io") を付け加えた名前でタグ付けします。

最後に、docker push コマンドを実行し、Azure Container Registry にイメージを Push します。

# Azure Container Registry にログイン
$ docker login containerreg4yoshio.azurecr.io -u containerreg4yoshio
Password: 
Login Succeeded

# Docker コンテナのタグ付け
$ docker tag tyoshio2002/payara-config-sample:1.0 containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample:1.0

# Azure Container Registry にタグ付けしたイメージを Push
$ docker push containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample:1.0

The push refers to repository [containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample]
bbd197848553: Pushed 
ec40a5d738cc: Pushed 
f95fe3528c56: Pushed 
bded2364df91: Pushed 
1bfeebd65323: Pushed 
1.0: digest: sha256:689dbacc212d37afe09c43417bc79d8e241c3fa7b5cf71c27097ef535cf77f76 size: 1368

4.5. Azure Container Registry に Push したイメージの確認

Azure Container Registry に正しくイメージが Push されていることを確認します。

$ az acr repository list -n containerreg4yoshio -g WebApp-Containers
Argument 'resource_group_name' has been deprecated and will be removed in a future release.
[
  "tyoshio2002/payara-config-sample"
]

4.6. Web App for Containers 用のリソース・グループを作成

Azure Conginer Registry を作成したので、次に Web App for Containers を作成します。まず、Web App for Containers を作成するリソース・グループを作成します。

$ az group create --name WebApp --location "Japan East"
{
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp",
  "location": "japaneast",
  "managedBy": null,
  "name": "WebApp",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}

4.7. Web App for Containers 用の AppService プランの作成

次に、Linux 用の AppService プランを作成します。今回は検証環境での動作確認のため、SKU は B1 を利用していますが、環境に応じて適宜、ご選択ください。

  • --name にAppService プラン名を指定します
  • --resource-group に上記で作成したリソース・グループ名を指定します
  • --sku に F1, B1, P1V2, P2V2, P3V2, I1, I2, I3 など稼働させるマシン・価格など適切な SKU (Stock Keeping Unit)を指定します
  • --is-linux は Linux 環境での構築を指定します。
$ az appservice plan create --name webapp4container --resource-group WebApp --sku B1 --is-linux
{
  "freeOfferExpirationTime": "2020-04-11T02:38:56.873333",
  "geoRegion": "Japan East",
  "hostingEnvironmentProfile": null,
  "hyperV": false,
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp/providers/Microsoft.Web/serverfarms/webapp4container",
  "isSpot": false,
  "isXenon": false,
  "kind": "linux",
  "location": "Japan East",
  "maximumElasticWorkerCount": 1,
  "maximumNumberOfWorkers": 3,
  "name": "webapp4container",
  "numberOfSites": 0,
  "perSiteScaling": false,
  "provisioningState": "Succeeded",
  "reserved": true,
  "resourceGroup": "WebApp",
  "sku": {
    "capabilities": null,
    "capacity": 1,
    "family": "B",
    "locations": null,
    "name": "B1",
    "size": "B1",
    "skuCapacity": null,
    "tier": "Basic"
  },
  "spotExpirationTime": null,
  "status": "Ready",
  "subscription": "f77aafe8-****-****-****-d0c37687ef70",
  "tags": null,
  "targetWorkerCount": 0,
  "targetWorkerSizeId": 0,
  "type": "Microsoft.Web/serverfarms",
  "workerTierName": null
}

4.8. コンテナ・イメージを指定し Web App for Containers を作成

次に、Azure Container Registry に Push したイメージを利用して Web App for Containers を作成します。

  • --name に Web App for Containers の名前を指定します
  • --resource-group に上記で作成したリソース・グループ名を指定します
  • --plan に上記で作成した AppService プラン名を指定します
  • --deployment-container-image-name に Azure Container Registry に Push したイメージ名を指定します。
$ az webapp create --resource-group WebApp \ 
                               --plan webapp4container \
                               --name yoshiowebapp \
                               --deployment-container-image-name containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample:1.0

No credential was provided to access Azure Container Registry. Trying to look up...
{
  "availabilityState": "Normal",
  "clientAffinityEnabled": true,
  "clientCertEnabled": false,
  "clientCertExclusionPaths": null,
  "cloningInfo": null,
  "containerSize": 0,
  "dailyMemoryTimeQuota": 0,
  "defaultHostName": "yoshiowebapp.azurewebsites.net",
  "enabled": true,
  "enabledHostNames": [
    "yoshiowebapp.azurewebsites.net",
    "yoshiowebapp.scm.azurewebsites.net"
  ],
  "ftpPublishingUrl": "ftp://waws-prod-ty1-***.ftp.azurewebsites.windows.net/site/wwwroot",
  "geoDistributions": null,
  "hostNameSslStates": [
    {
      "hostType": "Standard",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "yoshiowebapp.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    },
    {
      "hostType": "Repository",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "yoshiowebapp.scm.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    }
  ],
  "hostNames": [
    "yoshiowebapp.azurewebsites.net"
  ],
  "hostNamesDisabled": false,
  "hostingEnvironmentProfile": null,
  "httpsOnly": false,
  "hyperV": false,
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp/providers/Microsoft.Web/sites/yoshiowebapp",
  "identity": null,
  "inProgressOperationId": null,
  "isDefaultContainer": null,
  "isXenon": false,
  "kind": "app,linux,container",
  "lastModifiedTimeUtc": "2020-03-12T02:39:50.356666",
  "location": "Japan East",
  "maxNumberOfWorkers": null,
  "name": "yoshiowebapp",
  "outboundIpAddresses": "13.**.***.96,13.**.**.49,13.**.**.66,13.**.**.140,13.**.**.186",
  "possibleOutboundIpAddresses": "13.**.**.96,13.**.**.49,13.**.**.66,13.**.**.140,13.**.**.186,13.**.**.30,13.**.**.70,13.**.**.101,13.**.**.163,13.**.**.200",
  "redundancyMode": "None",
  "repositorySiteName": "yoshiowebapp",
  "reserved": true,
  "resourceGroup": "WebApp",
  "scmSiteAlsoStopped": false,
  "serverFarmId": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp/providers/Microsoft.Web/serverfarms/webapp4container",
  "siteConfig": null,
  "slotSwapStatus": null,
  "state": "Running",
  "suspendedTill": null,
  "tags": null,
  "targetSwapSlot": null,
  "trafficManagerHostNames": null,
  "type": "Microsoft.Web/sites",
  "usageState": "Normal"
}

4.9. デプロイしたアプリケーションの動作確認

Web App for Containers を作成したのち、Web App for Containers のエンドポイントにアクセスし、正しくアプリケーションが動作しているか否かを確認します。

ここでは、環境変数を設定していないため、プロパティで設定した値 (Injected value) が表示されます。

$ curl https://yoshiowebapp.azurewebsites.net/app/data/config/injected
Config value as Injected by CDI Injected value

4.10. Web App for Containers のアプリケーション設定の追加

次に、Web App Config のアプリケーション設定を追加し、injected_value に "Value from Server App Setting" という文字列を設定します。

$ az webapp config appsettings set --resource-group WebApp --name yoshiowebapp --settings injected_value="Value from Server App Setting"
[
  {
    "name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
    "slotSetting": false,
    "value": "false"
  },
  {
    "name": "DOCKER_REGISTRY_SERVER_URL",
    "slotSetting": false,
    "value": "containerreg4yoshio.azurecr.io/tyoshio2002"
  },
  {
    "name": "DOCKER_REGISTRY_SERVER_USERNAME",
    "slotSetting": false,
    "value": "containerreg4yoshio"
  },
  {
    "name": "DOCKER_REGISTRY_SERVER_PASSWORD",
    "slotSetting": false,
    "value": null
  },
  {
    "name": "WEBSITES_PORT",
    "slotSetting": false,
    "value": "8080"
  },
  {
    "name": "injected_value",
    "slotSetting": false,
    "value": "Value from Server App Setting"
  }
]

4.11. 設定変更後のアプリケーションの動作確認

最後に、アプリケーション設定で追加した設定が反映されているかを確認します。

$ curl https://yoshiowebapp.azurewebsites.net/app/data/config/injected
Config value as Injected by CDI Value from Server App Setting

上記 4.10 の設定後は、明示的にコンテナを再起動しなくても内部的に再起動が行われ設定が反映されます

以上で、MicroProfile Config を使用したアプリケーションを Azure Web App for Containers 環境で動作させることができました。また Web App for Containers のアプリケーション設定(外部の設定値)をアプリケーションで読み取ることができました。

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

初心者から始めるJava、抽象クラス

はじめに

この記事は備忘録である。
参考書レベルの内容だが、本記事に掲載するコードについては、
間違えたものが中心となる。これは実際にコーディング中に間違えた部分を掲載し、自分で反省するために投稿するという目的によるもの
また、後日にJavaSilver試験問題の勉強を兼ねて復習するため、深い部分の話はここでは触れない。

環境

言語:Java11、JDK13.0.2
動作環境:Windows10

オブジェクトを作れないクラス

今まで扱ったクラスはすべて、オブジェクト生成をしたあとのことを考えて内容を作っていた。だが、この抽象クラスそのものではオブジェクト生成・インスタンス化ができない
今回の抽象クラスは、「クラスを作ることを考えて内容を決める」必要があるクラスであり、「この抽象クラスを継承したクラスを作るとき、この機能だけはつけてくださいね」というつもりで内容を考えるとよい。

抽象クラスと、そのメソッドのうち処理内容を定義していないメソッドについて。抽象クラスは、abstract修飾子を持ち処理内容をその場では定義しないメソッド(抽象メソッド)を持つという特徴があり、classの前にabstractを付けることで抽象クラスとして宣言できる。

abstractClass.java
abstract class Wild
{
  protected int number;
  public void getNumber(int n)
  {
    number = n;
    System.out.println("この猫は" + number + "番目の猫です。");
  }

  abstract void show();

}

注意点として。抽象クラス内には抽象メソッドが必要だが、具象メソッド(abstractを付けていないメソッド)を実装してもよく、具象メソッドはサブクラスではそのまま継承されたメソッドとして利用できる

抽象メソッドは必須事項を定める

抽象クラスを継承したサブクラスは、abstractのついた抽象メソッドを元に具象メソッドをオーバーライドで実装しなければならない
オーバーライドするということは、

1.シグニチャ(メソッド名。この場合引数関係は実装を待つ、と考えている)が同じ
2.アクセス修飾子が同じかより緩い
3.戻り値の型が原則同じ(共変戻り値を除く)

この条件を満たす必要があるということ。

同じ名前のメソッドがサブクラスに必須となるので、抽象クラスの内容をよく理解しないといつまでもエラーを吐き続けることになる。

HouseCat.java
class HouseCat extends Wild
{
  public void see(int num)
  {
    number = num;
    System.out.println("この猫は" + number + "番目の猫です。");
  }
  //ちょっと待った、show()はどこだ?
}

おわりに

instanceof演算子を使うと、2項の変数が同じクラスに属しているかどうか調べることが出来る
Java SE11 Silver 問題集(通称黒本)から引用。

a instanceof b
aが、bと同じクラスかbのサブクラスのインスタンスであればtrue

main関数内で条件分岐に組み込むことが出来るので、チェック機構に向いている。

今回扱った抽象クラスによく似たものとして、次回予定のインターフェースがあるのだが、これらの違いをよく確認しておきたい。

参考

出来るだけ自分で変数や式を書いてコンパイルしているので、完全に引用する場合はその旨記述する。

やさしいJava 第7版
Java SE11 Silver 問題集(通称黒本)

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

なぜjavaでfinal class A{abstract class B{}}が許されるのか

javaでは、抽象メソッドを持つクラスは必ず抽象クラス(かインタフェース)になると決まっている。
しかし抽象内部クラスを持つクラスは、別に具体的クラスでも構わないのである

例えば次のようなコードは実行可能である。

java
final class A
{
    static abstract class B{String str="hoge";}
}

public class Main
{
    public static void main(String...args)
    {
        System.out.println(new A_B().str);
      //System.out.println(new A.B(){}.str); (匿名クラス)でも別に良い
    }
} 

class A_B extends A.B{}

もし具体クラスが抽象内部クラスを持てなかったら

一見矛盾する仮定ではありますが、「マクロ(全体)に多様性を認めたくないが、ミクロ(細部)に多様性を認めたい」という場合を考えます。

このような仮定は統計力学など、ミクロとマクロの関連を考えるとき役立ちます。

例えばAir(空気)クラスとMolecule(分子)クラスを作ったとします。

分子が集まって空気を作りますので、Air has a Molecule[] といえるでしょう。

分子には水素分子、水分子、二酸化炭素分子等いろいろ考えられます。
したがって、
H2 extends Molecule, H2O extends Molecule, CO2 extends Molecule
のように継承して使う一方、「単なる分子」という分子は存在しないので、継承せずに使うことはないと思います。
そこでMoleculeは抽象クラスとなりそうです。

一方、Airには下位概念が考えにくいので、抽象クラスにしたくはありません。

ここでもし、MoleculeAirからしか呼ばれたくないメソッドを定義したくなったらどうしましょう。

いつもならMoleculeAirのインナークラスにすることで解決できます。
もし具体クラスAirが、抽象クラスMoleculeを内部に持てなかったら、多分次のような面倒くさいことをしなくちゃいけない。

  1. final public static class ${private $(){}}Airに持たせる
  2. SomethingのメソッドにAir.$型引数を持たせる

で実現する。

Air.$型はpublic staticAir以外からも見えるため、Air以外のクラスのメソッドの仮引数型として利用できる。
しかしそのコンストラクタはprivateなので、インスタンス生成はAirからしか行えない。Air以外はすなわちAir.$型実引数を渡せない。

そのおかげで、「仮引数にAir.$型を持つメソッドならば、そのメソッドはAirクラスからしか呼べない」が成立する。

実例を以下に示す。

public class Test2 {

    public static void main(String[] args) 
    {

        //new H2O.only_for_air(new Air.$()); はアクセス権限のエラー。つまりAir以外からonly_for_airを呼べない
        new Air().molecule_caller(new H2O());//Airからは呼べる

    }

}



class Air
{
    final public static class ${private $(){}}//Airの子クラスで書き換えされても困るのでfinalizeした

    void molecule_caller(Molecule m)
    {
        new H2O().only_for_air(new Air.$());
    }


}

abstract class Molecule
{
    void only_for_air(Air.$ $)
    {
        System.out.println("succeeded");
    }
}


class H2O extends Molecule{}

とまあ、こんな面倒なことをしなくちゃいけなくなる。

だから「抽象内部クラスを持つ具体的クラス」がjavaでは可能なんだろうなと思った次第。

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

Javaの String とか StringBuilder のメソッドについてまとめてみた

始めに

 Java8 Silver取得に向けて絶賛勉強中の自分が、StringやStringBuilderのメソッドについてまとめた記事です。
ここではJava Silver受験する上で押さえておきたい、String や StringBuilder のメソッドを紹介しています。お察しの通り未熟者ですので、間違いがありましたら、ご指摘いただけると幸いです。

 次章から、Stringのメソッド・StringBuilder のメソッドの順に説明していきます。

Stringについて

Stringそのものについては、ざっくりとだけ説明します。押さえるポイントは

・プリミティブ型ではなく、参照型である
・Immutableである(一度定義されたら、メモリ上では書き変わらない)

ってところでしょうか。Stringはchar[]で文字列を扱っています。そのため下記の方法では、参照値は別のものでも、参照先のオブジェクトは同じになります(コードは、参考2より引用)。

String a = "りんご";
String b = new String("りんご");
String c = new String(new char[]{'り', 'ん', 'ご'});
System.out.println(a); // りんご
System.out.println(b); // りんご
System.out.println(c); // りんご

また、Immutableとは言いましたが、再代入出来ない訳ではないです。再代入すると、変数の参照先が切り替わります(詳しくは、参考3の記事をご覧ください)。

String の主なメソッド

それでは本題、以下、「ここ、テストに出るよ!」なメソッドたちです(メソッドの一覧は、参考1より引用)。
(メソッドの紹介がダラダラと続くので、実際に手を動かしてコードを書いてみて下さい)

char charAt(int i)

 iで指定した位置にある文字を返します。元の文字列は変化しません。
(コードの見方: // の後ろに書かれた文字が、出力される文字列を表しています)

String str = "Hello Java!";

System.out.println(str.charAt(4));  // J
System.out.println(str);  // Hello Java!

String concat(String str)

 strで指定した文字列を、後ろに連結します。やっぱり元の文字列は変わりません。

String str = "Hello ";

System.out.println(str.concat("Java!"));  // Hello Java!
System.out.println(str);  // Hello 

boolean equalsIgnoreCase(String str)

 対象の文字列とstrを、大文字小文字を区別せず、比較します。

String str = "Hello Java!";

System.out.println(str.equalsIgnoreCase("HELLO JAVA!"));  // true

// equals で比較した場合
System.out.println(str.equals("HELLO JAVA!")); // false

int indexOf(int ch), int lastIndexOf(int ch)

 この2つはセットでおぼえましょう!
どちらも、chで指定した文字が最初に登場した位置を返します。

 indexOf は、文字列を左から、lastIndexOf は右から探します。そして、一番左の文字を0番目として該当の文字が何番目にあるか教えてくれます(空白も1文字として数えます)。

String str = "Hello Java!";
// H  e  l  l  o     J  a  v  a   !
// 0  1  2  3  4  5  6  7  8  9  10

System.out.println(str.indexOf("l"));  // 2
System.out.println(str.lastIndexOf("l"));  // 3

int length()

 文字列の文字数を返します。ここでも空白は1文字としてカウントします。

String str = "Hello Java!";

System.out.println(str.length());  // 11

String replace(char o, char n)

 文字列中にある文字oを、すべて文字nで置換した文字列を返します。

String str = "Hello Java!";

System.out.println(str.replace('a', 'o'));  // Hello Jovo!

boolean startsWith(String prefix), boolean endsWith(String suffix)

 こちらもセットで覚えて良さそうですね!

startsWithは、文字列がprefixから始まっていればtrueを返します。
endsWith は、文字列がsuffixから始まっていればtrueを返します。

String str = "Hello Java!";

System.out.println(str.startsWith("Hello"));  // true
System.out.println(str.startsWith("Hey!"));   // false

System.out.println(str.endsWith("Java!"));   // true
System.out.println(str.endsWith("python!")); // false

String substring(int start, int end)

 startから、end番目の直前までの文字列、間にある文字列を返します。この「間にある」ってのが分かりにくいので、コードを見た方が理解が早いかもです。

 ポイントとしては、文字そのものではなく、文字の間を数えあげることです(下記の例では、パイプ( | )で元の文字列を区切って番号を付けています)。

String str = "Hello Java!";

// | H | e | l | l | o |   | J | a | v | a  | ! |
// 0   1   2   3   4   5   6   7   8   9   10   11

// 第1引数、第2引数共に指定した場合
System.out.println(str.substring(2, 7));  // llo J

// 第1引数のみ指定した場合
// 以下の2つは同じ文字列を出力する
System.out.println(str.substring(2));  // llo Java!
System.out.println(str.substring(2, str.length()));  // llo Java!

String toLowerCase(), String toUpperCase()

toLowerCase()は、大文字を小文字に変換します。元々小文字だった文字は、変化しません。

toUpperCase()は、小文字を大文字に変換します。それだけです。

String str = "Hello Java!";

System.out.println(str.toLowerCase());  // hello java!
System.out.println(str.toUpperCase());  // HELLO JAVA!

String trim()

 両端の半角空白を除去します。
ちょっと分かりにくいですが、文字数を数えてあげると一目瞭然ですね!

String str = " Hello Java! ";

System.out.println(str.trim());           // Hello Java!
System.out.println(str.trim().length());  // 11

System.out.println(str);           //  Hello Java! 
System.out.println(str.length());  // 13

StringBuilderについて

 StirngがImmutableな一方で、StringBuilderは再代入の際に参照先の値を置き換えます。
 要するに、Stringのメソッドが元々の文字列を変化させないのに対して、StringBuilderのメソッドは元々の文字列をを変えてしまうってことです。

基本的な使い方

StringBuilderの宣言の方法は、下記のやり方があります1

// 1. フツーに宣言、初期容量は16文字
StringBuilder sb1 = new StringBuilder();

// 2. 初期容量 capacity 文字で宣言
// 80.0 など実数を入れるとコンパイルエラーになります、指定できる数字はintのみ
StringBuilder sb2 = new StringBuilder(int capacity);

// 3. strで初期化する
StringBuilder sb3 = new StringBuilder(String str);

// 4. 宣言して即座に出力
System.out.println(new StringBuilder("Hello Java!"));

StringBuilderの主なメソッド

StringBuilder append(String str)

 strを文字列の後ろに付け加えます。Stringとは違い、元々の文字列は変化します。

StringBuilder sb = new StringBuilder("Hello Java!");

System.out.println(sb.append(" World!!"));  // Hello Java! World!!
System.out.println(sb);  // Hello Java! World!!

StringBuilder insert(int offset, String str)

 このメソッドも文字を付け加えるためのものですが、付け加える位置まで指定できます。
分かり易さのため、パイプ( | )で元の文字列を区切って番号を付けます。

StringBuilder sb = new StringBuilder("Hello Java!");
// | H | e | l | l | o |   | J | a | v | a  | ! |
// 0   1   2   3   4   5   6   7   8   9   10   11

System.out.println(sb.insert(10, "Script"));  // Hello JavaScript!
System.out.println(sb);  // Hello JavaScript!

StringBuilder delete(int start, int end)

 start番目から、end番目の直前までの文字を削除します。

StringBuilder sb = new StringBuilder("Hello Java");

System.out.println(sb.delete(2, 4));  // Heo Java!
System.out.println(sb);  // Heo Java!

// length() と合わせて使えば、全部の文字を削除できます
System.out.println(sb.delete(0, sb.length()));  // (何も出力されない)

StringBuilder reverse()

 文字列の並びを逆にします。

StringBuilder sb = new StringBuilder("Hello Java!");

System.out.println(sb.reverse());  // !avaJ olleH
System.out.println(sb);  // !avaJ olleH

void setCharAt(int index, char ch)

 index番目の位置の文字chで置き換えます。

StringBuilder sb = new StringBuilder("Hello Java!");

sb.setCharAt(6, 'j');
System.out.println(sb);  // Hello java!

StringBuilder replace(int start, int end, String str)

 start番目から、end番目の直前までの文字を、strで置き換えます。置き換える文字数とstrの文字数が一致していなくも大丈夫です。
setCharAtと違い、文字列の置き換えを行い、StringBuilder を返します。

StringBuilder sb = new StringBuilder("Hello Java!");

System.out.println(sb.replace(6, 10, "python"));  // Hello python!
System.out.println(sb);  // Hello python!

String substring()

 Stringのsubstring()と全く同じ使い方が出来ます。

String toString()

 ご覧の通り2、StringBuilderをStringに変換してくれます。
String と比較するときなどに使います。

StringBuilder sb = new StringBuilder("Hello Java!");
String str = "Hello Java!";

// str と sb の値が同じか比較
System.out.println( str.equals(sb.toString()) );  // true

最後に

 Java Silverでは、String・StringBuilder のメソッドに関する問題が多く出題されます。所謂、基本的な事らしいですが、とは言え調べてまとめるのは大変です(でした)。
 この記事が少しでもString・StringBuilder の理解、試験の合格に役立てればと思います。

参考

1.山本道子 (2015) Java プログラマ Silver SE 7(第5刷発行) ㈱翔泳社 発行
2.図で理解するJavaの参照
3.【Java初心者】値渡しと参照の値渡し(”参照渡し”という言葉は誤解を生むとのことで修正しています)


  1. 個人的には、4の方法はエラーになると思っていただけに驚きでした。 

  2. どこがご覧の通りなんだろう、って後になって思いましたが、敢えて修正しないことにしました。 

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