- 投稿日:2020-06-04T23:45:02+09:00
Ruby vs Java: What makes a perfect fit for your custom software?
Let's pay attention to the choice between Java and Ruby performance and the long-term prospects for choosing either one.Java and Ruby performance and the long-term prospects for choosing either one.
- 投稿日:2020-06-04T23:35:13+09:00
WebSocket通信におけるJSONデータの利用方法 (Java , JavaScript)
背景
チャットアプリ作成を基準とし、サーバー⇔ブラウザ間でのWebSocket通信の記事を書いた。
サーバーをJava、クライアントをjavascriptで実装。[ソケット通信に関する過去記事]
・JavaとJavaScriptでwebブラウザとのソケット通信①
・JavaとJavaScriptでwebブラウザとのソケット通信②今回はチャットアプリを改造しつつ、『JSONデータの取扱方法』について学んでいく。
また、『ソケット通信でのJSONデータの送受信』についても学ぶ。目的
- Java、JavaScriptでのJSONの扱い方法を学ぶ。
- Webソケット通信においてJSONでのデータの送受信方法を学ぶ。
- JSONを用いてWebソケット通信の複数パスを実現する。
前提
この記事では主にJSONの扱い方を中心に記述していく。
ソケット通信についての解説は過去記事やググったページを参考にしてほしい。
⇒ サーバープログラムはJava、クライアントプログラムはJavaScriptで実装。JSONとは
http://www.tohoho-web.com/ex/json.html
例:{ "name": "Tanaka", "age": 26 }
連想配列のような形で値を保持しているもの。
要はただの文字列と考えると楽に理解することができる。(本当は違うけど)
詳細は割愛。実践内容
- JavaScriptでJSONデータの取り扱い (エンコード、デコード)
- JavaでJSONデータの取り扱い (エンコード、デコード)
- ソケット通信におけるJSONデータの送受信方法
- JSON送受信を利用した複数パスのチャットアプリ作成
1. JavaScriptでJSONデータの取り扱い
JavaScriptでは容易にJSONデータを扱うことができる。
エンコード
オブジェクト ⇒ JSON
JSON.stringify()
メソッドを使用する。使用例var obj = { name: '太郎', age: 30, area: 'Tokyo' } var json = JSON.stringify(obj); console.log(json);実行結果{"name":"Taro Tanaka","age":30}
- 変数
obj
に連想配列の形式で値を代入。(オブジェクトの作成)JSON.stringify(obj)
を用いてエンコードし変数json
に代入。(JSONへ変換)console.log(json)
でJSONデータをコンソールに表示。実行結果を見ると、連想配列形式のオブジェクトがJSON形式に変換されていることが分かる。
⇒ キーや文字列は「" "」で囲われている。数値は裸のまま。デコード
JSON ⇒ オブジェクト
JSON.parse()
メソッドを使用する。使用例var obj1 = { name: '太郎', age: 30, area: 'Tokyo' } var json = JSON.stringify(obj1); //-----ここまでJSONデータの準備----- var obj2 = JSON.parse(json); console.log(obj2); console.log(obj2.name);実行結果{name: "太郎", age: 30, area: "Tokyo"} 太郎
- 変数
obj1
に連想配列の形式で値を代入。(オブジェクトの作成)JSON.stringify(obj1)
を用いてエンコードし変数json
に代入。(JSONへ変換)
----------ここまでJSONデータの準備 (前項のエンコードと同様)----------JSON.parse(json)
を用いてデコードし変数obj2
に代入。(オブジェクトへ変換)console.log(obj2)
でオブジェクトをコンソールに表示。obj2.~
で各プロパティにもアクセス可能。実行結果を見ると、JSONデータがオブジェクトに変換されていることが分かる。
obj1
(オブジェクト) ⇒json
(JSON) ⇒obj2
(オブジェクト)2. JavaでJSONデータの取り扱い
Javaのオブジェクトはクラスを指し、JavaScriptのように簡単に作成できない。
JSONとオブジェクトを変換する場合、事前にJSONの変数に対応したプロパティを持つクラスを作成しておく必要がある。JavaでJSONを扱うなら、外部ライブラリを使用することを推奨。
⇒ Java標準APIにもJSONを扱うものは用意されているが、かなり手間がかかる。JSONを扱う外部ライブラリは以下のものが有名。
- Jackson
- GSON
- JSONIC
- JSON in Java などなど
基本の利用方法は似たようなものだが、今回は「Jackson」を使用する。
他ライブラリの使用方法や外部ライブラリの適用方法は、参考ページを見るか適宜ググってほしい。エンコード
オブジェクト ⇒ JSON
エンコードにはObjectMapper
クラスのwriteValueAsString()
メソッドを使用する。使用方法ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(オブジェクトインスタンス);使用例import java.io.IOException; import com.fasterxml.jackson.databind.ObjectMapper; //JSONに変換するプロパティを持つクラス class Info { public String name = "Taro Tanaka"; public int age = 30; } //エンコードを実行するクラス public class Main { public static void main(String[] args) { Info info = new Info();//JSONへ変換するクラスをインスタンス化 ObjectMapper mapper = new ObjectMapper();//ObjectMapperクラスのインスタンスを作成 try { //writeValueAsString()メソッドでエンコード実施 String script = mapper.writeValueAsString(info); System.out.println(script); } catch (IOException e) { e.printStackTrace(); } } }実行結果{"name":"Taro Tanaka","age":30}
Info
クラスにてJSONに変換するためのプロパティを作成する。- エンコードを実行する
Main
クラスを作成。Info info = new Info()
にてJSONに変換するオブジェクトをインスタンス化。ObjectMapper mapper = new ObjectMapper()
にてObjectMapper
クラスをインスタンス化。mapper.writeValueAsString(info)
にてオブジェクトからJSONに変換。
このとき、try,catchでのエラー対応が必要。System.out.println(script)
でJSONデータをコンソールに出力。実行結果を見ると、オブジェクトクラスがJSON形式に変換されていることが分かる。
各プロパティの変数名と値がペアとなって格納されている。デコード
JSON ⇒ オブジェクト
デコードにはObjectMapper
クラスのreadValue()
メソッドを使用する。使用方法ObjectMapper mapper = new ObjectMapper(); mapper.readValue(JSONデータ,オブジェクトクラス.class);使用例import java.io.IOException; import com.fasterxml.jackson.databind.ObjectMapper; //JSONから変換されるプロパティを持つクラス class Info { public String name; public int age; } //デコードを実行するクラス public class Main { public static void main(String[] args) { String script = "{ \"name\":\"Taro Tanaka\", \"age\":30}";//文字列としてJSONデータを作成 ObjectMapper mapper = new ObjectMapper();//ObjectMapperクラスのインスタンスを作成 try { //readValue()メソッドでデコード実施 Info info = mapper.readValue(script, Info.class); System.out.println(info.name); System.out.println(info.age); } catch (IOException e) { e.printStackTrace(); } } }実行結果Taro Tanaka 30
Info
クラスにてJSONから変換されるプロパティを持つクラスを作成する。- デコードを実行する
Main
クラスを作成。- ここではStringの文字列としてJSONデータを用意する。
ObjectMapper mapper = new ObjectMapper()
にてObjectMapper
クラスをインスタンス化。mapper.readValue(script, Info.class)
にてJSONからオブジェクトに変換。
このとき、try,catchでのエラー対応が必要。System.out.println(info.~)
でオブジェクトプロパティをコンソールに出力。実行結果を見ると、JSONデータがオブジェクトクラスに変換されていることが分かる。
デコード後は指定したオブジェクトの各プロパティにアクセスすることで値を取得することができる。3. ソケット通信におけるJSONデータの送受信方法
サーバープログラム:Java
クライアントプログラム:JavaScriptソケット通信を行う際、JSONでの送受信方法を記述する。
JavaとJavaScriptで扱い方が異なるため、それぞれ解説する。JavaScriptでのJSON送受信
JavaScriptに関しては特筆すべきことはない。
前述した通り、JSON.stringify()
メソッド及びJSON.parse()
メソッドで簡単にオブジェクト ⇔ JSON間の変換が可能。
クライアントから送信前にエンコード、受信後にデコードすれば問題無い。送信時エンコード
オブジェクト ⇒ JSON
送信時var obj = { type:'A' , msg:'a' }; var json = JSON.stringify(obj); socket.send(json);
- オブジェクトを用意。
JSON.stringify()
メソッドでJSONへエンコード。- WebSocketの
send()
メソッドでJSONデータ送信受信時デコード
JSON ⇒ オブジェクト
受信時socket.onmessage = function(e) { var obj = JSON.parse(e.data); };
onmessage
でJSONデータを受信。JSON.parse(e.data)
でオブジェクトへデコードJavaでのJSON送受信
Javaのソケット通信時にJSONを用いる場合、JavaScriptのように簡単にはいかない。
⇒ エンコーダー、デコーダー、オブジェクトクラスの3つを用意する必要がある。送信時エンコード
オブジェクト ⇒ JSON
ソケット通信でJSONデータを送信する場合、エンコーダーを用いてオブジェクトからJSONに変換してから送信する。
通常、テキストデータを送信する場合には
sendText()
メソッドを使用するが、JSONを送信する場合はsendObject()
メソッドを使用する。
引数をオブジェクトとし、エンコーダーによってJSONに変換したのち送信する。
- 送信するオブジェクトを作成する。
- エンコーダーを作成する。
- @ServerEndpointアノテーションにエンコーダーを登録する。
1. 送信するオブジェクトを作成
通常のクラスと同様にプロパティを持つクラスを作成する。
コンストラクタ、セッター、ゲッターも通常通り記述。(無くても変換可能。)オブジェクトクラスpublic class JsonObj { private String type = "type1"; private String msg = "msg1"; //コンストラクタ public JsonObj() {} //セッター public void setType(String type) {this.type = type;} public void setMsg(String msg) {this.msg = msg;} //ゲッター public String getType() {return type;} public String getMsg() {return msg;} }2. エンコーダーを作成
エンコーダーは
javax.websocket
パッケージのEncoder.Text
クラスを実装する。
(Encoder.Binary
クラスも存在するが、バイナリデータを扱うためのクラスなので割愛)
Encoder.Text<>
のジェネリクスにはエンコードするオブジェクトクラスを記述しておく。エンコーダーimport javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonEncoder implements Encoder.Text<JsonObj>{ @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//エンコード処理 ( オブジェクト → JSON ) public String encode(JsonObj obj) throws EncodeException { ObjectMapper mapper = new ObjectMapper(); String json = ""; try { json = mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { e.printStackTrace(); } return json; } @Override//破棄は何もしない public void destroy() {} }
Encoder.Text
クラスを継承した場合、以下のメソッドをオーバーライドする必要がある。
init(EndpointConfig config)
:エンコーダーが起動したときの処理。encode(Object obj)
:エンコード処理。destroy()
:エンコーダが破棄された時の処理。基本的に
init()
及びdestroy()
は何も処理しなくてよい。
encode(Object)
にて引数をオブジェクトとし、エンコード後に戻り値としてJSONを指定。3. エンコーダーを登録
@ServerEndpoint//エンコーダークラスを指定 @ServerEndpoint(value = "/json" , encoders = JsonEncoder.class) public class JsonTest { //中略 //sendObject()の引数はオブジェクト //送信前にエンコードされる session.getAsyncRemote().sendObject(obj) //中略 }エンコーダーを利用するとき、
@ServerEndpoint()
にエンコーダークラスを指定する。
ここで指定しておくことで、オブジェクト送信時に指定したエンコーダーによってJSONデータへ変換される。
sendObject(obj)
⇒ エンコード処理 (obj→JSON) ⇒ 送信受信時デコード
JSON ⇒ オブジェクト
ソケット通信でJSONデータを受信する場合、デコーダーを用いてJSONからオブジェクトに変換してから受信する。
通常は
onMessage()
メソッドの引数にString型文字列として受信するが、デコーダーを用いることで受信前にデコード処理が実施され、オブジェクトとして受信することとなる。
- 受信するJSONの変換先オブジェクトを作成する。
- デコーダーを作成する。
- @ServerEndpointアノテーションにデコーダーを登録する。
1. 変換先のオブジェクトを作成
デコードする場合、JSONの要素に対応したプロパティを持つオブジェクトクラスを用意しておく必要がある。
複数のJSONを受信するとき内容や形式が異なるなら、それぞれのJSONに対応した複数のオブジェクトクラスを用意しなければならない。エンコードの場合、全てのオブジェクトは任意のタイミングでJSONに変換し送信することが可能
デコードの場合、受信するJSONの内容をあらかじめ把握し、デコード前に受け口となるオブジェクトクラスを用意しておかなければならない。オブジェクトクラスpublic class JsonObj { private String type; private String msg; //コンストラクタ public JsonObj() {} //セッター public void setType(String type) {this.type = type;} public void setMsg(String msg) {this.msg = msg;} //ゲッター public String getType() {return type;} public String getMsg() {return msg;} }2. デコーダーを作成
デコーダーは
javax.websocket
パッケージのDecoder.Text
クラスを実装する。
(Decoder.Binary
クラスも存在するが、バイナリデータを扱うためのクラスなので割愛)
Decoder.Text<>
のジェネリクスにはデコード先のオブジェクトクラスを記述しておく。デコーダーimport javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonDecoder implements Decoder.Text<JsonObj> { @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//デコードできるかの判定 public boolean willDecode(String text) { return (text != null); } @Override//デコード処理 ( JSON → オブジェクト) public JsonObj decode(String text) throws DecodeException { ObjectMapper mapper = new ObjectMapper(); JsonObj obj = null; try { obj = mapper.readValue(text, JsonObj.class); } catch (JsonProcessingException e) { e.printStackTrace(); } return obj; } @Override//破棄は何もしない public void destroy() {} }
Decoder.Text
クラスを継承した場合、以下のメソッドをオーバーライドする必要がある。
init(EndpointConfig config)
:デコーダーが起動したときの処理。willDecode(String text)
:デコード処理を実行するかどうかの判定。decode(Object obj)
:デコード処理。destroy()
:デコーダが破棄された時の処理。基本的に
init()
及びdestroy()
は何も処理しなくてよい。
willDecode()
の引数をJSONとし、戻り値がtrue
なら以下のデコードを実行。false
ならデコードされず以降の@OnMessage
メソッドは実行されない。
decode()
にて引数をJSONとし、エンコード後に戻り値としてオブジェクトを指定。3. デコーダーを登録
@ServerEndpoint//デコーダークラスを指定 @ServerEndpoint(value = "/json" , decoders = JsonDecoder.class) public class JsonTest { //中略 //@OnMessageメソッドの前にデコードされる @OnMessage public void onMessage(JsonObj obj , Session mySession) { } //中略 }デコーダーを利用するとき、
@ServerEndpoint()
にデコーダークラスを指定する。
ここで指定しておくことで、データ受信時に指定したデコーダーによってオブジェクトへ変換される。
⇒@OnMessage
メソッドの引数はオブジェクト型となる。(通常はString型)
- 受信 ⇒ デコード処理 (JSON→obj) ⇒
@OnMessage
メソッド4. 複数パスのチャットアプリ作成
過去に作成したチャットアプリを改変する。
変更点
- 送受信するデータをテキストからJSONへ変更 (JSONの使用)
- チャット欄を1つから2つへ増加 (複数パスの実現)
WebSocket通信ではデータを受信するための受け口が1つしかない。
つまりワンパスのため複数の送信元があっても見分けがつかない。
もし見分けるなら、文字列の内容を分解して区別する他無いだろう。
どうせデコードするのならJSONを扱うのが都合がよい。実際は 1.テキスト 2.バイナリ 3.PingPong と3種類の受信メソッドがあるが、ここではテキスト形式のみを扱うため受け口は1つと考える。
作成ファイル
- JsonIndex.html:ブラウザ表示用のHTMLファイル
- JsonSocket.js:ソケット通信のクライアントプログラム
- JsonTest.java:ソケット通信のサーバープログラム
- JsonObj.java:JSONと相互変換するオブジェクトクラス
- JsonEncoder.java:Javaエンコーダー
- JsonDecoder.java:Javaデコーダー
1. 表示用HTML
JsonIndex.html<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>JSON送受信</title> <script type="text/javascript" src="JsonSocket.js"></script> </head> <body> <div style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;" id="show1"></div> <input type="text" size="80" id="msg1" name="msg1" /> <input type="button" value="送信" onclick="sendMsg1();" /> <p></p> <div style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;" id="show2"></div> <input type="text" size="80" id="msg2" name="msg2" /> <input type="button" value="送信" onclick="sendMsg2();" /> </body> </html>ポイント
1. チャット欄を2つに増加した。
表示枠はそれぞれshow1
とshow2
、テキストボックスはmsg1
とmsg2
とした。
後述するJavaScriptファイルでこれらを操作する。2. クライアントプログラム
JsonSocket.js//JSON用のオブジェクト作成 var obj = { type:null , msg:null }; //WebSocketオブジェクト生成 var wSck= new WebSocket("ws://localhost:8080/jsonTest/json"); //ソケット接続時のアクション wSck.onopen = function() { document.getElementById('show1').innerHTML += "接続しました。" + "<br/>"; document.getElementById('show2').innerHTML += "接続したよ~" + "<br/>"; }; //メッセージを受け取ったときのアクション wSck.onmessage = function(e) { //JSONデータをオブジェクトへデコード var json = JSON.parse(e.data); //JSONデータのtype値によって実行内容を変更 if(json.type === 'msg1'){document.getElementById('show1').innerHTML += json.msg + "<br/>";} else if(json.type === 'msg2'){document.getElementById('show2').innerHTML += json.msg + "<br/>";} }; //メッセージ送信1 var sendMsg1 = function(val) { var element = document.getElementById('msg1') obj.type = element.name;//オブジェクトの内容を代入 obj.msg = element.value; var json = JSON.stringify(obj);//オブジェクトをJSONへエンコード wSck.send(json);//JSONを送信 element.value = "";//内容をクリア }; //メッセージ送信2 var sendMsg2 = function(val) { var element = document.getElementById('msg2'); obj.type = element.name; obj.msg = element.value; var json = JSON.stringify(obj); wSck.send(json); element.value = ""; };ポイント
1. JSON用のオブジェクトobj
を作成 ⇒ これがJSONへ変換される。
2. ソケット接続時のアクションをチャット欄増加に合わせて追加。
3. メッセージ受信時にJSONをオブジェクトへデコードし、type値によって処理内容を変更。
4. メッセージ送信1,2:obj
に値を代入後、サーバーへの送信前にJSONへの変換を実施。3. サーバープログラム
JsonTest.javapackage jsonTest; import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; // 引数にデコーダー、エンコーダーを記述 @ServerEndpoint(value = "/json" , decoders = JsonDecoder.class , encoders = JsonEncoder.class) public class JsonTest { private static Set<Session> user = new CopyOnWriteArraySet<>(); @OnOpen public void onOpen(Session mySession) { System.out.println("connect ID:"+mySession.getId()); user.add(mySession); } //このメソッドの前にデコードされる @OnMessage public void onMessage(JsonObj obj , Session mySession) { for (Session user : user) { user.getAsyncRemote().sendObject(obj);//送信するものはオブジェクト(送信前にエンコードされる) System.out.println(user.getId()+"番目に"+mySession.getId()+"番目のメッセージを送りました!"); } if(obj.getMsg().equals("bye")) {onClose(mySession);} } @OnClose public void onClose(Session mySession) { System.out.println("disconnect ID:"+mySession.getId()); user.remove(mySession); try { mySession.close(); } catch (IOException e) { System.err.println("エラーが発生しました: " + e); } } }ポイント
1.@ServerEndpoint
にデコーダー及びエンコーダーのクラスを指定。
2. クライアントからデータを受信したとき、@OnMesaage
メソッドが実行される前に指定したデコーダーが実行されJSONからオブジェクトに変換される。
⇒@OnMesaage
メソッドの引数はObj
型となる。
3.sendObject(obj)
メソッドでオブジェクトを送信するとき、クライアントへ送信する前に指定したエンコーダーが実行されオブジェクトからJSONに変換される。
※ 文字列を送信する場合はsendText()
を使用するが、オブジェクトを送信する場合はsendObject()
を使用する。4. オブジェクトクラス
JsonObj.javapackage jsonTest; public class JsonObj { private String type; private String msg; //コンストラクタ public JsonObj() {} //セッター public void setType(String type) {this.type = type;} public void setMsg(String msg) {this.msg = msg;} //ゲッター public String getType() {return type;} public String getMsg() {return msg;} }ポイント
1. このクラスのプロパティはtype
とmsg
の2つ。
⇒ クライアントプログラムのオブジェクト (受信するJSON) に対応したクラスを作成している。
2. デコード後、type
プロパティ値によって送信元を判断する。5. エンコーダー
JsonEncoder.javapackage jsonTest; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonEncoder implements Encoder.Text<JsonObj>{ @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//エンコード処理 ( オブジェクト → JSON ) public String encode(JsonObj obj) throws EncodeException { ObjectMapper mapper = new ObjectMapper(); String json = ""; try { json = mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { e.printStackTrace(); } return json; } @Override//破棄は何もしない public void destroy() {} }ポイント
1.Encoder.Text<JsonObj>
を実装したクラスを作成。
2.encode()
メソッドの引数はオブジェクト、戻り値はJSONとなる。
3. エンコードにはObjectMapper
クラスのwriteValueAsString()
メソッドを使用する。6. デコーダー
package jsonTest; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonDecoder implements Decoder.Text<JsonObj> { @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//デコードできるかの判定 public boolean willDecode(String text) { return (text != null); } @Override//デコード処理 ( JSON → オブジェクト) public JsonObj decode(String text) throws DecodeException { ObjectMapper mapper = new ObjectMapper(); JsonObj obj = null; try { obj = mapper.readValue(text, JsonObj.class); } catch (JsonProcessingException e) { e.printStackTrace(); } return obj; } @Override//破棄は何もしない public void destroy() {} }ポイント
1.Decoder.Text<JsonObj>
を実装したクラスを作成。
2.decode()
メソッドの引数は文字列(JSON)、戻り値はオブジェクトとなる。
3. エンコードにはObjectMapper
クラスのreadValue()
メソッドを使用する。実行結果
チャット欄が2つに増加した。
各チャット欄で独立して送受信が可能。
⇒ 複数パスの実現が完了。実行順序
長くなったので、クライアントとサーバーでどのような処理が行われているか記述する。
- HTMLでページを表示。
- ハンドシェイク完了後、ソケット通信開始。
- テキストボックスにメッセージを記述し送信ボタンをクリック。
- JavaScriptによりオブジェクトをJSONにエンコードしサーバーへ送信。
- サーバーでJSONデータを受信。
- デコーダーによりJSONからオブジェクトへデコード。
- デコードされたオブジェクトを引数に
@OnMessage
メソッドを実行。sendObject()
メソッドでオブジェクトを送信。- 送信前にエンコーダーによりオブジェクトからJSONへエンコードし、クライアントへ送信。
- クライアントでデータ受信。
- JSONデータをオブジェクトへデコード。
type
プロパティ値によって送信元を判別し、各送信元へメッセージを表示。その他
改善点
・未知のJSONデータを受信すると、対応したオブジェクトクラスを用意していないためエラーとなる。
⇒ 対応している人はいるので、そのうち調べるかも。結構ややこしそう。・複数の型のJSONデータを受信する場合に未対応。
⇒ そこまで難しくなさそう。デコーダーとオブジェクトクラスを増やしてJSONの中身に対応すればできる。・入れ子構造のJSONに対応するにはどういうオブジェクトクラスを作成すればいいか不明。
⇒ ググれば出るし、必要となれば調べる。・バイナリデータの扱いは理解が進んでいないため、バイナリデータの送受信には未対応。
⇒ 画像の送受信など、必要となれば調べる。感想
思った以上にややこしかった。長くなってしまったので反省。
もっとまとめるべきだった。JavaでJSONを扱うのに、外部ライブラリを使用したりオブジェクトクラスを用意したりと面倒。
JavaScriptなら1行で終わるのにね。Javaのソケット通信でJSONを扱うときに、エンコードクラスやデコードクラスが用意されているのは便利なのかどうなのか。
わざわざエンコーダークラスやデコーダークラスを用意しなくちゃいけないのは、面倒といえば面倒。面倒だが、JSONを使いたいならしょうがない。
JavaでのJSONの扱い方について理解が深まって良かった。
個人制作レベルなら利用していけそう。参考ページ
- 投稿日:2020-06-04T23:31:11+09:00
○ヶ月から、○年○ヶ月に変換する
概要
月数から、年数と残りの月数を求める方法です。(Javaで書いてます)
35ヶ月は2年11ヶ月ですこのような感じ。
変換方法
コードはこんな感じ。
YearMonth.javapublic class YearMonth{ public static void main(String args[]){ int monthCount = 35; System.out.println(monthCount + "ヶ月は" + monthCount/12 + "年" + monthCount%12 + "ヶ月です"); } }変数monthCountに格納された35ヶ月が、年月に変換されています。
年とヶ月は別々で計算しました。
年の方は、35ヶ月を12ヶ月(1年)で割り、Math.floorで小数点を切り捨て。
そしてそのままだと小数点が.0と付いてしまったので、前に(int)を置いて整数に。ヶ月の方は、35ヶ月から12ヶ月(1年)を割った余りにすることで計算できました。
- 投稿日:2020-06-04T20:18:52+09:00
JavaによるWebアプリケーション構造とプレゼンテーション層における処理の流れ
はじめに
新入社員研修において、Javaを用いたWebアプリケーションの開発を行ったので、そこで学んだことを簡単にまとめていきたいと思います。
間違い等も多くあると思いますので、ご指摘してくださると嬉しいです。Webアプリケーションのサーバーサイドにおける処理の流れを中心に取り上げていきます。想定しているサンプルとしては、簡単な1件検索のサンプルになります。
サーバ間のやりとり
動的なWebページを実装する場合のクライアントとサーバー間における動的な情報のやりとりについて簡単に説明します。
まず、クライアントからWebサーバーにリクエストを送信します。このクライアントとWebサーバー間の送受信はHTTPプロトコルによって行われております。動的なWebページを表示する場合、Webサーバー側で何らかの処理(プログラム)を行う必要があります。その処理がWebアプリケーションであり、それをJavaやPHPなどのサーバーサイド言語で実装していくことになります。
今回はJavaでの実装を想定としています。そのアプリケーションを実行するためには、Webサーバーとは別にアプリケーションサーバーを用意する必要があります。
( JavaのアプリケーションサーバーではTomcatが有名です。)
アプリケーションサーバーも、クライアントとWebサーバーの送受信と同様に、Webアプリケーションと送受信を行うには、あるプロトコルが必要になります。上記のようにアプリケーションサーバーにはTomcatを使用して、WebサーバーにはApatchを使用する場合、Apatchにmod_jkという連携用モジュールを拡張機能として実装します。
そのmod_jkのajp13というプロトコルを使用してアプリケーションサーバーとWebサーバーは送受信を行うことができます。
Tomcatは簡易的なWebサーバーを保有しているため、特に連携しなくても送受信は可能です。Webアプリケーションの構造
では、そのサーバーサイドでどのようにWebアプリケーションが処理されているのかを見ていきます。
WebアプリケーションはMVCアーキテクチャをモデルとして設計されており、大きく分けて次の3つの階層に分けて設計されています。
・プレゼンテーション層
・ビジネス層
・インテグレーション層それぞれの層に含まれるプログラムの役割は明確に分けられています。
次に、クライアントからのリクエスト情報を元にどのように処理がなされているのかを記します。
サーバーサイドにおけるアプリケーションの処理の流れ
リクエスト情報は、まずFrontControllerに渡されます。このFrontControllerはサーブレットであり、Webアプリケーションの処理を一元管理しています。
そのFrontControllerはリクエスト情報に応じて処理を振り分けます。(switch-case文が有効)
その中でActionを呼び出し、そのActionが入力値のチェックやセッション管理(セッションオブジェクトの生成や解放)、ビジネス層の業務Logicへの処理依頼を行います。
(余談ですが、セッション管理ではサーバリソースを有効に活用するために一定時間アクセスが途絶えたらセッションを無効化するなど、出来るだけ早く解放するほうが良いそうです)そして、肝となる業務Logicでは業務処理を制御します。
業務処理に必要な情報は、データベースへのアクセスによって取得します。
データベースへアクセスするためにはConnectionManagerを用います。具体的には、ConnectionManagerのgetConnectionメソッドを用いてConnectionオブジェクト(データベースへの接続)を取得します。そのConnectionオブジェクトをDAOのコンストラクタの引数に渡して、DAOオブジェクトを生成します。
そして、DAOのメソッドを呼び出し、Entityを用いて戻り値を受け取ります。
DAO内の戻り値の設定処理方法(1件検索の場合)
while(res.next())で検索結果がある場合に{}内の処理を行う。{}内ではres.get~を用いて戻り値を設定する
業務LogicがEntityのオブジェクトを受けとった後、定義したオブジェクトに格納し、戻り値に設定します。
Actionにおいて業務Logicを生成し、メソッドを呼び出します。その結果をスコープなどに設定します。
戻り値としては遷移ページ先を設定します。FrontControllerにおいてActionから遷移ページ先を受け取り、フォワードします。
プレゼンテーション層におけるアプリケーションの動作
サーブレットとJSP
Webページを表示するHTMLファイルを生成するためにはサーブレットというプログラムが必要になります。サーブレットはTomcatなどのWebアプリケーションサーバーによって実行されます。
このサーブレット単体でもWebページは表示させることは可能なのですが、プログラムの記述が煩雑になってしまい、綺麗なプログラムではなくなってしまうので、HTMLにJavaコードを埋め込んだJSPにWebページの表示を依頼することになります。
また、Webページを遷移させていくためには、クライアントが送信したリクエストにパラメータを付加させる必要があります。
そのリクエストの送信方法としてはGETとPOSTの二種類があります。GETとPOST
GETとPOSTのどちらの方法でも、任意の値をパラメータとして設定する必要があります。
GETオペレーションでは、パラメータに設定した任意の値がURLに組み込まれる形でWebサーバーに送信されます。
ゆえに、Webページを再度閲覧したいという場合は簡単にアクセスすることができます。
つまり、ブックマークはパラメータごとに保存がされているという状況になります。
デメリットとしてはパラメータの長さに制限があることや、上記のようにセキュリティ面が低いことが挙げられます。一方、POSTオペレーションはパラメータに設定した任意の値はURLに組み込まれず、リクエストのメッセージボディに格納される形でWebサーバーに送信されます。
ゆえに、セキュリティ面ではGETオペレーションより高いです。また、パラメータの長さにも制限がなく、大量のデータを送信することができます。アプリケーションでの処理ではユーザー定義サーブレットで、HttpServletにおいて定義されているdoGetメソッドとdoPostメソッドをオーバーライドする形で記述します。そのことによりHTTPの機能を使用することができます。
まとめ
今回はサーバーサイドにおけるWebアプリケーションの動作についてとプレゼンテーション層におけるアプリケーションの動作について簡単に解説しました。
これ以上は長くなってしまうので、ビジネス層とインテグレーション層に関しては別記事で解説できたらと思います。
- 投稿日:2020-06-04T20:18:29+09:00
HashMap のvalue で値をソートしたい.
javaでMap(HashMap)をvalueを使ってソートする際のやり方をまとめておきます.
ここでは,B_arrayを使ってA_arrayの値も同時にソートしたい状況を考えています.
HashMapのentry型を持つリストをソートする形なので,TreeMapなど,他のMapでも使えます.コード
Solution.javaimport java.util.*; class Solution { public static void main(String[] args) { int[] A_array = {1,6,3,10,5}; int[] B_array = {8,21,13,4,5}; tryComperator(A_array, B_array); } public static void tryComperator(int[] a, int[] b){ Map<Integer, Integer> map = new HashMap<Integer, Integer>(); //配列の要素をhashmapに格納. for (int i = 0; i < a.length; i++){ map.put(a[i], b[i]); } //Hashmapの各要素をリストに格納. List<Map.Entry> mapValuesList = new ArrayList<Map.Entry>(map.entrySet()); //mapValuesList を出力してみる.(10=4 のような形で出力されるらしい.) for (Map.Entry v: mapValuesList){ System.out.println("map>> "+v+",key>> "+v.getKey()+"value>> "+v.getValue()); } //Comparatorクラスをインスタンス化 Comparator<Map.Entry> comparator = new Comparator<Map.Entry>() { public int compare(Map.Entry entry1, Map.Entry entry2) { return ((Integer) entry1.getValue()).compareTo((Integer) entry2.getValue()); } }; //comparator を使ってソートする. Collections.sort(mapValuesList, comparator); //確認のための出力 for (Map.Entry s : mapValuesList) { System.out.println(s); } } }出力
map>> 1=8,key>> 1value>> 8 map>> 3=13,key>> 3value>> 13 map>> 5=5,key>> 5value>> 5 map>> 6=21,key>> 6value>> 21 map>> 10=4,key>> 10value>> 4 10=4 5=5 1=8 3=13 6=21valueの昇順に出力されました.
- 投稿日:2020-06-04T19:41:24+09:00
Visual Studio Codespaces を使って Azure Functions の Java アプリケーションの Hello World まで
背景
Visual Studio Codespaces
が登場して、VDIのマシンを立てずとも、Azure
上の仮想マシンを使ってのコーディングがやり易くなりました。しかもWindows
とLinux
が選択できます。Visual Studio Code
との連携がシームレス過ぎて感動さえ覚えます。Visual Studio Codespaces:
https://visualstudio.microsoft.com/ja/services/visual-studio-codespaces/Visual Studio Codespaces - document:
https://docs.microsoft.com/ja-jp/visualstudio/online/overview/what-is-vsonline
- 執筆時点 (2020/6/5) は Public Preview です!
一方、
Azure Functions
は Serverless で人気のホスティング環境です。Azure Functions:
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-overviewその実行エンジンはGitHub上で開発されており、オンプレミスだけでなく、AWS や GCP、あるいは k8s 上などあらゆる環境にホスティングして動かす事ができます。
https://github.com/Azure/azure-functions-host
ここでは、それらを使っての Java アプリケーションの Hello World までの手順をまとめておきます。
ローカルに Java でさえインストールしていない点にご注意ください?必要なもの
ローカルに
- Visual Studio Code
https://code.visualstudio.com/
- Visual Studio Code Remote Development Extension Packhttps://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack
Azure Subscription
手順
1. Visual Studio Codespaces の環境作成
こちらのドキュメントに従って作成してください?
Visual Studio Codespaces Quickstart:
https://docs.microsoft.com/ja-jp/visualstudio/online/quickstarts/browser数十秒で作成されます!
Container ベースであることも読み取れて興味深いですね。2. Visual Studio Codespaces に Visual Studio Code から接続
Visual Studio Codespaces はデフォルトでブラウザー内での作業になることが多いです。ですが、私はローカルでのコーディングが好きなので?、ここからはローカルの Visual Studio Code で作業をしていきます。
- Visual Studio Codespaces のサイトに移動します
https://online.visualstudio.com/
- 作成したインスタンスを選んで [Open in VS Code] を選びます。
- ブラウザーから、Visual Studio Code を開こうとしているというメッセージが出てきます。[開く]で先に進みます。
こんな形で、リモートに接続ができます。
- もし認証がうまくできないなどで、接続がうまくいかない場合は、画面左下の緑の[Codespace: <インスタンス名>]をクリックして、[Codespaces: Open Codespace in New Window]にて、別のVisual Studio Codeを起動してみてください.
3. 開発に必要なモジュールを Visual Studio Codespaces にインストール
開発に必要な幾つかのモジュールが入っていません。ターミナル から、それらをインストールします。
- Visual Studio Code でターミナルを開きます 実際にはリモートのContainerに接続しているんですよね?
3.1. Maven
sudo apt-get install maven3.2. Azure Functions Core Tools
Visual Studio Codespaces 上で Azure Functions を実行するためですね。リモートでのローカル実行になります?
sudo apt-get install azure-functions-core-toolsAzure Functions Core Tools の操作:
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=linux%2Ccsharp%2Cbash3.3. Azure Functions - Visual Studio Code Extension
Visual Studio Code の Extension です。Visual Studio Codespaces の方にインストールします。
これで、Visual Studio Code のアクティビティバーに Azure のアイコンが表示されます。
3.4. Java Extension Pack - Visual Studio Code Extension
Visual Studio Code の Java 用の各種 Extension です。デバッグのためですね。
これで、ほぼ既存の Azure Functions のドキュメントの手順の通り作業ができます?
4. Azure Functions のローカルプロジェクトの作成
手順はこちらですね。
ちなみに、以下の様なJavaのコードのひな型を作成してくれます。
/src/main/java/com/function/Function.java
package com.function; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.HttpMethod; import com.microsoft.azure.functions.HttpRequestMessage; import com.microsoft.azure.functions.HttpResponseMessage; import com.microsoft.azure.functions.HttpStatus; import com.microsoft.azure.functions.annotation.AuthorizationLevel; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; import java.util.Optional; /** * Azure Functions with HTTP Trigger. */ public class Function { /** * This function listens at endpoint "/api/HttpExample". Two ways to invoke it using "curl" command in bash: * 1. curl -d "HTTP Body" {your host}/api/HttpExample * 2. curl "{your host}/api/HttpExample?name=HTTP%20Query" */ @FunctionName("HttpExample") public HttpResponseMessage run( @HttpTrigger( name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request, final ExecutionContext context) { context.getLogger().info("Java HTTP trigger processed a request."); // Parse query parameter final String query = request.getQueryParameters().get("name"); final String name = request.getBody().orElse(query); if (name == null) { return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build(); } else { return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build(); } } }5. Visual Studio Codespace 上で、Azure Functions のコードをデバッグする
ドキュメントの通りですと、F5 でローカルでAzure Functionsが起動します。ここでは、Visual Studio Codespace 側、つまり、リモートで起動させます。
- [ターミナル] を起動して、以下のコマンドを発行します。
mvn clean packagemvn azure-functions:runで、見慣れた Azure Functions の実行画面に。
これで、Visual Studio Codespaces 上で、Azure Functions が起動しました!
ちなみに、debug実行する際には以下のコマンドになります。
ブレークポイントが動いていませんが...?mvn azure-functions:run -DenableDebug
6. Visual Studio Codespace 上の Azure Functions にアクセスする
Azure Functions Core Tools がデフォルトで作成してくれる アクセス用のURLである http://localhost:7071/api/HttpExample は、自分のPCからそのままではアクセスできませんよね。
Visual Studio Codespaceのインスタンス上の Azure Functionsに接続するため、ここでは、Visual Studio Code の Remote Extensionに入っているポートフォワーディングを利用します。
- アクティビティーバーの [リモートエクスプローラー] を選択します。
- [Forward port...] を選択し、ローカルでのポート番号を入れます。Azure Functions Core Tools が設定してくれたポート番号である[7071]を設定します。
- 作成した Forward Ports を選択すると、URLをコピーするアイコンが表示されます。
この文字列がクリップボードにコピーされます。
http://127.0.0.1:7072/そのままブラウザーのURLに張り付けてコピーすると、Azure Functions が稼働していることが確認できます。
このサンプルは、Visual Studio Codespaces 側では以下のURLで待っています。
http://localhost:7071/api/HttpExampleこれを以下の様に書き換えます。
QueryStringの name の先はどんな文字列でも良いです?http://127.0.0.1:7072/api/HttpExample?name=Hello-VisualStudioCodespace
Azure Functions が呼び出せましたね!
まとめ
Visual Studio Codespaces が、Azure上のリモート環境という事を忘れなければ、普段の開発に近い形で行えると思います。
- Container の 作成・削除は容易
- 追加モジュールもインストール可能
- アイドル時間設定で、勝手に止めてくれる
是非、楽しんでください!
参考
Visual Studio Codespaces VS Code How-to:
https://docs.microsoft.com/ja-jp/visualstudio/online/how-to/vscodeMaven Plugin for Azure Functions:
https://docs.microsoft.com/ja-jp/java/api/overview/azure/maven/azure-functions-maven-plugin/readme?view=azure-java-stable#azure-functionsrunAzure Functions の Java 開発者向けガイド:
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-java?tabs=consumptionVisual Studio Code での Remote 環境での開発:
https://code.visualstudio.com/docs/remote/remote-overview
- 投稿日:2020-06-04T17:21:07+09:00
Javaで"cls"みたいなことをしたい
JavaでWindowsの
cls
やLinuxのclear
みたいなことをしたいときのプログラム。Javaで外部コマンドを実行するときに、それぞれのコマンドを指定することで、それっぽくしてます。
/** * コンソールをクリアする(Windows,Linuxのみ) */ public void executeCls(){ // OSの確認 String osName = System.getProperty("os.name").toLowerCase(); try { // OSがWindowsの時 if(osName.startsWith("windows")) { new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor(); // OSがLinuxの時 }else if(osName.startsWith("linux")) { new ProcessBuilder("clear").inheritIO().start().waitFor(); }else { // Windows, Linux以外のOSの場合 System.out.println("コンソールはクリアされません。"); } }catch(IOException e) { e.printStackTrace(); }catch(InterruptedException e) { e.printStackTrace(); } }
- 投稿日:2020-06-04T16:09:57+09:00
Play Framework 2.8 (Java) からSlackに投稿する
概要
Play Framework 2.8でSlack APIを使用する方法です。
シンプルな投稿機能の実装をします。SlackのWeb APIを簡単に利用できるSlack公式のJava用クライアントを使います。
公式の記事にある内容をPlay Frameworkで実践しようという趣旨の記事です。
https://slack.dev/java-slack-sdk/guides/ja/web-api-basics下準備
プロジェクトの作成
省略します。
Play FrameworkのJava用テンプレートを使い、空のプロジェクトを用意してください。依存性の記述
SlackのWeb APIをJavaで楽に扱うためのクライアントをSlack公式が用意しています。
これを先程作ったプロジェクトにインストールしていきます。↓セットアップについての公式ページ
https://slack.dev/java-slack-sdk/guides/ja/web-api-client-setup公式ページではMavenを使ってプロジェクトにインストールする方法を紹介していますが、Play FrameworkではMaven2を使用しており、簡単な記述でインストールを行うことができます。
プロジェクトファイルの一番上のディレクトリにある
build.sbt
を開き、以下の依存性を追記します。build.sbtlibraryDependencies += "com.slack.api" % "slack-api-client" % "1.0.8"依存性の内容は、左からグループID・アーティファクトID・バージョンですので、必要に応じてしてください。
Play Frameworkプロジェクトでの準備は以上です。
初回のコンパイル時にクライアントのダウンロードやインストールも自動でやってくれます。
あとはjavaファイル中のimport文でクライアントを使えるようになります。トークンの取得
今度はSlack側の設定です。
トークンはSlackのワークスペースから発行されるもので、いわば「それを持ってたらSlackに投稿してもいいよ、という許可証」です。
この文字列があればSlackに投稿できるようになります。詳細は省きますが、
- 新しいアプリケーションの作成
- 必要なトークン情報
- ワークスペースへのアプリケーションの追加
をすればトークンの発行が可能です。
トークンの種類として、ボットとユーザーがありますが投稿を試すだけならどちらでも構いません。
スコープにはchat:write
が必要です。以下の公式ページを参考にしてみてください。
https://slack.com/intl/ja-jp/help/articles/115005265703-%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90
https://slack.com/intl/ja-jp/help/articles/215770388-API-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E7%94%9F%E6%88%90%E3%81%A8%E5%86%8D%E7%94%9F%E6%88%90実装
今回は簡単のために、起動時に実行される
HomeController.java
のindexメソッドに記述しています。
実際のアプリケーション作成のときはモデルかなんかに書きましょう。HomeController.javapackage controllers; import com.slack.api.model.Message; import play.mvc.*; import com.slack.api.Slack; import java.io.IOException; import com.slack.api.methods.response.chat.ChatPostMessageResponse; import com.slack.api.methods.MethodsClient; import com.slack.api.methods.request.chat.ChatPostMessageRequest; import com.slack.api.methods.SlackApiException; public class HomeController extends Controller { public Result index() { Slack slack = Slack.getInstance(); String token = "トークンの文字列"; MethodsClient methods = slack.methods(token); ChatPostMessageRequest request = ChatPostMessageRequest.builder().channel("#general").text(":wave: Hi from a bot written in Java!").build(); try { ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request); if (response.isOk()) { Message postedMessage = response.getMessage(); System.out.println("成功"); } else { String errorCode = response.getError(); System.out.println("失敗1"+errorCode); } } catch (SlackApiException requestFailure) { System.out.println("失敗2"); } catch (IOException connectivityIssue) { System.out.println("失敗3"); } return ok(views.html.index.render()); } }トークンの文字列の部分には取得したトークンが入っていれば大丈夫です。
公式ページでは、環境変数に格納したトークンを取得する方式を取っています。
直接入力するのは、汎用性の観点でもセキュリティの観点でもアウトなので実際のアプリケーションでは絶対にやめましょう。流れとしては、
- Slackインスタンスの初期化
- トークンの格納
- リクエスト生成用のインスタンスにトークンをセット
- リクエストの生成
- (リクエストを送って)レスポンスを受け取る(例外処理付き)
って感じです。
Slack Web APIに沿ってリクエストを生成しているので、どうやってリクエストを生成しているかはわかりやすいと思います。
例外処理を書かないと実行時に注意されるので、公式ページを参考に書いてください。
うまくいけば実行時にコンソールに
成功
と表示され、指定したチャンネルに投稿されると思います。
失敗してしまう場合は公式ドキュメントを参考にエラーを確認してみてください。
自分の場合ははトークンがちゃんと設定できていなかった、投稿チャンネルの指定が間違っていたというエラーを吐かれていました。まとめ
- Slack APIのクライアントをPlay Frameworkで利用できるようにした。
- Play FrameworkからSlackにメッセージを投稿した。
- 投稿日:2020-06-04T13:12:56+09:00
CICS-Javaアプリケーションを動かす - (1)単純なサンプルアプリの稼働
はじめに
CICS Transaction Server for z/OS のアプリケーション開発言語と言えば、COBOL, PL/I, Asembler辺りが使われることが多いですが、ずいぶん前からJavaもサポートされています。つまり、CICSのアプリケーションをJavaで書けるということです。EXEC CICSコマンドに相当するJavaのクラスライブラリ(JCICS)が提供されるので、Javaのメソッドを使ってEXEC CICS LINKとかEXEC CICS STARTみたいな操作を行うことができます。
ここではCICS-Javaアプリのサンプル(OSGi準拠)を実際に動かすところをやってみます。※ちなみにCICS上でJavaEEアプリを動かすということもできます。
参考: CICS上でJavaEEアプリケーションを動かす関連記事
CICS-Javaアプリケーションを動かす - (1)単純なサンプルアプリの稼働
CICS-Javaアプリケーションを動かす - (2)Mavenによるビルド管理
CICS-Javaアプリケーションを動かす - (3)Gradleによるビルド管理環境情報
開発環境
Windows10
CICS Explorer V5.6(Beta)実行環境
z/OS V2.4
CICS Transaction Server for z/OS V5.6(Beta)CICSのJavaサポートについて
CICSのJavaサポートについて整理しておきます。
CICSではOSGi準拠のJavaアプリケーションがサポートされます。
Javaで実装したCICSアプリを動す場合、以下のような手続きが必要になります。ランタイム準備
Javaアプリケーションを実行するために、ターゲットのCICSリージョンにJVMSERVERという資源定義を作成しJavaの実行環境を用意しておきます。開発
開発作業はEclipseベースの無償ツール"CICS Explorer"で行います。
JCICSのライブラリーをimportしたJavaアプリを作成し、OSGiバンドル(jarの拡張のようなもの)という形でパッケージングします。(参考:OSGI概要 ) この時、CICSプログラムとして認識させたい先頭のロジックはmain関数で実装する必要があります。
いくつかのOSGi Bundleを"CICSバンドル"という単位にパッケージングします。
※CICS上ではLibertyも稼働させることができ、JavaEEアプリをCICS上のLibertyで稼働させることもできます。その場合JavaEEアプリはWARやEARの単位でパッケージされますが、その場合もそれらを"CICSバンドル"という単位にパッケージングするという考え方は同様です。デプロイ
CICSバンドルを実行環境のz/OSのUSS上に転送します。
CICSリージョンの資源定義でBUNDLE定義を作成し、USS上に転送したCICSバンドル(Javaアプリ)を認識させます。
CICSリージョンの資源定義でPROGRAM定義を作成し、BUNDLEに含まれるJavaクラスをCICSプログラムとして登録します。事前準備
開発環境
以下の辺りを参考にCICS ExplorerというEclipseベースのツールをダウンロードしてセットアップしておきます。
参考:Downloading and starting CICS Explorer
※このツールはCICS利用者は無償で使えるツールです。ホスト接続定義として、ターゲットとなるCICSが稼働するz/OSに対してFTP接続構成をしておきます。
実行環境
サンプルを稼働させるCICSリージョンを用意します。
ここでは、CT56B4A1という名前のリージョンを使うことにします。
そこに、Javaアプリケーションを稼働させるためのJVMServerという資源を追加します。JVMプロファイル
USS上にJVMプロファイルを準備します。(JVMServerに関するプロパティーは実質このUSS上のファイルに指定します。JVMSERVER資源定義ではこのJVMプロファイルのファイル名をポイントすることになります。)
サンプルが提供されているのでそれをコピーして使用します。/var/cicsts/cicsts56/CT56B4A1/JVMProfiles というディレクトリを作成し、そこに/usr/lpp/cicsts/cicsts56/JVMProfiles/DFHOSGI.jvmprofile(CICS導入ディレクトリ下に配置されているOSGi用のサンプル定義)をコピーします。
環境に合わせて適宜カスタマイズします。DFHOSGI.jvmprofile抜粋JAVA_HOME=/usr/lpp/java/J8.0_64/ WORK_DIR=/var/cicsts/cicsts56/CT56B4A1/work -Xms32M -Xmx256M -Xmso1M -Xgcpolicy:gencon -Xscmx256M -Xshareclasses:name=cicsts%g,groupAccess,nonfatal -Xtune:virtualized -Dcom.ibm.tools.attach.enable=no _BPXK_DISABLE_SHLIB=YESSIT
上のプロパティーファイルを配置したディレクトリを、SITパラメーター"JVMPROFILEDIR"に指定します。
JVMPROFILEDIR=/var/cicsts/cicsts56/CT56B4A1/JVMProfiles変更反映のためにリージョンを再起動します。
JVMServer定義
JVMSERVER資源定義を準備します。
製品提供のDFH$OSGIというグループにあるJVMSERVER定義"DFHJVMS"を適当なグループにコピーしてインストールします。OBJECT CHARACTERISTICS CICS RELEASE = 0730 CEDA View JVmserver( DFHJVMS ) JVmserver : DFHJVMS Group : TAG$OSGI DEScription : CICS JVM server to run OSGi samples Status : Enabled Enabled | Disabled Jvmprofile : DFHOSGI (Mixed Case) Lerunopts : DFHAXRO Threadlimit : 015 1-256 DEFINITION SIGNATURE DEFinetime : 06/02/20 17:28:17 CHANGETime : 06/02/20 17:28:17 CHANGEUsrid : CICSUSER CHANGEAGEnt : CSDApi CSDApi | CSDBatch CHANGEAGRel : 0730※Jvmprofile: DFHOSGIとなっていますが、これはSITのJVMPROFILEDIRに指定されたディレクトリ下のDFHOSGI.jvmprofileというファイルがJVMプロパティーファイルとして使用されることを意味します。
CEMT I JVMSERVERで見てEnableになっていればOK。
I JVMS STATUS: RESULTS - OVERTYPE TO MODIFY Jvm(DFHJVMS ) Ena Prf(DFHOSGI ) Ler(DFHAXRO ) Threadc(000) Threadl( 015 ) Cur(9965280)bundleファイル配置用ディレクトリ
bundleファイルを配置するディレクトリをUSS上に作成しておきます。
ここでは、/var/cicsts/cicsts56/CT56B4A1/bundles/
というディレクトリを作成しておくことにします。サンプルアプリ稼働確認
参考: Java samples: JCICS examples
サンプルプロジェクトの作成
CICS Explorerのメニューからウィンドウ-設定を選択して設定ウィンドウを開きます。
プラグイン開発-ターゲット・プラットフォームを選択し、追加をクリックします。
テンプレートで対象のCICSのバージョンを選択します。ここではCICS TS V5.6を選択。
Javaパースペクティブで、メニューからファイル-新規-その他を選択
サンプルの中身確認
このプロジェクトではいくつかのサンプルアプリが提供されていますが、そのうちの一番シンプルなHello Worldのソースを見てみます。
(com.ibm.cics.server.examples.helloプロジェクト(OSGiバンドル)に含まれるexamples.hello.HelloCICSWorld.java)HelloCICSWorld.javapackage examples.hello; import com.ibm.cics.server.CommAreaHolder; import com.ibm.cics.server.Task; public class HelloCICSWorld { public static void main(CommAreaHolder CAH) { Task t = Task.getTask(); if ( t == null ) System.err.println("HelloCICSWorld example: Can't get Task"); else t.out.println("Hello from a Java CICS application"); } }JCICSのcom.ibm.cics.server.Taskが使われています。
Task.outは端末出力のためのPrintWriterを意味するので、端末に対して単純な文字列を送信するロジックになっています。
参考: Javadoc - Task同プロジェクトのMETA-INF/MANIFEST.MFを確認してみます。
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Hello Plug-in Bundle-SymbolicName: com.ibm.cics.server.examples.hello Bundle-Version: 1.0.0 Bundle-RequiredExecutionEnvironment: J2SE-1.4, J2SE-1.5, JavaSE-1.6 Import-Package: com.ibm.cics.server;version="[1.0.0,2.0.0)" CICS-MainClass: examples.hello.HelloCICSWorld, examples.hello.HelloWorldCICS-MainClassの指定で、mainメソッドを持つクラスを指定しています。ここで指定されたクラスのmainメソッドがCICSプログラムとして認識可能です(プログラム定義と紐づけ可能)。
com.ibm.cics.server.examples.bundleというCICSバンドルプロジェクトを確認します。
META-INF/cics.xmlをCICSバンドルマニフェストエディターで開くと、上のOSGiバンドルが含まれていることが確認できます。
com.ibm.cics.server.examples.hello.osgibundleを開きます。
ここに指定されているjvmserverの値を確認します。ここには稼働させるJVMSERVER定義の名前を指定する必要があります。事前準備の所で作成したJVMSERVER定義の名前に合わせて修正します。サンプル定義を同名でコピーしている場合はDFHJVMSになっていると思うので変更は不要です。
アプリケーションをCICSへのデプロイ
CICSへのデプロイは、"CICSバンドル"の単位で行います。
com.ibm.cics.server.examples.bundleプロジェクトを右クリックしてz/OS UNIXファイルシステムへのバンドル・プロジェクトのエクスポートをクリック
ファイルシステム内の特定のロケーションにエクスポートを選択して次へ
先に作成しておいたbundleファイル配置用のディレクトリを指定して終了
メッセージを確認
バンドルファイルがUSS上に配置されました。CICS提供のDFH$OSGIグループの各リソースを適当なグループにコピーします。
BUNDLE定義"DFH$OSGB"をカスタマイズして、BUNDLEDIRの値を、上でバンドルファイルを配置したディレクトリ(/var/cicsts/cicsts56/CT56B4A1/bundles/com.ibm.cics.server.examples.bundle_1.0.0
)に置き換えます。OVERTYPE TO MODIFY CICS RELEASE = 0730 CEDA ALter Bundle( DFH$OSGB ) Bundle : DFH$OSGB Group : TAG$OSGI DEScription ==> CICS bundle containing OSGi sample bundles Status ==> Enabled Enabled | Disabled BUndledir ==> /var/cicsts/cicsts56/CT56B4A1/bundles/com.ibm.cics.server. (Mixed Case) ==> examples.bundle_1.0.0 ==> ==> ==> BAsescope ==> (Mixed Case) ==> ==> ==> ==> DEFINITION SIGNATURE DEFinetime : 06/02/20 18:27:37 CHANGETime : 06/02/20 18:27:37インストールしてEnableになることを確認します。
I BUNDLE STATUS: RESULTS - OVERTYPE TO MODIFY Bun(DFH$OSGB) Ena Par(00003) Tar(00003) Enabledc(00003) Bundlei(com.ibm.cics.server.exampl)ちなみにCICS Explorerで資源定義確認すると、BUNDLE定義だけでなくBUNDLEに含まれるBUNDLEPARTSも確認できます。
サンプル実行
今回はHello Worldのサンプルを実行してみるので、このサンプルに関連する資源定義を追加でインストールします。
参考: Running the Hello World exampleプログラム定義"DFJ$JHE2"と、トランザクション定義"JHE2"ですね。実体であるJavaプログラムはBUNDLEとして先にインストール済みです。
いずれもDFH$OSGIに提供されているのでコピーしてそのままインストールします。
ちなみにDFH$JHE2の定義を覗いてみると、OBJECT CHARACTERISTICS CICS RELEASE = 0730 CEDA View PROGram( DFJ$JHE2 ) PROGram : DFJ$JHE2 Group : TAG$OSGI DEScription : OSGi Hello CICS world sample program Language : CObol | Assembler | Le370 | C | Pli RELoad : No No | Yes RESident : No No | Yes USAge : Normal Normal | Transient USElpacopy : No No | Yes Status : Enabled Enabled | Disabled RSl : 00 0-24 | Public CEdf : Yes Yes | No DAtalocation : Any Below | Any EXECKey : Cics User | Cics COncurrency : Required Quasirent | Threadsafe | Required Api : Cicsapi Cicsapi | Openapi REMOTE ATTRIBUTES DYnamic : No No | Yes REMOTESystem : REMOTEName : Transid : EXECUtionset : Fullapi Fullapi | Dplsubset JVM ATTRIBUTES JVM : Yes No | Yes JVMClass : examples.hello.HelloCICSWorld (Mixed Case) : : : : JVMServer : DFHJVMS JVMProfile : (Mixed Case) JAVA PROGRAM OBJECT ATTRIBUTES Hotpool : No No | Yes ...こんな感じで、JVM:Yes, JVMClass:examples.hello.HelloCICSWorld, JVMServer:DFHJVMSが指定されています(BUNDLEPARTSでインストールされているクラス)。※事前準備で作成したJVMServer名が違う場合はそれに合わせて適宜修正してください。
これで一通り資源は整ったので実行してみます。
CICS端末からJHE2トランザクションを実行します。
Hello from a Java CICS applicationというJavaプログラムから出力している文字列が表示されました!
これでJavaがCICSプログラムとして動いたことが確認できました。
- 投稿日:2020-06-04T11:24:57+09:00
JavaでAWS S3オブジェクトの削除
AWS SDK for Javaを利用して、S3オブジェクトの削除ができます。
1つのオブジェクトの削除
bucket名とオブジェクトキーを指定すれば、指定オブジェクトの削除ができます。
try { final AmazonS3 s3 = new AmazonS3Client(); s3.deleteObject(bucket_name, object_key); } catch (AmazonServiceException e) { e.printStackTrace(); }指定フォルダーにある全てのオブジェクトの削除
bucket名とフォルダー名を指定すれば、指定フォルダーにある全てのオブジェクトの削除ができます。
try { final AmazonS3 s3 = new AmazonS3Client(); ObjectListing objListing = s3.listObjects(bucket_name,folder_nm); List<S3ObjectSummary> objList = objListing.getObjectSummaries(); for (S3ObjectSummary obj : objList) { s3.deleteObject(bucket_name, obj.getKey()); } } catch (AmazonServiceException e) { e.printStackTrace(); }s3.listObjectsは以下のようなポリシー設定は必要。
PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: "s3:*" Resource: - "arn:aws:s3:::bucket-test-01" - "arn:aws:s3:::bucket-test-01/*"
- 投稿日:2020-06-04T00:13:35+09:00
Javaで作る年齢当てゲーム
とあるテレビ番組を見ていて
BSで「プロスタ」というScratchを題材にした子供向けプログラミング教育番組が
放送されているのを拝見。
私が見た回の中で、安藤先生という方の年齢を少ない質問数で当ててみようという
コーナーがあったのでその部分を参考にJavaで年齢当てゲームを作ってみました。Main.javapackage andouteacher; import java.util.Random; import java.util.Scanner; // 安藤先生の年齢を当てるゲームです // 指定された回答数の中で年齢を当てましょう // コツは2分探索です // 効率の良い答え方で候補を絞っていきましょう public class Main { public static void main(String[] args) { Random rnd = new Random(); int age = rnd.nextInt(80); int limit = 5; Scanner sc = new Scanner(System.in); System.out.println("安藤先生は何歳に見えますか?"); System.out.println("回答するチャンスは"+ limit +"回までです"); for(int i = 0; i < limit; i++) { int answer = sc.nextInt(); if(answer == age) { System.out.println("正解です"); break; } else if(answer > age){ System.out.println("それよりも年下です"); } else { System.out.println("それよりも年上です"); } } System.out.println("正解は" + age + "歳でした"); } }番組内では質問できる回数に制限はありませんでしたが、このプログラムでは
回数を5回までに制限しています。int型のlimitという変数がそれにあたります。
すごくシンプルなゲームですがプレイしてみると意外に楽しめます。
興味を持たれた方は是非、このコードを実行して遊んでみてください(^-^)追記
ちなみにですが安藤先生は「70歳以上ですか?」と質問されると少しションボリします。
(これは番組内であったやりとりの話であって、このプログラムには一切関係ありません・・・)