- 投稿日:2020-03-18T23:36:50+09:00
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 Checksumで
jdk-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
以上
- 投稿日:2020-03-18T23:36:50+09:00
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 Checksumで
jdk-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
以上
- 投稿日:2020-03-18T20:59:11+09:00
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; } }これも誰がそんなことするんだよという例ですが、やってみました。どうやら明示的に書いた方が有効になるみたいですね。
感想
早く正式な機能になってください。
参考リンク
- 投稿日:2020-03-18T16:41:09+09:00
AppVeyorでMavenプロジェクトのCI
以下の記事で作ってきたMaven+Javaのプロジェクトを、CIで回そうと思って調査したので覚書です。
https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2bAppVeyor
こちらから登録できます。
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]を選ぶ
- [MSBuild]の隣の、[Script]を選ぶ
- [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上ではビルドもテストも正常終了する
- 投稿日:2020-03-18T15:29:37+09:00
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); } } }; }
- 投稿日:2020-03-18T13:59:50+09:00
ボタンが急に効かなくなった
すみません。
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.jsfunction 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; }
- 投稿日:2020-03-18T13:53:52+09:00
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 アノテーションはクラスレベル、もしくはメソッドレベルで付加可能で、クラスに付加した場合、クラス内に存在する全メソッドに適用されます。メソッドに付加した場合、指定したメソッドだけが対象になります。クラスでアノテーションを付加し、メソッドにも付加した場合は、メソッドで指定した設定が有効になります。
- 正常に処理が終了した場合、結果を正常に返します。
- 送出された例外を abortOn で指定した場合、スローされた例外を再送出します
- 送出された例外を retryOn で指定した場合、メソッド呼び出しが再試行されます
- それ以外の場合、送出された例外を再送出します
また、他の 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種類の方法で設定可能です。
- スレッド・プールの分離 : (@Asynchronous アノテーションと併用した場合)
スレッド・プール内の待機中のキューサイズで最大同時リクエスト数を設定します。- セマフォの分離:(@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 の各種機能と組み合わせる事により、より有効的に働くため仕様内に取り込まれました。
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.completedFuture(conn); }上記の例では、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 上で耐障害性を持つ複数のサービスを連携をしてみたいと思います。
- 投稿日:2020-03-18T13:50:50+09:00
Azure Web App for Containers 環境で MicroProfile Config 動作検証
1. Azure Web App for Containers 環境で MicroProfile Config 動作検証
アプリケーションは、外部システムとの連携のための接続情報 (DB や、外部の HTTP エンドポイント)や、開発環境、テスト環境、本番環境などの環境設定の差を、プログラム・ソースコードから切り離し、外部の設定ファイル等に書き出すことで容易に接続先や設定を切り替えることができます。
また、外部の設定ファイルなどに書き出しておくことで、接続先を切り替えるためにアプリケーションのソースコードの編集やビルドは不要で、同一のソースコードや実行ライブラリを利用できます。クラウド・ネィティブなアプリケーションを構築していくために、設定情報の外だしはとても重要です。
「参照:The Twelve Factors のIII. 設定 : 設定を環境変数に格納する」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 の実装プロバイダから個別に提供されています。
- Apache Geronimo Config
- WebSphere Liberty 2017 March-June Beta so far
- Payara Server 173 and Payara Micro 173
- WildFly & Thorntail
- microBean™ MicroProfile Config
MicroProfile Config の概要
MicroProfile Config は数少ない API から構成されています。
MicroProfile Config API 1.4 の一覧
- Config
- ConfigBuilder
- ConfigProperty
- ConfigProvider
- ConfigProviderResolver
- ConfigSource
- ConfigSourceProvider
- Converter
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 value2. サンプル・プロジェクトのビルドと実行
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 valueMicroProfile ではプロパティ・ファイルの設定値をシステム・プロパティで上書き設定することができます。そこで環境変数を設定し、環境変数の値を 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 環境で動作確認を行います。下記の手順に従い動作確認を行います。
- Azure Container Registry 用のリソース・グループを作成
- Azure Container Registry を作成
- Azure Container Registry のパスワードの確認
- Azure Container Registry にログインしイメージを Push
- Azure Container Registry に Push したイメージの確認
- Web App for Containers 用のリソース・グループを作成
- Web App for Containers 用の AppService プランの作成
- コンテナ・イメージを指定し Web App for Containers を作成
- デプロイしたアプリケーションの動作確認
- Web App for Containers のアプリケーション設定の追加
- 設定変更後のアプリケーションの動作確認
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: 13684.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 value4.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 のアプリケーション設定(外部の設定値)をアプリケーションで読み取ることができました。
- 投稿日:2020-03-18T11:51:25+09:00
初心者から始めるJava、抽象クラス
はじめに
この記事は備忘録である。
参考書レベルの内容だが、本記事に掲載するコードについては、
間違えたものが中心となる。これは実際にコーディング中に間違えた部分を掲載し、自分で反省するために投稿するという目的によるもの。
また、後日にJavaSilver試験問題の勉強を兼ねて復習するため、深い部分の話はここでは触れない。環境
言語:Java11、JDK13.0.2
動作環境:Windows10オブジェクトを作れないクラス
今まで扱ったクラスはすべて、オブジェクト生成をしたあとのことを考えて内容を作っていた。だが、この抽象クラスそのものではオブジェクト生成・インスタンス化ができない。
今回の抽象クラスは、「クラスを作ることを考えて内容を決める」必要があるクラスであり、「この抽象クラスを継承したクラスを作るとき、この機能だけはつけてくださいね」というつもりで内容を考えるとよい。抽象クラスと、そのメソッドのうち処理内容を定義していないメソッドについて。抽象クラスは、
abstract
修飾子を持ち処理内容をその場では定義しないメソッド(抽象メソッド)を持つという特徴があり、class
の前にabstract
を付けることで抽象クラスとして宣言できる。abstractClass.javaabstract 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.javaclass 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
関数内で条件分岐に組み込むことが出来るので、チェック機構に向いている。今回扱った抽象クラスによく似たものとして、次回予定のインターフェースがあるのだが、これらの違いをよく確認しておきたい。
参考
出来るだけ自分で変数や式を書いてコンパイルしているので、完全に引用する場合はその旨記述する。
- 投稿日:2020-03-18T09:22:01+09:00
なぜjavaでfinal class A{abstract class B{}}が許されるのか
javaでは、抽象メソッドを持つクラスは必ず抽象クラス(かインタフェース)になると決まっている。
しかし抽象内部クラスを持つクラスは、別に具体的クラスでも構わないのである。例えば次のようなコードは実行可能である。
javafinal 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 aMolecule[]
といえるでしょう。分子には水素分子、水分子、二酸化炭素分子等いろいろ考えられます。
したがって、
H2 extends Molecule
,H2O extends Molecule
,CO2 extends Molecule
のように継承して使う一方、「単なる分子」という分子は存在しないので、継承せずに使うことはないと思います。
そこでMolecule
は抽象クラスとなりそうです。一方、
Air
には下位概念が考えにくいので、抽象クラスにしたくはありません。ここでもし、
Molecule
にAir
からしか呼ばれたくないメソッドを定義したくなったらどうしましょう。いつもなら
Molecule
をAir
のインナークラスにすることで解決できます。
もし具体クラスAir
が、抽象クラスMolecule
を内部に持てなかったら、多分次のような面倒くさいことをしなくちゃいけない。
final public static class ${private $(){}}
をAir
に持たせるSomething
のメソッドにAir.$
型引数を持たせるで実現する。
Air.$
型はpublic static
でAir
以外からも見えるため、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では可能なんだろうなと思った次第。
- 投稿日:2020-03-18T01:47:57+09:00
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); // Helloboolean equalsIgnoreCase(String str)
対象の文字列と
str
を、大文字小文字を区別せず、比較します。String str = "Hello Java!"; System.out.println(str.equalsIgnoreCase("HELLO JAVA!")); // true // equals で比較した場合 System.out.println(str.equals("HELLO JAVA!")); // falseint 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")); // 3int length()
文字列の文字数を返します。ここでも空白は1文字としてカウントします。
String str = "Hello Java!"; System.out.println(str.length()); // 11String 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!")); // falseString 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()); // 13StringBuilderについて
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 olleHvoid 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初心者】値渡しと参照の値渡し(”参照渡し”という言葉は誤解を生むとのことで修正しています)