- 投稿日:2021-01-15T23:18:10+09:00
JavaScript,PHP,Javaのrandom関数をまとめてみる
今回はrandom関数を各言語ごとにまとめてみます。
なんでrandom関数なのかというとミニゲームなど作成時に使用する頻度が高く、好きな関数の一つだからです。
言語ごとに微妙に違いがあり、備忘録として記事にしておきます。ちなみにrandom関数にもアルゴリズムがあるので「擬似乱数」であって、
「真乱数」ではないところに注意。JavaScriptのMath.random()
JavaScriptのMath.random()関数は0以上1未満の値を返します。
※0は含むが1は含みません!let num = Math.floor(Math.random() * n) + m; // n = 上限値 , m = 下限値Math.random()は浮動小数点で返ってきますので、Math.floor()で小数点以下を切り捨てます。
「n」は上限値を設定し、「m」で下限値を設定します。例 ) 1〜10の乱数を生成する
let num = Math.floor(Math.random() * 10) + 1;
- Math.random()によって0以上〜1未満の乱数が生成される
- 10を乗算することによって0以上〜10未満の乱数となる
- Math.floor()によって小数点以下を切り捨て(ここで0〜9のいずれかの整数になっている)
- 1を足してあげることで1〜10のいずれかの整数が生成されます。
注意すべき点は1未満の数字を生成する点です。
PHPのrandom_int()
PHPで擬似乱数を生成するためにはrandom_int()関数を使用します。
$num = random_int(int $min, int $max);第一引数に下限値を渡し、第二引数に上限値を渡します。
min〜maxを含む乱数を整数値で返します。例 ) 1〜10の乱数を生成する
$num = random_int(1, 10);指定した数値を含むので簡単ですね。
JavaのRandom()クラス
Javaではjava.utilパッケージにあるRandom()クラスとnextInt()メソッドを使用します。
int num = new java.util.Random().nextInt(n);int型で返ってくるため変数はint型で宣言します。
nextInt()の引数が上限値となり、0〜上限値未満の値を返します。
JavaScriptと同じく指定した上限値自体は含みません!例 ) 1〜10の乱数を生成する
public class Main { public static void main(String[] args) { int num = new java.util.Random().nextInt(10) + 1; } }上限値を含まない点に注意して値を渡しましょう。
まとめ
Javascript
0以上1未満の浮動小数点を生成。
Math.floor()とセットで覚える。PHP
下限値、上限値指定した値を含む整数値を返す。Java
Random()クラスを使用する。
nextInt()メソッドの引数に上限値を渡す。
0以上「上限値」未満の整数を返す。こぼれ話
訓練校で「なぜ1未満なのか?」と先生に質問しました。
先生の答えはこうだった「なぜでしょうね〜、、、」
何か意図があって1未満なのかなと思ったが、
そんなの言語作った人にしかわからんよな。と思いました。それ以降、定義済の関数や変数、定数に疑問を抱くのはやめましたw
- 投稿日:2021-01-15T20:19:07+09:00
spring boot ポート重複問題
- 投稿日:2021-01-15T15:17:37+09:00
JAVAで最大公約数・最小公倍数を求める
はじめに
最大公約数・最小公倍数を求めるJAVAのプログラムを作る.
アルゴリズム
最大公約数と最小公倍数を求めるアルゴリズムについて説明する.
最大公約数
最大公約数はユークリッドの互除法で求める.
ユークリッドの互除法では,割り算の等式$$
a=bq + r
$$において,$a$と$b$の最大公約数は$r$と$b$の最大公約数と等しいという性質を用いて最大公約数を求めている.(参照:高校数学の美しい物語)
求める手順は次の通りである.(参照:最大公約数を求めるプログラム例)
2つの自然数a,b(a>=b)に対して,
1. aをbで割った余りをrに代入
2. r=0ならbが最大公約数となり処理を終了
3. aにbを代入,bにrを代入して1に戻る計算量は$O(\log(\min(a,b))$
最小公倍数
2つの自然数$a$,$b$の最小公倍数$lcm$は,$a$と$b$の最大公約数を$gcd$としたとき,次の式で求められる.(参照:2つの自然数の最小公倍数(LCM)を求めるアルゴリズム)
$$
lcm = \frac{a\times b}{gcd}
$$
計算量は$O(\log(\min(a,b))$実装
実装したプログラムは次の通りである.
Euclid.javapublic class Euclid{ public static int gcd(int a,int b){ if(a <= 0 || b <= 0){ return -1; } else if(a < b){ int tmp = a; a = b; b = tmp; } if(a % b == 0) return b; else return gcd(b,a%b); } public static int lcm(int a,int b){ return a / gcd(a,b) * b; } }EuclidMain.javaimport java.util.Scanner; public class EuclidMain{ public static void main(String[] args){ Scanner scan = new Scanner(System.in); System.out.println("Input 2 natural numbers."); int a = scan.nextInt(); int b = scan.nextInt(); System.out.println("Greatest common divisor."); System.out.println(Euclid.gcd(a,b)); System.out.println("Least common multiple."); System.out.println(Euclid.lcm(a,b)); } }実行例
>javac EuclidMain.java >java EuclidMain Input 2 natural numbers. 12 9 Greatest common divisor. 3 Least common multiple. 36参考
- 投稿日:2021-01-15T15:04:35+09:00
Gradle testでリモートデバッグ
問題
Eclipseからjunitのテストクラスを実行すると以下のようなエラーがでるとき、ググるとたいていクラスパスがどうのといわれる。
エラー: メイン・クラスorg.eclipse.jdt.internal.junit.runner.RemoteTestRunnerを検出およびロードできませんでした 原因: java.lang.ClassNotFountException: org.eclipse.jdt.internal.junit.runner.RemoteTestRunnerいろいろな解決策を試すも一向に治らないので(仕事で時間かけてるのもアホらしいので)、Gradleテストでリモートデバッグできないのかと調べたらあった。
リモートデバッグ
「--debug-jvm」オプションをつけて実行すると5005ポートで待受になるので、Eclipseからリモートデバッグで接続することが可能。
$ ./gradlew test --tests "*XxxxTest" --info --debug-jvm
- オプション
- --tests "*XxxxTest": 実行したいテストクラスのFQCN
- --info: Gradle実行のログレベルをINFOに(好みで)
まとめ
今までできないものだと思ってたが、Gradleにもリモートデバッグが可能になるオプションがあった。
- 投稿日:2021-01-15T08:36:28+09:00
正確な時間測定(Java)
方法
以前まではcurrentTimeMills()メソッドを使って、時間計測を行っていた。
しかし、非常に短い時間で行われる処理の計測をする場合、ミリ秒単位でしか測れないのでは
正確な時間測定ができない。
そのため、ナノ秒単位で時間測定を行えるnanoTime()メソッドで時間測定を行う。long start = System.nanoTime(); // 計測したい処理を記述 long end = System.nanoTime(); long timeSeconds = TimeUnit.NANOSECONDS.toSeconds((end - start));currentTimeMillis()との比較
currentTimeMillis nanoTime 特徴 1970年1月1日午前0時0分0秒からの経過秒数をミリ秒で表したもの 時間測定だけに特化したメソッド。 メリット スレッドセーフである。(複数のスレッドから呼び出されても安全なメソッドであること。) ナノ秒単位で測れるので、currentTimeMillisよりも正確な時間測定が可能 デメリット 過去のある時点からの時間計測をする際に、うるう秒を含んでないため、測定結果がずれる可能性がある スレッドセーフではない。
- 投稿日:2021-01-15T05:54:04+09:00
クリリンでもわかるJava入門(前編)
配列からオブジェクト指向までまとめる。
復習用にどうぞ
(タイトルのクリリンと特に関連性はないです)--以下の簡単な文法は割愛--
if構文
if-else if-else構文
swith文
while文
do-while文
for文配列
配列の各要素には同一種のデータしか格納できない。(intならintのみ、Stringのみ等)
添え字(index)は0から数える。(要素0番目~)配列作成手順
String型配列を作ってみる。
//String型配列変数dragonBallを定義 String[] dragonBall; //String型要素7つ作成し、代入し配列dragonBallの完成 dragonBall = new String[7]; //1行で作成することも可能 String[] dragonBall = new String[7];配列の要素数を知りたい
lengthを使う。
System.out.println(dragonBall.length);
とすれば、7と出力される。配列に要素を代入したい
配列dragonBallの2つ目(要素1番目)に二星球(アルシンチュウ)を代入する場合
dragonBall[1] = "二星球";
配列の要素は自動で初期化される
配列に何も代入していない要素を出力したらどうなるか。
String[] dragonBall = new String[7]; System.out.println(dragonBall[0]);↑nullが出力されエラーは起こらない。
配列もintやString同様に自動で初期化される。
・int,double型 ⇒ 0
・boolean型 ⇒ false
・String型 ⇒ null配列の作成と初期化を同時に行う
次の2パターンの記述方法がある。
①int[] score1 = new int[]{20,30,40,50};
⇒ 要素4つの配列が作成される
②int[] score2 = {20,30};
⇒ 要素2つの配列が作成されるfor文と配列
配列の添え字に変数を用いることができる。
public class Main { public static void main(String[] args){ int[] score = {10,20,30}; for(int i =0 ; score.length ; i++){ //4行目 System.out.println(score[i]); } } }変数iは、0,1,2と変化していく。これを配列を回すという。
4行目に「length」を用いることで、初期値を変更されても要素数は変わらないので
コードの修正が不要というメリットがある。拡張for文
単純に配列型を全て回したいときは拡張for文が便利。
for(型 変数名 : 配列){}
int[] score = {10,20,30}; for(int value : score){ System.out.println(value); }動きは1週目の value には10が入り、2週目の value には20が入る・・・。
要素の数だけ回転してくれる。参照
次のコードを見てほしい。
出力したbには何が入っているか。int a = 1; int b = a; a = 2; System.out.println(b);bには1が入る。
a=1のときに代入したから、その後aに何が代入されてもbには関係がない。しかし、配列だと変わる。
int[] a = {1}; int[] b = a; a[0] = 2; System.out.println(b[0]);bには2が入る。
配列aには直接値が入っているわけではない。
値がある場所を知っているだけである。これを参照という。
int[] b = a;
では場所を共有している状態。場所を住所に例えると、
Aさんは101号室にバナナを置く。
B=Aで、AさんはBに部屋番号を教える。
Aさんがリンゴを追加する。
Bさんが101号室に行くとバナナとリンゴがある。二次元配列
二次元配列の例
int[][] scores = new int[2][3];
2つの箱があって、それぞれ3つの箱が入っている状態。
[0] ⇒ [0][1][2]
[1] ⇒ [0][1][2]
計6つの箱が用意される。練習問題.javapublic class Main { public static void main(String[] args){ int[][] scores = {{10,20,30}, {40,50,60}}; System.out.println(scores[1][1]); System.out.println(scores.length); System.out.println(scores[0].length); } } }実行結果.50 2 3メソッド
メソッドの作成をメソッドの定義という。
使用することをメソッドを呼び出すという。
メソッド名の後ろの「{}」の部分をメソッドブロックという。
メソッド名();
で呼び出せる。public static void main(String[] args){ methodA(); } public static void methodA(){ System.out.println("メソッドAを呼んだ"); }引数
メソッドに値を渡すことができ、その値を引数という。
さらに細かく言うと、渡す値を実引数、受け取る値を仮引数という。引数を渡す側
メソッド名(値);
引数を受け取る側~ メソッド名(引数の型 引数を代入する変数){}
public static void main(String[] args){ methodA("クリリン"); } public static void methodA(String name){ //変数nameに引数クリリンが入ってくる。 System.out.println(name + "さんです。"); }引数を複数渡す
例:
引数を渡す側メソッド名("クリリン",20);
引数を受け取る側~ メソッド名(String name, int age){}
ローカル変数
ローカル変数の独立性
メソッド内で宣言された変数をローカル変数という。
ローカル変数はその変数が属するメソッド内だけで有効。(有効範囲をスコープという)
別メソッドで宣言された同名のローカル変数とは別物となる。戻り値
値を返すことを値を戻すという。
戻されるデータ(値)のことを戻り値という。
return文⇒return 戻り値;
で値を戻せる。public static 戻り値の型 メソッド名(型 変数){ return 戻り値; }戻す値がないときは「戻り値の型」の部分を
何も返さないという意味の「void」にする。戻り値活用例.javapublic static void main(String[] args){ int a = 10; int b = 20; int total = tasu(a, b); //戻り値が返ってくるのでtotalへ代入 System.out.println("合計は" + total + "です。"); } //足し算を行うメソッド public static int tasu(int x, int y){ return x+y; }return文はメソッドの終了も行う為、return文以降にコードを書いてもエラーになる。
オーバーロード
同名メソッドを定義することをオーバーロードという。
同じメソッド名でも仮引数の型や引数の数が違えば定義しても問題ない。
メソッドを呼ぶときに自動で判断してくれる。public static int add(String x, int y){ return x+y; } public static int add(int x, int y){ return x+y; } public static int add(int x, int y,int z){ return x+y+z; }
add("クリリン", 20)
と呼べば、きちんと一番上のメソッドが呼ばれる。参照渡し
これまでのサンプルコードで値そのものを渡すことを値渡しという。
配列を渡すことを参照渡しという。参照渡し例.javapublic class Main { public static void main(String[] args) { int[] array = {1,2,3}; //配列を渡す(参照渡し)。 printArray(array); } public static void printArray(int[] array){ for(int element : array){ System.out.println(element); } } }参照渡し例2.javapublic class Main { public static void main(String[] args) throws Exception { int[] array = {1,2,3}; intArray(array); for(int i : array){ System.out.println(i); } } public static void intArray(int[] array){ for(int i = 0; i < array.length; i++){ array[i]++; } } }実行結果は2,3,4となる。
戻り値で返してるわけでもないのに配列arrayの値に変化があるのは
配列型が参照型だから。クラスとパッケージ
クラスを利用することで1つのプログラムを複数に分ける、つまり部品化し、
開発を分担できる。
クラスが増えてきたらパッケージを用いてさらに部品化できる。
・クラス名は一文字目は大文字でなければいけない。
・パッケージ名の一文字目は基本小文字。
・パッケージは独立しているので、パッケージ毎に同じクラス名を作成することも可能。
・パッケージ名にドット「.」を含めることが多い。
・自身以外のパッケージを使うとき、所属パッケージを添える必要がある。例:
movieパッケージのとあるクラスで
計算(calc.app)パッケージの計算処理(CalcLogic)クラスのtasuメソッドを使いたい。
int total = calc.app.CalcLogic.tasu(a,b);
パッケージ名を先頭につけたこのようなクラスを
完全限定クラス(full qualified class name)
略してFQCNという。パッケージのメリット
- パッケージ毎に別で開発すれば、クラス名が他人と被っても名前の衝突が起きない。
- パッケージ名はドメインを逆にしたものを推奨。(foo.exsample.com ⇒ com.exsample.foo)
ドメインは世界中の誰とも被ることがない。
※名前の衝突:名前が被ってエラーになること。
インポート文
import文:FQCNの記述を省略できる。
import パッケージ名.クラス名;
※import文はソースの先頭に記述。ただし、パッケージよりは後。以下のように記述量が減る。
int total = calc.app.CalcLogic.tasu(a,b);
⇒int total = CalcLogic.tasu(a,b);
インポートしたいクラスが大量にあると大変。
全てのクラスをインポートしたいときはアスタリスクを使う。
計算(calc.app)パッケージなら
import calc.app*;
API
APIとは便利な公式のようなもの。
java.util.Arrays.sort()で自動並び替えを使う為にjava.util.*
をインポートする。import java.util.*; public class Main { public static void main(String[] args) throws Exception { int[] heights = {20,35,10}; java.util.Arrays.sort(heights); for(int h : heights){ System.out.println(h); } } }実行結果は10,20,35となる。
API 意味 java.lang 必須のクラス群 java.util 便利なクラス群 java.math 数学系のクラス群 java.net ネット通信系のクラス群 java.io ファイル読み書き,データ処理クラス群 java.langは自動的にインポートされる。
様々なAPIの詳細は「Java APIリファレンス」と検索すれば見れる。オブジェクト指向 ~インスタンスとクラス~
オブジェクトの作成方法
①クラスの定義
②インスタンス化
③インスタンス(オブジェクト)の完成オブジェクトの仕組みをガンプラで例えると・・・
①ガンプラの基本となる型を作れば
②それを利用し様々なガンプラを生成し
③できあがる
元となる基本を作れば簡単に大量生産できるメリットがある。プログラムに必要なものをRPGで例えると・・・
・mainメソッド(神様クラス、最初に動く)
・その他のメソッド(登場人物クラス)クラスの説明
public class Gokuu{ String name; int hp; void attack(){・・・} void jump(){・・・} }変数を宣言することを属性の定義という。
定義された変数をフィールドという。
メソッドの宣言は操作の定義という。フィールド宣言の頭に「final」を付けると値の変更不可になる。
「final」が付くと定数フィールドという。定数フィールドはわかりやすく大文字推奨。
final String NAME;
final int HP;
this
public class Gokuu{ String name; int hp; void attack(){ this.name = "悟空"; } }「this.name = "悟空";」と「name = "悟空";」は同じ動作だが、
ローカル変数や仮引数にも「name」が使われていれば予想外の動作が起こる可能性があり、
防ぐ為にthisで自身のフィールドだと現している。以下は極端な例だが
「this」があるおかげで仮引数とフィールドの見分けがつきやすい。仮引数にnameが使われている例.javapublic class Main { public static void main(String[] args) { //インスタンスの生成(後で説明) Monster m = new Monster(); //引数「魔人ブウ」を渡す m.attack("魔人ブウ") } } public class Monster{ String name; public void attack(String name){ //仮引数をフィールドに代入 this.name = name; System.out.println(this.name + "を攻撃!") } }クラス図
クラス⇒属性⇒操作 この3つでまとめた図をクラス図という。
フィールドとメソッドをまとめてメンバという。クラス定義で可能になること
1.そのクラスに基づいてインスタンスを生成できる。
2.そのクラスから生まれたインスタンス を入れる変数の型が利用できる。インスタンスの生成方法
クラス名 変数名 = new クラス名();
とあるクラスで悟空クラスのattackメソッドを利用したいとき
インスタンスを生成することで利用できる。
「new」インスタンスの生成を行う。
new Gokuu();
で悟空クラスのインスタンスを作成。
変数名は「gokuu1」とする。
悟空クラスのインスタンスなので型は「Gokuu」よって、
Gokuu gokuu1 = new Gokuu();
となる。
さらに悟空クラスのattackメソッドも使いたいので
gokuu1.attack();
となる。
また悟空クラスのHPフィールドを取得したいなら
gokuu1.hp;
と書けば良い。生成は二行に分けても良い
悟空インスタンスの生成.javaGokuu gokuu1; //クラス型はフィールドに用いることも可能。 gokuu1 = new Gokuu();ここで「this」の説明時に用いたコードをもう一度見ると理解できる。
仮引数にnameが使われている例.javapublic class Main { public static void main(String[] args) { //インスタンスの生成 Monster m = new Monster(); //引数「魔人ブウ」を渡す m.attack("魔人ブウ") } } public class Monster{ String name; public void attack(String name){ //仮引数をフィールドに代入 this.name = name; System.out.println(this.name + "を攻撃!") } }インスタンスの独立性
次のコードで出力される値はいくつか。
Gokuu g1 = new Gokuu(); g1.hp = 100; Gokuu g2; g2 = g1; g2.hp = 200; System.out.println(g1.hp);実行結果は200。
g1とg2は同じ「new Gokuu();」を参照している。(配列のときと同じ。)
なのでg2で値を変更すればg1の値も変わる。
しかしインスタンスの独立性と言って、g2が自身で「new Gokuu();」すると、
参照はされない。
悟空1号と同じHPのコピーも作れるし、HPの違う悟空2号を作ることも可能ということ。クラス型をメソッド引数や戻り値に用いる
とりあえずカリン様に回復を求める孫悟空。
例.javapublic class Main { public static void main(String[] args) { Gokuu g1 = new Gokuu(); g1.hp = 100; Karinsama k1 = new Karinsama(); k1.heal(g1); //悟空を渡して回復させる。 } } public class Karinsama { public void heal(Gokuu g){ g.hp += 100; System.out.println("カリン様に仙豆でHP100回復してもらった。") } }コンストラクタ
newで生み出されたばかりのインスタンスのフィールドには、まだ何も入っていない。
しかし、厳密には初期値が設定されている。フィールドの初期値
型 値 int,short,long等 0 char ¥u000 boolean false 配列 null String,クラス null newされた直後に自動的に実行されるメソッドをコンストラクタという。
これまではインスタンス後に悟空のHPを決めていたが、
基本的100で良いなら、悟空クラスで先に100を代入しておく。public class Gokuu{ int hp; public Gokuu(){ this.hp = 100; } }コンストラクタになれる条件は2つ。
①メソッド名がクラスと同じ。
②メソッドに戻り値が宣言されていない(voidも✖)名前などインスタンス毎に変えたいフィールドは引数にすれば良い。
public class SuperSaiyaJin { int hp; String name; public SuperSaiyaJin(String name){ this.hp = 100; this.name = name; } } public class Main { public static void main(String[] args) { SuperSaiyaJin gokuu = new SuperSaiyaJin ("悟空"); SuperSaiyaJin beji-ta = new SuperSaiyaJin ("ベジータ"); } }実はJavaはインスタンス化時、必ず1つはコンストラクタを定義しなければならない。
しかし、1つも定義されていなければコンストラクタが自動で生成される為、
今までは気にせずインスタンス化が可能だった。コンストラクタのオーバーロード
コンストラクタも引数で判別して処理してくれる。
public class SuperSaiyaJin { //引数なければこれが動く public SuperSaiyaJin(){ } //引数あればこれが動く public SuperSaiyaJin(String name){ } }同一クラスのコンストラクタの利用
this();
で自身のコンストラクタを呼べる。public class SuperSaiyaJin { int hp; String name; public SuperSaiyaJin(){ this("悟空"); } public SuperSaiyaJin(String name){ this.hp = 100; this.name = name; } }この状態だと引数無しで SuperSaiyaJin()を呼んだ場合でも、
this("悟空");
があるのでhpは100に設定されている。静的メンバ
静的フィールド
インスタンスの独立性により、new毎に個々のフィールドを持っている。(自身でnewすれば参照(共有)されない)
ただしstaticを使うと全インスタンスで共有される。
インスタンスは別々で生成しても一部のフィールドを共有したいときに便利。staticの付いたフィールドを静的フィールドという。
静的フィールドはインスタンス化しなくても使える。
この特徴からクラス変数ともいう。
使い方:クラス名.静的フィールド名;
※もちろん静的フィールドと言うこともあり、メソッド内の変数(ローカル変数)にstaticは付けられない。
静的メソッド
static付きのメソッドを静的メソッドという。クラスメソッドともいう。
静的フィールドと合わせて静的メンバと呼ばれる。静的メソッドは静的フィールドと同じく以下3つの能力がある。
- ①メソッドがクラスに属する(共有される)ので、
クラス名.メソッド名();
で呼べる。- ②
インスタンス変数名.メソッド名();
で呼べる。- ③インスタンスを作らずとも呼べる。
※③にあるように、最初に呼ばれるmainメソッドはインスタンス化できないので常に「static」が必要となる。
例.javapublic class Item { public static void main(String[] args) { static int money; static void setRandomMoney(){ Item.money = (int)(Math.random()*1000); } } } public class Main { public static void main(String[] args) { //staticなのでnew不要 Item.setRandomMoney(); System.out.println(Item.money); //※1 Item i = new Item(); System.out.println(i.money); //※2 } }「Math.random()」は0.0~1.0未満の乱数を発生させる(double型)
※1と※2は今までなら別々の数字が出るが、今回の変数moneyは静的
つまり共有さているので同じ値が出力される。静的メソッドの制約
静的メソッド内で「static」がないフィールドやメソッドは呼べない。
staticが付いたメソッドはインスタンス化せずに呼べるが、
ブロック内にstatic無しで宣言されていたフィールドがあればエラーとなる。staticの付いていないフィールドはインスタンス化しないと呼べないからである。
静的メソッドで扱うフィールドは静的フィールドでなければならない。続きはこちら⇒後編
- 投稿日:2021-01-15T03:29:36+09:00
Lambda Container Image を Java 15ベースで作ってみる
概要
AWS Lambdaがコンテナイメージをサポートしました。
コンテナイメージ作成には、AWSの提供しているベースイメージを使うのが手っ取り早いです。
が、2021/01/15現在、Java15は提供されていません。私の参画しているプロジェクトでは現在Java15を採用しており、
11だと色々めんどくさいので15ベースでコンテナイメージを作れ! と言われ、
俺には無理だろ...と思いながら色々調べてやったら動いたので、共有します。ソースコード
早速ですが成果物としてのソースコードを公開します。
多分動くと思います。
200 OK
を返すだけのサンプルです。
intx24/java-15-lambda-containerHandlerの処理はほとんど awsdocs/aws-lambda-developer-guideのサンプル から拝借しています。
変更点として、Java15対応を確認するため、不要なswitch式を付け足しました。どうやったか
1. まずJava11ベースイメージで動かしてみる
Java11のベースイメージを元に、まず動かしてみます。
ありがたいことに関連記事があったため、参考にして丸パクリし、Java11ベースのLambdaイメージを作成しました。
AWS LambdaのコンテナサポートをJavaで試してみた。2. Java11ベースイメージDockerfileを見てみる
「Java11ベースイメージのDockerfileを参考にしてみましょう」と上司に言われたので、見てみます。
よくわからない
tar.xz
を大量にADDしていることがわかります。
git lfs pull
することでtar.xz
ファイルが解凍可能になります。(最初これがわからなくて詰んだと思った)
以下、理解していったことを書いていきますlambda-entrypoint.sh
DockerfileのENTRYPOINTです。
どうやら
/var/runtime/bootstrap
が実行されるらしいです。
${AWS_LAMBDA_RUNTIME_API}
がない場合(多分ローカル環境)では エミュレータを使ってローカル実行を可能にしているっぽい。エミュレータに関しては、Runtime support for Lambda container images
からダウンロード出来るので、これをローカルからコンテナにCOPYすれば良さそうです。lambda-entrypoint.sh#!/bin/sh # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. if [ $# -ne 1 ]; then echo "entrypoint requires the handler name to be the first argument" 1>&2 exit 142 fi export _HANDLER="$1" RUNTIME_ENTRYPOINT=/var/runtime/bootstrap if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then exec /usr/local/bin/aws-lambda-rie $RUNTIME_ENTRYPOINT else exec $RUNTIME_ENTRYPOINT fi/var/runtime/bootstrap
よくわからないファイルでした。
コンテナにコピペしても動かなかった & 中身がブラックボックス過ぎと判断したので、代替手段を探します。
どうやらカスタムランタイムを作る手順でbootstrapを作ればいけるらしい?カスタムランタイムについて調べつつ、無理だから諦めようと思った頃に、
aws-lambda-java-runtime-interface-client でLambda関数が実行できることを知りました。
このコマンドをaws-lambda-rie
の引数にすれば良さそうです。3. 実装してみる
Dockerfile
Java15が入ってるAmazonLinux2のイメージがあるのでこれをベースにします。
後述する、自作のlambda-entrypoint.sh
をENTRYPOINTとして,
Lambda関数example.Handler::handleRequest
を渡します。FROM amazoncorretto:15 # set environment variables ENV CLASSPATH /var/task/* WORKDIR /var/task # copy lambda execution files COPY aws-lambda-rie /usr/local/bin/aws-lambda-rie COPY lambda-entrypoint.sh /lambda-entrypoint.sh # Copy function code COPY build/libs/java-15-lambda-container-1.0-SNAPSHOT-all.jar /var/task/ # Set the Entrypoint ENTRYPOINT ["/lambda-entrypoint.sh"] # Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) CMD [ "example.Handler::handleRequest" ]lambda-entrypoint.sh
aws-lambda-java-runtime-interface-client
を実行します。
$HANDLER_NAME
には、 DockerfileのCMD
で指定したLambda関数が渡されます。
aws-lambda-java-runtime-interface-client
の README通りにjava -cp ./* com.amazonaws.services.lambda.runtime.api.client.AWSLambda example.App::sayHello
を引数として渡すと、
aws-lambda-rie
がjava
をbootstrap
のあるパスだと解釈して死ぬため、/usr/bin/java
に置き換えます。また、ここではクラスパスをDockerfile内で環境変数として指定済みです。
#!/bin/sh if [ $# -ne 1 ]; then echo "entrypoint requires the handler name to be the first argument" 1>&2 exit 142 fi HANDLER_NAME=$1 if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then # local environment exec /usr/local/bin/aws-lambda-rie \ /usr/bin/java \ com.amazonaws.services.lambda.runtime.api.client.AWSLambda "$HANDLER_NAME" else exec /usr/bin/java \ com.amazonaws.services.lambda.runtime.api.client.AWSLambda "$HANDLER_NAME" fi/var/runtime/bootstrap
lambda-entrypoint.sh
内に統合したので不要となりました。苦労したこと
javaのビルド周りがわからん
java自体の経験が1年ほど + intelliJ経由で触ってた + プロトタイプが出来ているプロジェクトに途中参画した 人間なので、
javaはgradleタスク実行したらとりあえず動くぐらいの認識でした。そもそもクラスパスを通さなければ実行できないということも知らず,
Class Not Found
エラーで半日ほど消耗しました。
同じ原因で、カスタムランタイムのビルドについても1分で諦めました。AWS提供のベースイメージを
run -it
オプションでコンテナを探索できない最初は
ENTRYPOINT
の概念すら知らなかったため、
各種ファイルを確認するため、run -it
でコンテナを探索しようと思ったら出来ず困ってました。
結果的にはdocker exec
経由でcat
したり、
IntelliJのdocker のFilesタブで確認していました。まとめ
今回、Lambda Container Image を Java 15ベースで作ってみて、一応動くところまで出来ました。
調査・実装含めて4日ほど作業にあてましたが、
まだまだ知るべきことが多くあるなと思わされる経験でした。