20201104のJavaに関する記事は14件です。

実家の会社の日報表をwebアプリ化した。(スマホ用)

はじめに

実家の会社が未だに紙ベースでやりとりをしているので、実際に使ってほしいという気持ちを持ちつつ作ってみました。(スマホ用)

概要と背景

  • 営業マンがその日行った営業を日報表にまとめてその日の終わりに提出する。
  • 紙面でのやりとりなので、事前に報告ができない、過去の営業記録をいちいち紙で探す必要がある、など不便なことが多い。
  • 日報表の項目は、訪問先住所・訪問先氏名・会見者・訪問結果・内容・日付の6項目。
  • 社員の管理は社員用テーブルで管理し、訪問履歴はセールス記録テーブルで管理する。
  • 訪問履歴は社員に個別に与えられた社員番号で個別に管理する。

開発環境

  • サーバーサイド
    • java
    • java EE
  • フロントサイド
    • javascript
    • html
    • css
  • データベース
    • MySQL
  • その他
    • bootstrap

基本機能

  • ログイン機能
    • 社員名と社員ごとに与えられたパスワードで管理
    • セッションによるログイン管理
  • ログアウト機能
    • セッションを削除
  • 日報表の入力・送信機能
    • 項目は概要の通り
    • 営業記録の入力の際に、社員番号を入力せずにセッション情報から社員番号を取得し、サーバーサイドで自動的に入力
  • 入力の際のチェック機能、送信の際の再確認機能
  • 入力した日報の一覧表示
    • 社員が自分の日報のみ見られる(他の社員の日報は見れない)ように設定
    • 今日、今週、今月、全て、から選ぶことが可能
  • 訪問履歴の検索機能
    • 訪問先氏名または郵便番号による検索が可能

できたもの

  • ログイン画面
    日報表ログイン.jpg

  • 日報入力画面
    日報表入力.jpg

  • 営業記録一覧を表示した例
    一覧は横スクロールで対応
    input.gif

  • 検索例
    input2.gif

工夫した点

  • 社員は結構年を取っている人が多いのでUIをなるべく大きめに設定した。
  • ajaxを使って郵便番号を入力すると該当する都道府県市区まで自動入力されるようにした。
  • 日報を入力し、送信を押したときに再確認するよう入力された情報をアラートで表示し、OKか確認するよう設計した。
  • 名前別の検索だけだと別の住所であっても重複する可能性があるため、より確実な郵便番号による検索を設けた。

作った感想

日報表をwebアプリ化してみて、紙でのやりとりより圧倒的に役立つと確信できた。(まだ使ってくれてない)

最後に

この日報表の他にも、紙でやりとりされていて、かつアプリ化できるものがたくさんあるので、これからアプリ化してどんどん実家の会社の業務効率をよくしたい。(使ってくれるかは分かりませんが。)

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

[Java]個人的にまとめた件について。

Javaについて、理解するためにまとめる。
随時更新していく。

流れ

①ソースコードの作成

ソースファイルの拡張子は「.java」

②コンパイル

ソースファイルをクラスファイルに変換
拡張子は「.class」
クラスファイルにバイトコード(ソースコードの命令をコンピュータが認識できるようにしたコード)がある

③実行

バイトコードの解読が行われる
よってコンピュータが命令通りに動作するようになる

基本構造

public class クラス名 {   ※クラス名はアルファベット大文字始まり //クラスブロック
  public static void main(String[] args) {  //メソッドブロック
  }
}

ブロックによる二重構造

変数

宣言
データ型 変数名;
データ型

[整数] 小
byte:かなり小さい byte glasses; glasses = 7;
short:小さい short age; age = 7;
⭐︎int:通常 int salary; salary = 7777;
long:大きい long Japanese; Japanese = 124_000_000L;

[小数] 大
float:あいまい float weight; weight = 60.0F;
✨double:通常 double height; height = 177.7;
※金額に対しての使用はNG(厳密な計算が難しいため)

[真偽値]
boolean:trueかfalse boolean isValid;

[文字]
char:1文字のみ char color; color = '緑';
※シングルクォーテーションにしなければならない

[文字列]
⭐︎String:文字の並び String name; name = "John"
※ダブルクォーテーションで問題なし

初期化

データ型 変数名 = 値;

String likes = "ねこ";  //変数の初期化 ※宣言と代入の同時化
likes = "いぬ";         //再代入 ※「ねこ」が上書きされる
System.out.println(likes + "が好きです");

定数(final付きの変数)

宣言
final データ型 定数名 = 初期値; ※定数名はすべて大文字が基本

final double PI = 3.14; //定数の宣言
int pie = 7;
PI = 10; //コンパイルエラーの確認
System.out.println("半径" + pie + "cmの円の面積は");
System.out.println(pie * pie * PI);

書き換え防止のために、値を固定できる

式(計算式や宣言式など)

オペランドと演算子から成り立つ

オペランド

・式に使う値を指す
・ソースコードでの具体的な指定値は「リテラル」と呼ばれる
・リテラルはデータ型によって、表記方法が異なる

エスケープシンス

String型やchar型で使用される特殊文字
¥" ダブルクォーテーション
¥' シングルクォーテーション
¥¥ 円記号(¥)
¥n 改行

評価

3原則による式に従った計算処理のこと

①評価結果への置換

演算子はオペランドを用いた計算を行い、計算結果にたどり着く

②優先順位
2 + 7 * 10 = 72    //掛け算が優先
(2 + 7) * 10 = 90  //かっこで囲むことで優先できる

複数の演算子がある場合、Javaのルールに従って優先される

③結合規則

複数の同じ優先順位の演算子がある場合、演算子毎の定まった方向順が優先される
+演算子:左から
=演算子:右から

演算子

式に使う記号を指す

算術演算子(+、ー、*、/、%)

・すべて左から
・優先順位も高め

文字列結合演算子(+)

・同じく左から
・優先順位も高め

代入演算子(=、+=、ー=、*=、/=、%=)

・すべて右から
・優先順位は低い
・最後に行われるもの

インクリメント/デクリメント演算子(++、ーー)

・単項演算子とも呼ばれる
・JavaScriptと同じ
・最も優先度が高い
・他演算子と併用しないのが望ましい(バグの原因になる恐れがある)

データ型の変換

①代入時の自動型変換

float y = 7;  //代入時の自動型変換
double z = y; //代入時の自動型変換
int w = 0.7;  //型より小さい型はコンパイルエラーになる
byte q = 2;   //byte。short型にint型の代入は例外

各型より大きい型に対しての代入は値が自動的に変換される仕組み

②強制的な型変換

int e = (int)7.7; //キャスト演算子

キャスト演算子

定義
(変換先の型名)値;

・強制的な変換を指示する

[デメリット]
・データの一部を破損してまでも変換を行うため、データの中身が欠落する
 ➡︎上記の場合、小数点以下が出力されなくなる
  余程の理由がない限り、使用しないことが望ましい

③演算時の自動型変換

long b = 7 + 7L;           //intをlongに変換
double c = 7.7 * 2;        //intをdoubleに変換
String msg1 = 7 + "が好きです";  //intをStringに変換

・原則は同じ型同士
・異なる型同士の場合は比較して大きな型へ自動的に統一される

命令実行

定義
命令文(引数);

int p = 1, o = 2;
int m = Math.max(p, o); //値を比較して大きいものを代入する
System.out.println(m);  //画面に表示する
System.out.print(m);    //画面に表示する(改行なし)

String age = "12";
int n = Integer.parseInt(age);             //文字列を数値に変換する
System.out.println("年齢は" + n + "です");

int r = new java.util.Random().nextInt(7); //乱数を発生させる
System.out.println("年齢は" + r + "ですか?");

System.out.println("名前を入力してください");
String name = new java.util.Scanner(System.in).nextLine();   //キーボードから1行の文字列入力を受け取る
System.out.println("年齢は?");
int age1 = new java.util.Scanner(System.in).nextInt();       //キーボードから1つの整数入力を受け取る
System.out.println("いらっしゃい!" + age1 + "歳の" + name + "さん");

制御構文

・条件式とブロックから成り立つ
・仕組みはJavaScriptと似ている

条件式

文字列型の変数.equals(比較対象の文字列) 

if (str.equals("おはよう")) 

・等しいを表すのは「==」
・ただ文字列は「==」で比較できない

条件分岐

①if-else文
②if文

③if-else if-else文

➡︎elseブロックが空なら、丸ごと省略が可能

④switch文

➡︎if文をスマートにできる
 条件❶左右辺が一致するかの式であること
   ❷比較値が小数と真偽値ではないこと
 ※caseラベルの末尾にはbreak文

繰り返し

①while文

➡︎前置判定(ブロックの実行前に条件式の評価を行う)

②do-while文
do {
  temp++;
  System.out.println("1度上がりました");
} while(temp > 27);

➡︎後置判定(ブロックの実行後に条件式の評価を行う)
 ※つまり最低でも1回は処理が行われる

③for文
for (int 変数名 = 0; 変数名 < 繰り返す値; 変数名++)

for (int 変数名 = 1; 変数名 < 繰り返す値; 変数名++)     //ループ変数を1から始める
for (int 変数名 = 0; 変数名 < 繰り返す値; 変数名 += n)  //ループ変数をnずつ増やす
for (int 変数名 = n; 変数名 > 1; 変数名--)            //ループ変数をnから1ずつ減らす
for (; 変数名 < n; 変数名++)                         //ループ変数を初期化しない
for (int 変数名 = 0; 変数名 < 繰り返す値;)        //繰り返し処理を行わない

・JavaScriptと似ている
・繰り返す回数が定まっている場合に使われる

その他

繰り返しの中断

・JavaScriprと似ている
①break文
②continue文

無限ループ
①while (true) {
   処理
 }

②for (;;) {
   処理
  }

中断されない限り、意図的にループし続ける

配列

JavaScriptと似ている

宣言
データ型[] 配列変数名;

int[] scores;
代入
配列変数名 = new データ型[値];

scores = new int[5];
初期化
①データ型[] 配列変数名 = new データ型[] {値};
②データ型[] 配列変数名 = {値};

int[] scores = new int[] {10, 20, 30, 40, 50};
int[] scores = {10, 20, 30, 40, 50};

※初期化するための値設定
 boolean:false
 String:null
 それ以外:0

要素数の取得
配列変数名.length 

int num = scores.length;

文字列変数名.length();

String str = "Javaを学ぶ";
System.out.println(str.length());
エラー
ArrayIndexOutOfBoundsException 

存在しない要素を使用しているため

for文との組み合わせ

①ループによる全要素の利用
for (int i = 0; i < 配列変数名.length; i++) {
  配列変数名[i]を用いた処理
}

for (int i = 0; i < scores.length; i++) {
  System.out.println(scores[i]); //ループ毎にiが変化する
}
②ループによる集計
int sum = 0;                               //変数の初期化
for (int i = 0; i < scores.length; i++) {
  sum += scores[i];                        //1科目ずつsumに合算する
}

int count = 0;
for (int i = 0; i < scores.length; i++) {
  if (scores[i] >= 25) {
    count++;                                //条件に合致する要素をカウントする
  }
}
③添え字に対応した情報の利用
int[] seq = new int [10];
for (int i = 0; i < seq.length; i++) {
seq[i] = new jaba.util.Random().nextInt(4);
}
for (int i = 0; i < seq.length; i++) {
    char[] base = {'A', 'T', 'G', 'C'};
}
System.out.println(base[seq[i]] + " ");
④拡張for文
for (データ型 変数名 : 配列変数名) {
  処理
}

int[] scores = {10, 20, 30, 40, 50};
for (int value : scores) {
  System.out.println(value);
}

・要素を1つずつ取り出すループ専用
・通常のfor文より、ループ変数や添え字の記述なしでスッキリした形になる

後片付け

ガベージコレクション
boolean valid = true;
if (valid == true) {
  int[] array = {2. 4. 6}
}                             //この時点でarrayはメモリからなくなる

・newで確保された3要素はブロックが終了しても残り続ける(ゴミ)
・ただどの配列変数からも参照されない
・そんなゴミを自動で探して片付けてくれる仕組み

null
int[] array = {1, 3, 5};
array = null;
array[0] = 9;

・参照型変数にのみ代入が可能
・意図的に配列の参照を行わないようにする(=参照を切る)

エラー
NullPointerException

nullが格納された配列変数を利用すると起こる

二次元配列(総称して多次元配列)

宣言
データ型[][] 配列変数名 = new データ型[行数][列数];

int[][] scores = new int[3][3];
使用方法
配列変数名[行の添え字][列の添え字]

System.out.println(scores[1][1]);

メソッド

・複数の文で書かれた処理コードを1つにまとめて名前をつけたもの
・メソッドを定義する立場と呼び出す立場が常に同一ではないことに注意
 ➡︎開発は複数人で行うことが多いため
・メソッド名は処理内容がわかるものが望ましい

[メリット]
①コードの見やすさから、把握がしやすくなる
②機能毎のメソッドに分けることで、修正範囲が限定できる(エラーの原因元を探しやすくする)
③メソッドは使い回しができるため、作業効率化を図れる

定義
①public static 戻り値のデータ型 メソッド名(引数リスト) {
  ②メソッドの呼び出しにより実行される具体的な処理
}

public static void hello() {
  System.out.println("こんにちは");  //メソッドブロック=実行内容
}

[2段階構成]
①重要事項の表明
②処理内容

呼び出し
メソッド名(引数リスト);

public static void main(String[] args) {
  System.out.println("メソッドの呼び出し");   //mainメソッドブロック
  hello();                              //メソッドの呼び出し
  methodA();                            //methodBを含むmethodAが呼び出される
}
public static void methodA() {
  System.out.println("A");
  methodB();
}
public static void methodB() {
  System.out.println("B");
}

・定義だけでは実行されない
・メソッドの記述順に決まりはない(mainメソッドが中心になるため)

引数

メソッド名()             //何も渡さない
メソッド名(値)           //1つ渡す
メソッド名(値1, 値2・・・)  //複数渡す

① public static void main(String[] args) {
     hello("田中"); //引数の定義(1つ)
     hello("佐藤"); 
     hello("鈴木");
   }
   public static void hello(String name) {      //文字列型変数nameを定義
     System.out.println(name + "さん、こんばんは");
   }

② public static void main(String[] args) {
    add(100, 200); //引数の定義(2つ)
    add(10, 20);
  }
  public static void add(int x, int y) {
    int ans = x * y;
    System.out.println(ans);
  }

❸ public static void main(String[] args) {
     methodC("さようなら", 777); 
   }
   public static void methodC(int x, String y) { //コンパイルエラー
     System.out.println(x + y);
   }

❹ public static void main(String[] args) {
     int a = 7; 
     int b = 77;
     add();
   }
   public static void add() { 
     int ans = a + b;             //コンパイルエラー
     System.out.println(a + b);
   }

・メソッドで定義するデータ型と順番には注意
・引数と変数の各データ型が異なるとコンパイルエラーになる
・仮引数:受け取る変数➡︎ここで言うx、y
・実引数:渡す変数➡︎ここで言う100、200、10、20
・スコープにより、mainメソッドで定義した変数は別メソッドでは使用不可
・ローカル変数:メソッド内で宣言した変数➡︎ここで言うmain()、add()
        そのメソッド内でのみ有効なため、別メソッドの同名ローカル変数はまったくの別物

戻り値

public static 戻り値のデータ型 メソッド名(引数リスト) {  //戻り値のデータ型はreturnによって戻される値と同じ型
  メソッドの呼び出しにより実行される具体的な処理
  return 戻り値;  //戻り値は変数名やリテラル(100、"hello"など)を指定する
}

public static int add(int x, int y) {
  int ans = x + y;
  return ans;
}

  
・何も戻さない場合に「void」を使う
・return文は「値を戻す」だけでなく「メソッドを終わらせる」意味を持つ
 ➡︎そのため、return文後に処理コードを書くとコンパイルエラーになる

呼び出し
データ型 変数名 = メソッド名(引数リスト);  //メソッドの呼び出しが優先される

① public static void main(String[] args) {
    int ans = add(100, 200);  //呼び出し
    System.out.println("100 + 200 = " + ans);
   }

② public static void main(String[] args) {
     System.out.println(add(add(100, 200), add(10, 20)));  //戻り値をそのまま使用
   }

・必ずしも変数で戻り値を受け取る必要はない

オーバーロード(多重定義)

①public static void main(String[] args) {
  System.out.println(add(7, 77));
  System.out.println(add(7.7, 77.7));
  System.out.println(add("yahoo", "google"));
}
public static int add(int x, int y) {              
  return x + y;
}
public static double add(double x, double y) {
  return x + y;
} 
public static String add(String x, String y) {
  return x + y;
}

②public static void main(String[] args) {
  System.out.println(add("7 * 77 = " + add(1, 2)));            //*が呼び出される
  System.out.println(add("7 * 77 * 2 = " + add(1, 2, 3)));   //#が呼び出される
}
public static int add(int a, int b) {              
  return a + b; //*
}
public static int add(int a, int b, int c) {
  return a + b + c; //#
} 

・原則メソッドに同名を名付けることはできない(似たような処理内容だとしても)
 ➡︎それを可能にする仕組み

[例外]
①メソッドの仮引数のデータ型が異なる場合(ここで言うint、double、String)
②メソッドの仮引数の要素数が異なる場合(ここで言うa・b、a・b・c)

引数(配列ver)

public static void main(String[] args) {
  int[] array = {1, 3, 5};
  printArray(array); //配列を渡す
}
public static void printArray(int[] array) { //引数に配列指定
  for (int element : array) {
    System.out.println(element);
  }
}

・値渡し:値そのものが渡される呼び出しのこと
     配列を用いる場合、値(ここで言う1、3、5)ではなく要素のアドレスがコピーされる
     引数としてアドレスを渡すことを「参照渡し」と呼ぶ
     ➡︎呼び出し先での書き換えは呼び出し元にも反映される

戻り値(配列ver)

public static void main(String[] args) {
  int[] array = makeArray(3);
  for (int i : array) {
    System.out.println(i);
  }
}
public static int[] makeArray(int size) { 
  int[] newArray = new int[size];
  for (int i = 0; i < newArray.length; i++) {
    newArray[i] = i;
  }
}

コマンドライン引数

String[] args

・プログラム起動時にそのプログラムに渡す値
・その値はmainメソッドに渡される

起動
java プログラム名 引数リスト

java Sample abc def ghi
java Sample 1 2 3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Spring AOPの実行順序

はじめに

今回はSpring AOPの実行順序について書いていきます。
同じAspect内にあるAdvice間の優先順位や、別Aspect同士の同じAdviceの優先順位づけ方法などをご紹介いたします!

Spring AOPそのものの簡潔な説明はこちらのQiita(はじめてのSpring AOP)が詳しいので、まずAOPについてざっくり知りたい方はそちらをご参照いただいてから読むとスムーズです。

使用環境とバージョン

  • macOS Catalina
  • jdk13
  • STS 4.6.1
  • Spring Boot 2.3.0(spring-core5.2.6)

Adviceの優先順位

Adviceの実行順には優先順位があります。1

  1. @Around
  2. @Before
  3. @After
  4. @AfterReturning
  5. @AfterThrowing

すなわち、同じジョインポイントに@Around@Beforeが存在する場合、@Around内で定義されているジョインポイント実行前の処理が先に実行されます。
それでは同じAspectの同じジョインポイントに@Before@After@AfterReturning、そしてジョインポイント実行前後にそれぞれ処理がある@Aroundが存在する場合は、どのようになるでしょうか?

以下3クラスからなる簡単なHelloWorldコードで実験してみました。

  1. メインクラス
  2. コントローラークラス
  3. Aspectを定義しているクラス

それぞれ、簡単な紹介とソースコードを記載します。

①メインクラス
シンプルにSpringを実行するクラスです。特別な処理は行っていません。
@EnableAspectJAutoProxyはAOPを有効にするために必要なアノテーションなので、今回すべてのクラスに付与しています。

AopSampleApplication.java
package com.example.practiceaop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@EnableAspectJAutoProxy
@SpringBootApplication
public class AopSampleApplication {
        public static void main(String[] args) {
            SpringApplication.run(AopSampleApplication.class, args);
        }
}

②コントローラークラス
http://localhost:8080/ を叩くとコンソールに【Hello World!】を表示します。
以前書いたQiita記事のコードをかなり流用しました(作っておいて良かった!)。

AopSampleController.java
package com.example.practiceaop;

import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@EnableAspectJAutoProxy
public class AopSampleController {
    @RequestMapping("/")
    @ResponseBody
    public void helloWorld() {
        System.out.println("【Hello World!】");
    }
}

③Aspectを定義しているクラス
ジョインポイントは全Advice共通でAopSampleControllerのhelloWorld()としています。

Aspect1.java
package com.example.practiceaop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Aspect
@Component
@EnableAspectJAutoProxy
public class Aspect1 {
    @Before("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
    public void before1(JoinPoint jp) {
        System.out.println("Before1");
    }

    @After("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
    public void after1(JoinPoint jp) {
        System.out.println("After1");
    }

    @AfterReturning("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
    public void afterReturning1(JoinPoint jp) {
        System.out.println("AfterReturning1");
    }

    @Around("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
    public Object around1(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Around1 メソッド実行前");
        try {
            Object result = pjp.proceed();
            System.out.println("Around1 メソッド実行後");
            return result;
        } catch (Throwable e) {
            throw e;
        }
    }
}


実行結果は以下のとおりです。

Around1 メソッド実行前
Before1
【Hello World!】
AfterReturning1
After1
Around1 メソッド実行後

Aroundの優先順位は1位であるはずなのに、ジョインポイント後は実行順が最下位になっているのは何故でしょうか?理解を助けるために、公式ドキュメントDeepl翻訳で日本語にしたもの(少し手動で体裁を整えています)を記載します。

Spring AOPは、AspectJと同じ優先順位ルールに従って、アドバイスの実行順序を決定します。アドバイスの優先順位が高いものが最初に実行されます(つまり、2つのBeforeアドバイスがあった場合、優先順位の高いものが最初に実行されます)。ジョインポイントから「出て行く途中」では、優先順位の高いアドバイスが最後に実行されます(したがって、2つのAfterアドバイスが与えられた場合、優先順位の高い方が2番目に実行されます)。

ジョインポイントから「出て行く途中」では、優先順位の高いアドバイスが最後に実行されます、この法則がややこしいですね。
つまり、ジョインポイントのメソッド実行前は、優先順位=実行順ですが、ジョインポイントのメソッド実行後は、優先順位の低いものから実行されるということです。

同列の場合の優先順位づけ

例えば、同じジョインポイントの前にトランザクション処理とログ出力を挟みたいとします。また、その実行順はトランザクションが絶対先に行われてほしいとします。
Adviceのうちの一つ、Beforeを使用すればジョインポイントの前で実行するのは可能です。しかし、公式ドキュメントによると、同じ種類の複数のAdviceが同じジョインポイントに存在する場合、実行順は不定で、実行ごとに変化してしまいます。

同じアスペクトで定義された2つのアドバイスが、両方とも同じ結合点で実行される必要がある場合、順序は不定です(javacでコンパイルされたクラスでは、反射を通して宣言順序を取得する方法がないため)。

これを解決するには、同じAdviceにトランザクションとログ出力を入れるか、別のAspectに切り出して@Orderを使用するかの、2つの方法があります。
同じAdviceに含めて問題のない処理同士であれば良いのですが、そうでない場合がほとんどだと思うので、@Orderを使用する方が現実的です。@Orderを使用すると、同列に存在するAspectの実行順位を指定することができます。

試しに、先ほど使用したAspect1に@Order(1)をつけ、Aspect1をコピーしてコンソール出力文字だけ変更したAspect2クラスを作成し@Order(2)をつけて実行してみます。

Aspect1.java



前略
@Aspect
@Component
@EnableAspectJAutoProxy
@Order(1) // アノテーション追加
public class Aspect1 {
後略




Aspect2.java



前略
@Aspect
@Component
@EnableAspectJAutoProxy
@Order(2) // アノテーション追加
public class Aspect2 { //Aspect1のコピー
@Before("execution(* com.example.practiceaop.AopSampleController.helloWorld(..))")
    public void before1(JoinPoint jp) { 
        System.out.println("Before2"); // Before1→Before2に変更。以下Adviceも同様。
    }
後略




結果は以下のとおりです。
Aspect1と2間で実行順序が入り乱れることなく実行できていることが分かります。

Around1 メソッド実行前
Before1
Around2 メソッド実行前
Before2
【Hello World!】
AfterReturning2
After2
Around2 メソッド実行後
AfterReturning1
After1
Around1 メソッド実行後

念のため、Aspect1に@Order(2)を、Aspect2に@Order(1)をつけて実行してみました。
こちらも以下のように、Aspect1と2間で実行順序が入り乱れることなく、またAspect同士の優先順位だけをきれいに入れ替えることができました。

Around2 メソッド実行前
Before2
Around1 メソッド実行前
Before1
【Hello World!】
AfterReturning1
After1
Around1 メソッド実行後
AfterReturning2
After2
Around2 メソッド実行後

おわりに

今回はSpring AOPの実行順序について見ていきました。
特にジョインポイント後の挙動がいまいち分かっておらず、検索してもドキュメントと矛盾する結果が出てくることもあり、かなり混乱していました。
ドキュメント作成者自身もややこしいと感じているのか、新しいバージョンになるにつれ記載が丁寧になっていってる感ありますね…

この記事がどなたかの参考になれば幸いです。また、何か間違いなどございましたら、そっと教えてください…!

お読みいただき、ありがとうございました!


  1. Spring-core5.3系のドキュメントになってしまうため、今回使用したバージョン(5.2.6)とは若干異なりますが、5.2.6の方はAdviceの優先順位が明記されていなかったことと、後述の実験結果とも齟齬がなかったことから、この部分だけ5.3系を参照しました。https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#aop-ataspectj-advice-ordering 

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

チャットアプリを作った。

はじめに

webアプリの練習としてチャットアプリを作ってみました。

基本機能

  • ログイン制のアプリ
    • パスワードはハッシュ化してDBで管理
  • セッションを使ったログイン管理機能
  • アカウント作成機能
  • メッセージ投稿機能
    • 投稿内容をDBに保存
    • XSS対策のエスケープ処理
  • 画像投稿機能
    • 画像をbyte配列に変換し保存し軽量化
  • 入力チェック機能
    • ログイン時
    • アカウント作成時
    • メッセージ投稿時
  • メッセージ削除・編集機能(ユーザー視点から、DBに保存してある個人のメッセージを個別に削除・編集が可能)
  • Bootstrapフレームワークを使ったアプリデザイン
  • スマホに対応したレスポンシブデザイン

開発環境

  • 使用言語
    • Java
    • javascript
    • html
    • css
  • データベース
    • MySQL
  • その他
    • bootstrap

画面

ログイン画面

  • PC用 roguinngamen.png
  • スマホ用
  • スマホの画面.jpg

チャット画面

  • PC用
    チャット画面.png

  • スマホ用
    tyattogamen.png

作った感想

  • 普通のチャットアプリでもいざ作ってみると面倒なことが多い。
    • 改行を判定するための処理とXSS対策の両立
  • 画像を投稿する際にbyte列に変換したほうが良いということを学んだ。表示する際にはbase64にエンコードして表示する方法を選んだが、この方法はページを表示する際に負荷がかかるため、今後の反省点にしたい。
  • デザインには自信がないため、bootstrapのおかげでだいぶマシになった。

最後に

チャットアプリといえど、その処理や構造はほぼほぼ掲示板や、他の入力フォームを伴うアプリと同じあるいは似ているので、他のアプリでも流用が効くことがわかった。

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

EclipseでJUnitを動かす方法

JUnitとは

  • Javaプログラムにおいてユニットテストができるツール(フレームワーク)を行う
  • テスト自動化ができる
    • 手作業が減るため、テスト工数削減やヒューマンエラー防止に効果あり。

前提条件

  • Eclipseがインストールされていること
  • 以下のような構成のプロジェクト、パッケージ、プログラムファイルを作成していること
ProjectA  ----  src
                 |
                 ------ xx.yy.zzz(パッケージ)
                            |
                            ------- Door.java (テスト対象クラス)

設定

パッケージxx.yy.zzz配下に、Door.java (テスト対象クラス)をテストするためのクラス「DoorTest.java」を作成します。

  • Eclipseでプロジェクトを右クリック [新規] - [その他]をクリック

image.png

  • 「Java」 - 「JUnit」 - 「Junitテストケース」を選択して 次へ をクリック

image.png

  • 下図を参考に、必要な値を設定する

image.png

  • 「DoorTest.java」の雛形ができました。
DoorTest.java
package xx.yy.zzz;  // テスト対象クラスと同じパッケージにする

import static org.junit.Assert.*;

import org.junit.Test;

public class DoorTest {

    @Test
    public void test() {
        fail("まだ実装されていません");
    }

}

  • 試しに実行してみましょう。 DoorTest.javaを右クリックして、 [実行] - [実行の構成]

image.png

  • 下図を参考に、必要な値を設定する。テスト・ランナーは「JUnit 4」にするのを忘れずに!
    image.png

  • 実行結果です。failメソッドが呼び出されるので、結果は必ず「失敗」になります。

image.png

テストプログラムを書く

テスト対象クラス

Door.java
package xx.yy.zzz;  // テストクラスと同じパッケージにする

public class Door {
    private boolean isOpen = false;

    public void open(String str) {
        if (str.equals("開けゴマ!")) {
            isOpen = true;
        } else {
                        isOpen = false;
            System.out.println("違います。");
        }
    }

    public void close() {
        isOpen = false;
    }

    // getter
    public boolean isOpen() {
        return isOpen;
    }

}

テスト内容

  • test1. openメソッドで"開けゴマ!"以外を指定すると、ドアが開らない(isOpenメソッドの戻り値がfalse)こと。また、「違います。」と標準出力されていること。
  • test2. openメソッドで"開けゴマ!"を指定すると、ドアが開く(isOpenメソッドの戻り値がtrue)こと。
  • test3. closeメソッドでドアが閉じる(isOpenメソッドの戻り値がfalse)こと。

テストクラス

Door.java
package xx.yy.zzz;  // テスト対象クラスと同じパッケージにする

import static org.junit.Assert.*;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class DoorTest {

    Door door;
    private ByteArrayOutputStream baos;

    // 前処理
    @Before
    public void setUp() {
        door = new Door();

        baos = new ByteArrayOutputStream();
        System.setOut(
            new PrintStream(
                new BufferedOutputStream(baos)
            )
        );
    }


    // 「@Test」は、JUnitがテストメソッドと認識するためのアノテーション
    // public voidで作成すること
    @Test
    public void test1() {
        door.open("ドア、開いてください");

        // 標準出力
        System.out.flush();
        String expected = "違います。\r\n";
        String actual = baos.toString();

        // 期待値との確認
        assertEquals(false, door.isOpen());
        assertEquals(expected, actual);
    }

    @Test
    public void test2() {
        door.open("開けゴマ!");
        assertEquals(true, door.isOpen());
    }

    @Test
    public void test3() {
        door.close();
        assertEquals(false, door.isOpen());
    }

    // 後処理
    @After
    public void fin() {
        // DO Nothing
    }

}

[補足]

  • System.setOutメソッド:「標準」出力ストリームを割り当てし直します。引数はPrintStreamクラスです。
  • PrintStreamクラス:新しい出力ストリームを作成するために使います。
  • BufferedOutputStreamクラス:バッファのデータを出力ストリーム化します。
  • ByteArrayOutputStreamクラス:データがバイト配列に書き込まれる出力ストリームを実装します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

youtubeDataApiを試してみた。

はじめに

初めての投稿、というかブログ自体が初めてなので文章が拙いかもしれません。

概要

youtubeDataApiを使って、特定のキーワードを検索を行い、その動画の再生数と動画チャンネルの登録者数を取得し、再生数/登録者数が60%以上の動画のみを抽出し、csvファイルに纏めるプログラム。
ネタを真似すればyoutubeでバズれる?

開発環境

使用言語

  • Java

結果(例)

ポケモンで検索してみた結果。
qiitajpg.jpg

作ってみた感想

  • 困った点
    • youtubeAPIは使用制限があり、特に今回のような検索機能を用いるとあっという間に1日の上限に引っかかってしまうため、乱用ができない。
    • たまに、検索に引っかかった動画で登録者数が取得できないことがあり、そのたびエラーになってしまっていた。該当する動画を確認したところ、チャンネル登録者数を非公表にしているチャンネルだった。(エラーにならないように分岐を加えた。)
    • 特にフレームワークを使わずにコードを書いたため、apiのurlを変数に直に書くという不格好なコードになってしまった。springなどのフレームワークを使えばもっとスタイリッシュに書けるのだろうか。。。もしくは他の言語でも。
  • 面白かった点
    • webAPIを扱うのは初めてだったためとても勉強になった。
    • youtubeDataAPIや、他のyoutubeのAPIを組み合わせれば効率良く情報収集できるのではないかとアイデアが膨らんだ。

最後に

調べたところ、ネット上には膨大な数のwebAPIが存在し自分の生活が効率よくなりそうなプログラムがたくさん書けるとワクワクした。youtubeでいうと登録したチャンネルがライブを開始した際に自動でurlを開くchromeの拡張機能とか作りたい。(chromeに通知機能があるからいらないかも)

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

Graphviz version 2.44.1 + PlantUML on VSCode でバージョン不一致エラー

環境(64bit)

VS Code

Version 1.50.1
VS CodeでPlantUMLをプレビューするには、Java(JRE)、Graphviz、PlantUML、Markdown Preview Enhancedが必要になる。全部最新版を入れるとエラーになったのでメモ。

VS Code Extension

Markdown Preview Enhanced : v0.5.13
PlantUML : v2.13.14

Graphviz

Version : 2.44.1
Graphvizインストーラー
https://www2.graphviz.org/Packages/stable/windows/10/cmake/Release/x64/
graphviz-install-2.44.1-win64.exe

エラーメッセージ

An error has occured!
When you're in trouble, confuse everything
Diagram size: 19 lines / 367 characters.
For some reason, dot/GraphViz has crashed.
This has been generated with PlantUML (1.2020.10).
This version of PlantUML is 170 days old, so you should
consider upgrading from http://plantuml.com/download
Java Runtime: Java(TM) SE Runtime Environment
JVM: Java HotSpot(TM) Client VM
Java Version: 1.8.0_271-b09
Operating System: Windows 10
Default Encoding: UTF-8
Language: ja
Country: JP
Machine: j-yamauchi2-PC
PLANTUML_LIMIT_SIZE: 4096
Processors: 8
Max Memory: 259,522,560
Total Memory: 16,252,928
Free Memory: 7,172,640
Used Memory: 9,080,288
Thread Active Count: 1
Default dot version: dot - graphviz version 2.44.1 (20200629.0846)
Please go to http://plantuml.com/graphviz-dot to check your GraphViz version.
You should send this diagram and this image to
plantuml@gmail.com
or
post to
http://plantuml.com/qa
to solve this issue.
You can try to turn arround this issue by simplifing your diagram.

Graphviz よりもPlantUMLのバージョンが90日以上古い場合に発生する様子。

回避策

まずはcmakeからGraphvizをインストール
https://www2.graphviz.org/Packages/stable/windows/10/cmake/Release/x64/
graphviz-install-2.44.1-win64.exe

デフォルトで以下の場所にインストールされる。
C:\Program Files\Graphviz 2.44.1

以下をダウンロードして解凍する。
https://www2.graphviz.org/Packages/stable/windows/10/msbuild/Release/Win32/
graphviz-2.44.1-win32.zip

解凍したものを以下に上書きする。
C:\Program Files\Graphviz 2.44.1\bin

以上で解決した。

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

new 演算子で生成したインスタンスのオブジェクト名取得

インスタンス.constructor.nameでオブジェクト名を参照できる。
Function.name - JavaScript | MDN

function Foo() {};
let foo = new Foo();

console.log(foo.constructor.name);
//=> 'Foo'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

180秒で分かるJava(超初心者編)

はじめに

パソコンを買ったばかりでJavaを1ミリも知らない人に向けて、高校、専門学校で習うかもしれない90分の授業を180秒で伝える記事です。
詳しい記事はほかの偉い人達が書いてるのでそれを読んで下さい。

この記事はあと170秒で読めます。

1. Javaとは

私の身の回りで呟かれているJavaという単語をざっくり3つに分類しました。
java1.png

  • プログラミング言語
     色々なアプリケーション(パソコンなどの中で動いてるソフト)を開発するための言葉です。「Java言語」というと誤解なく伝わります。開発するためには「JDK:Java Development Kit」と呼ばれる開発者向けのツール群が必要です。一応無償でも手に入ります。アプリケーションを使うだけならJDKは不要です。

  • アプリケーションそのもの
     Java言語で作られたアプリケーションのことです。「Javaアプリ」というと誤解なく伝わります。マイクラであればマイクラのゲーム本体の事です。マイクラJava版とかPC版とか言われてるアレです。因みに私はPC版という表現は曖昧なのでJava版と呼んでいます。

  • アプリケーションを動かす場所
     Java言語で作られたアプリを動かすための専用の場所です。「Java実行環境」「Javaランタイム」や「JRE」などというと誤解なく伝わります。実はマイクラJava版には下記画像の通りJREが付属しているので、JREの事を何も気にせずに遊べます。他のアプリケーションの場合には、そもそも「JREを準備してください。」などと言われることがあります。
    javapath1.png 

2. Java仮想マシン

通常、Mac用のアプリケーションをWindowsの上に乗せても動きません。WindowsとMacでは頭の形が違いますし会話する言葉も違うからです。
dora0.png
ですが30年以上前に、ある天才が「会話できないなら翻訳機を作れば良いじゃないの。」と呟き、その数年後にJVM(Java仮想マシン、Java Virtual Machine)と呼ばれる革命的なソフトウェアが誕生しました。
JVMはJREの一部であり心臓みたいなものです。JVMはみんなの頭の形に合わせて良い感じにフィットしてくれる専用のソフトウェアとして開発されました。アプリケーションからすると、WindowsでもMacでも自分を乗せている頭の形が常に一定の形に見えており、このJVMが自分を動かしてくれているパソコン(VM:仮想的なパソコン)として振舞ってくれている訳ですね。
dora2.png

JavaのアプリケーションはこのJVMの上で動いており、プログラマー達はスネオの頭の形を意識することなく、常に同じ形のアプリケーションを作ることができるようになったので、WindowsでもMacでも気にせずにアプリケーションを開発して配布できるようになりました。
java2.png
マイクラJava版を起動すると上記の画像のように、Javaの中でマイクラが動いていることが分かります。Google ChromeはそのままGoogle Chromeとして動いていますね。
この翻訳機、仮想マシンというアイデアは今ではとても一般的なIT用語になり、色々なところで仮想化という単語を聞くようになっています。パソコンの中だけに限らず、DVDドライブの仮想化、サーバーの仮想化、ネットワークの仮想化、データセンターの仮想化などなど。利用者から見てあたかもそこに〇〇があるかのように使う(ハードウェアの種類や形や住所を意識せずに使える)というのがこのアイデアの根幹です。

3. 悪いところと良いところ

Javaには悪いところと良いところがあります。
dora3.png

  • 悪いところ
     JVMという翻訳機を間に通しているため、その機械専用に作られたアプリケーション(例えばWindows用のマイクラ統合版:ネイティブアプリケーション)よりも少しだけ遅いです。

  • 良いところ
     上で書いた通り、色々な環境で同じアプリケーションを動かすことができます。また、JVM自身もソフトウェアなので複数のJVMを入れて使い分けることができます。Java版のマイクラに元々付属しているものを使っても良いですし、自分で入れた最新バージョンのJavaを使うこともできます。
    mine.png
     また、WindowsやMacのバージョンが変わったとしても、Javaアプリを動かしているJVMのバージョンさえ変わらなければ、50年後でも今のマインクラフトを変わらずに遊ぶことができます(できるはずです)。アプリケーションを動かす環境自体を凍結して何十年でも別の場所に持ち運ぶことができるので、アプリケーションを作る人にとっても必ず動く環境が1つ保証されるという訳ですね。

まとめ

私はこの記事を読むのに210秒かかりました。

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

【Java】プログラミングにおける基本用語

プログラミング勉強日記

2020年11月4日
復習もかねてJavaを0から勉強しなおそうと思う。そこでまずは基本的な用語をおさらいする。

クラスとメソッド

 クラスとメソッドは以下のように記述する。(詳しくは下記で説明する)

class クラス名{
  メソッド名 (引数){
    // 実行処理
    メソッド本体
  }
}

クラス

 簡単に言うと、クラスはプログラムを実行するときに必要な処理をまとめた設計図のようなもので、メソッドと変数によって構成される。
 Javaのようなオブジェクト指向言語ではプログラミングの対象をオブジェクト(もの)としてとらえる。なので、クラスはオブジェクトの設計図ではあるが、オブジェクトそのものではない。そのため、クラスを使用するときにはオブジェクトを生成(=インスタンス化)する必要がある。

 クラスを使えるようにするためには、クラスを定義しなくてはならない。フィールドはクラス内部の定義された変数で、クラスの持つ情報が格納されている。オブジェクトにはフィールドとメソッドが1つにまとめられている。

クラスの定義
class クラス名 {
  // フィールドやメソッドなどを記述
}

 

メソッド

 簡単に言うと、メソッドはクラスが持つ機能を記述したもので、メソッド名と引数、メソッド本体によって構成される。
 上記で述べたようにクラスを利用するために生成されたオブジェクトは属性とメソッドという要素から構成される。メソッドは自分で作ることもできるが、多くのメソッドがあらかじめ用意されている。メソッドは1つのクラスに複数記述することもできる。

オブジェクトとオブジェクト指向

 クラスは設計図で、オブジェクトはその設計図に基づいて作られる部品のようなものである。オブジェクトはクラスを実体化(=インスタンス化)したもので、オブジェクトを使用するためには下記のような記述をしないといけない。

オブジェクトを利用する
クラス名 変数名 = new クラス名(引数);

 オブジェクトは「モノ」という意味があり、オブジェクトを部品のように組み合わせてシステムを構築する考え方のことをオブジェクト指向という。
 

継承

 特定のクラスのフィールドやメソッドを引き継いで新しいクラスを定義することを継承という。継承を行うときに元のクラスをスーパークラス、新しく作るクラスをサブクラスという。

クラスの継承
class サブクラスの名前 extends スーパークラスの名前{
  // 処理内容、メソッド
}

オーバーライド

 スーパークラスのメソッドをサブクラスで再定義することをオーバーライドという。オーバーライドしたメソッドを呼び出すときは下記のように記述する。(finalやstaticといった修飾子がついてるメソッドはオーバーライドできない)

オーバーライド
super.メソッド名;

オーバーロード

 同じクラス内で同じメソッド名を定義することはできないが、引数の型や並び替えすることで同じ名前のメソッドを複数定義することができる仕組みをオーバーロードという。
 

参考文献

Java入門:プログラミングの必須知識!まず押さえたい基本用語集
【Java入門】コンパイル、クラス、メソッド...押さえたい基本用語まとめ

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

【Java】継承(Inheritance)

継承とは

  • 元になるクラスのメンバーを引き継ぎ、新たなクラスを定義すること
  • 基底クラス(スーパークラス、親クラス):継承元
  • 派生クラス(サブクラス、子クラス):継承してできたクラス
  • 機能の共通した部分を切り出して差分だけを書く仕組み
  • cf: 委譲:特定の役割を別のクラスとして切り出しそのインスタンスをフィールドとして保存

継承の使い方

  • class 派生クラス extends 基底クラス{}
  • extends省略するとObjectクラスを継承したことになる
  • 現在のクラスで要求されたメンバーを検索し、存在しない場合上位クラスで定義されたメンバーを呼び出す!
    • 基底、派生クラス両方にあるメソッドは派生クラスを優先して呼ぶ
  • 注意:
    • Javaでは基底クラスは1個だけ(多重継承認不可)
    • 基底クラスで定義されたメンバーを派生クラスで削除できない
    • =派生クラスは基底クラスの全性質を含む
Person.java
public class Person {
  public String name;
  public int age;

  public String show() {
    return String.format("%s(%d歳)です。", this.name, this.age);
  }
}
BusinessPerson.java
//Personを継承したBusinessPersonクラス作成
public class BusinessPerson extends Person {
  //派生クラス独自のworkメソッド定義
  public String work() {
    return String.format("%d歳の%sは今日も元気に働きます。", this.age, this.name);
  }
}
public class InheritBasic {
  public static void main(String[] args) {
    //BusinessPersonのみ呼び出す
    var bp = new BusinessPerson();
    bp.name = "佐藤一郎";
    bp.age = 30;
    //showメソッドがbpクラスのメンバーのように呼べる!
    System.out.println(bp.show()); //佐藤一郎(30歳)です。
    System.out.println(bp.work());  //30歳の佐藤一郎は今日も元気に働きます。
  }
}
  • ポイント
    • 親クラスと子クラスにis-aの関係があれば継承をつかう
      • is-a:SubClass is a SuperClass
        • Person⊃BusinessPerson
        • BusinessPersonがPersonに全て含まれる関係
      • 派生クラスは基底クラスの特化
      • 基底クラスは派生クラスの汎化

フィールドの隠蔽

  • 継承ではメンバーによって変更可否が異なる
    • オーバーライド:メソッドが可能
    • 隠蔽:フィールド、入れ子クラス/インターフェースが可能

隠蔽

  • 基底クラスの同名フィールドを派生クラスで定義した場合、基底クラスのフィールドが見えなくなる
  • データ型が異なっても名前が同じなら隠蔽される
  • 予約変数superを用いることで隠蔽フィールドにアクセス可能
  • 注意:基底クラスのフィールドがprivateではアクセス不可能
Person.java
import java.time.ZonedDateTime;
public class Person {
  public String name;
  public ZonedDateTime birth = ZonedDateTime.now();
}
BusinessPerson.java
import java.time.LocalDateTime;
public class BusinessPerson extends Person {
  //基底クラスのbirthフィールドを隠蔽
  public LocalDateTime birth = LocalDateTime.now();
  public void show() {
    //隠蔽フィールドにアクセス
    System.out.println(super.birth);
  }
}
public class HideBasic {

  public static void main(String[] args) {
    var bp = new BusinessPerson();
    //BusinessPerson.birthを表示
    System.out.println(bp.birth); 
    bp.show();
    //Person.birthフィールドを表示
    Person p = new BusinessPerson();
    System.out.println(p.birth);
  }
}

メソッドのオーバーライド

  • 基底クラスで定義されたメソッドを派生クラスで上書き(再定義)
  • 見るのは名前のみではない(隠蔽と異なる)
    • メソッド名:完全一致
    • 仮引数:データ型/個数一致
      • 基底の(CharSequence s)を派生の(String s)でオーバーライド不可
    • 戻り値:型一致/派生型
    • アクセス修飾子:一致/基底型より緩い
      • public>protected>無指定>private
      • privateはオーバーライド不可
    • throw句(例外):一致/派生型
  • @Overrideアノテーションを使えばOK
    • 基底クラスのメソッドをオーバーライドしていることを明示的に宣言
public class Person {
  public String name;
  public int age;

  public String show() {
    return String.format("%s(%d歳)です。", this.name, this.age);
  }
}
public class BusinessPerson extends Person {
  public BusinessPerson() {}
  //基底クラスの同名showメソッドを上書き
  @Override
  public String show() {
    return String.format("会社員の%s(%d歳)です。", this.name, this.age);
  }

  public String work() {
    return String.format("%d歳の%sは今日も元気に働きます。", this.age, this.name);
  }
}

基底クラス参照(super)

  • superによるメソッド呼び出しは派生クラスのメソッド定義の先頭
//BusinessPersonクラスを継承
public class EliteBusinessPerson extends BusinessPerson {
  @Override 
  //基底クラスのworkメソッドを呼び出し、独自の処理を追加する

  public String work() {
    //派生クラスの先頭でsuperメソッド呼び出し
    var result = super.work();
    return String.format("%sいつでも笑顔で!", result);
  }
}
public class InheritBaseCall {
  public static void main(String[] args) {
    var ebp = new EliteBusinessPerson();
    ebp.name = "山田太郎";
    ebp.age = 35;
    System.out.println(ebp.work()); //35歳の山田太郎は今日も元気に働きます。いつでも笑顔で!
  }
}

派生クラスのコンストラクタ

  • コンストラクターはメソッドと同じようには引き継がれない
  • 継承関係にあるクラスでは上位クラスから順にコンストラクタが呼ばれ、最終的に現在のクラスのコンストラクタが呼ばれる
    • 基本クラスの初期化は基本クラスのコンストラクタ
    • 派生クラスの初期化は派生クラスのコンストラクタ
      • 1.全フィールドを規定値で初期化
      • 2.基底クラスの初期化ブロック実行
      • 3.基底クラスのコンストラクタ実行
      • 4.派生クラスの初期化ブロック実行
      • 5.派生クラスのコンストラクタ実行
MyParent.java
public class MyParent {
  public MyParent() {
    System.out.println("親です。");
  }
}
MyChild.java
public class MyChild extends MyParent {
  public MyChild() {
    System.out.println("子です。");
  }
}
public class InheritConstruct {
  public static void main(String[] args) {
    var c = new MyChild(); //親です。 子です。
  }
}
  • 上位クラスで暗黙的に呼ばれるのは引数なしコンストラクタのみ
    • 明示的にコンストラクタを定義(=引数付き)した場合、デフォルトコンストラクタは自動生成されない
    • →引数付きコンストラクタを、派生クラスのコンストラクタからsuperで呼ぶ
    • 必ずコンストラクタの先頭文で呼ぶ
//上位クラス(引数付きコンストラクタ)
public class MyParent {
  public MyParent(String name) {
    System.out.printf("%sの親です。\n", name);
  }
}
public class MyChild extends MyParent {
  public MyChild(String name) {
    //派生クラスのコンストラクタから上位クラスの引数なしコンストラクタを呼ぶ
    super(name);
    //基底クラス→派生クラスの順でコンストラクタが呼ばれる
    System.out.printf("子の%sです。\n", name); //山田太郎の親です。\n山田太郎の子です。
  }
}
public class InheritConstruct {
  public static void main(String[] args) {
    var c = new MyChild("山田太郎");
  }
}

継承/オーバーライド禁止

  • 継承可能クラスの実装や修正は派生クラスへの影響も考慮するべき
  • 継承/オーバーライドを想定しないクラス/メソッドの場合、final修飾子で禁止する
public class Person {
  String name;
  int age;
  public Person() {}
  //showメソッドをオーバーライド禁止
  public final String show() {
    return String.format("%s(%d歳)です。", this.name, this.age);
  }
}
//BusinessPersonクラスを継承禁止
public final class BusinessPerson extends Person {
  public String intro() {
    return "会社員です。";
  }
}

参照型の型変換

  • 型同士が継承/実装の関係にあること

アップキャスト

  • 派生クラスから基底クラスへの変換のこと
    • BusinessPerson型をPerson型に変換
  • 派生クラスは基底クラスのメンバを全部含んでいる
  • 派生クラスのインスタンスは基底クラスのインスタンスとして利用可能
  • この場合変換の型はPerson、オブジェクトの型はBusinessPerson(後述の 変数の型/オブジェクトの型 参考)
//アップキャスト
public class CastUp {
  public static void main(String[] args) {
    //BusinessPersonオブジェクトを基底クラスPerson型の変数に代入して変換
    Person bp = new BusinessPerson();
    bp.name = "山田太郎";
    bp.age = 20;
    System.out.println(bp.show());
  }
}

ダウンキャスト

  • 基底クラスから派生クラスへの変換のこと
    • Person型をBusinessPerson型に変換
  • 基底クラスは常に派生クラスとして振る舞えない
  • キャスト構文で明示的に型変換が必要
  • Person p = new BusinessPerson();
  • BusinessPerson bp = (BusinessPerson)p;

変数の型/オブジェクトの型

  • PersonクラスのshowメソッドをBusinessPersonでオーバーライド
  • しかし以下コードではBusinessPersonクラスのworkメソッドを呼べない
    • 変数pはPerson型の変数
      • 変数pがworkメソッドを呼び出せない
    • 変数pのオブジェクトの型(実体)はBusinessPerson
      • BusinessPersonのshowメソッドは呼び出される
BusinessPerson.java
public class BusinessPerson extends Person {
  public BusinessPerson() {}

  @Override
  public String show() {
    return String.format("会社員の%s(%d歳)です。", this.name, this.age);
  }

  public String work() {
    return String.format("%d歳の%sは、働きます。", this.age, this.name);
  }
}
public class TypeDifference {

  public static void main(String[] args) {
    Person p = new BusinessPerson();
    p.name = "山田太郎";
    p.age = 30;
    // System.out.println(p.work()); //エラー
    System.out.println(p.show()); //会社員の山田太郎(30歳)です。
  }
}
  • つまり、、、
    • キャストは変数の型を差し替える
      • BusinessPerson変数型をPerson変数型に変換
    • オブジェクトの型(実体)はキャストしても変化しない
    • 呼び出されるメソッドはオブジェクトの型が決める
      • PersonではBusinessPersonの振る舞いができない

クラスメソッド/フィールドの隠蔽

  • クラスメソッドはクラス名.メソッド名(...)で呼ぶ
    • 変数の型/オブジェクトの型が存在しない
    • 常に指定クラスのメソッドが呼ばれるのでオーバーライド概念がない
  • フィールドの選択は変数の型で決まる
public class HideBasic {
  public static void main(String[] args) {
    var bp = new BusinessPerson();
    System.out.println(bp.birth);
    bp.show();
    //変数pはPerson型
    Person p = new BusinessPerson();
    //PersonクラスのbirthフィールドはZonedDateTime型
    System.out.println(p.birth); //ZonedDateTime型
  }
}

型判定

  • ダウンキャスト時にはオブジェクトの型チェックが必要
  • 例えばこんなことが。。。
    • BusinessPerson/StudentはPerson派生クラスである
      • Person p = new BusinessPerson(); //OK
      • BusinessPerson bp = (BusinessPerson)p; //OK
      • Student st = (Student)p; //NG
    • 変数pの実体(オブジェクト型)はBusinessPerson
    • コンパイル時はPersonとBusinessPerson/Student継承関係しか分からないのでコンパイルエラーにならないが、実行時にエラーになる!
  • instanceof演算子を使う
    • 変数に格納されたオブジェクト型が指定型に変換できる場合true
//型チェック
if(p instanceof Student){
    Student st = (Student)p;
    //正しくキャストできたときの処理
}

型取得

  • getClassメソッドでオブジェクトの型を取得
  • 変数の型によらずオブジェクトの型を取得
public class Main {
    public static void main(String[] args) {
    Person p1 = new Person();
    System.out.println(p1.getClass()); //class Person
    Person p2 = new BusinessPerson();
    System.out.println(p2.getClass()); //class BusinessPerson
  }
}

委譲

  • 継承は基底/派生クラスが密結合
  • 継承を使うタイミング
    • 基底/派生クラスがis-a関係
      • リフコフの置換原則:基底クラスの変数にその派生クラスのインスタンスを代入してもコードの妥当性が損なわれない
    • 拡張を前提とし、その旨を文書化している
  • 継承が不適な例
import java.util.Random;

public class Roulette extends Random {
//ルーレット上限
private int bound;

  public Roulette(int bound) {
    this.bound = bound;
  }
  //boundフィールド上限とする値を取得し乱数生成
  @Override
  public int nextInt() {
    return nextInt(this.bound);
  }
  //他の不要メソッドは無効化している(リフコフの置換原則に反する)
  @Override
  public boolean nextBoolean() {
    throw new UnsupportedOperationException();
  }

  @Override
  public long nextLong() {
    throw new UnsupportedOperationException();
  }
}
import java.util.Random;

public class RouletteClient {
  public static void main(String[] args) {
    Random rou = new Roulette(10);
    System.out.println(rou.nextBoolean()); //UnsupportedOperationException
  }
}
  • 委譲では再利用したい機能をもつオブジェクトを現クラスのフィールドとして取り込む
    • 委譲はインスタンス同士の動的な関係である
  • RouletteクラスのrandomフィールドにRandomオブジェクトを保持(has)し必要に応じてRandomクラスのメソッドを利用できる
    • has-a関係という
  • メリット:
    • クラス同士の関係が緩くなる
    • publicメンバーを利用するので内部実装に左右しない
    • フィールドでインスタンス保持するので関係が固定しない
      • 後から委譲先変更可能
      • 複数クラスに処理委譲可能
      • インスタンス単位で委譲先変更可能
import java.util.Random;

public class Roulette {
  private int bound;
  //以上先のオブジェクトをフィールドに保持
  private Random random = new Random();

  public Roulette(int bound) {
    this.bound = bound;
  }
  //必要に応じて処理を委譲
  public int nextInt() {
    return this.random.nextInt(this.bound);
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】byte配列を16進数にしたい

やりたいこと

文字コード“あ”をUTF-16にしたい

期待する結果

10進数:48(10) 46(10)
16進数:30(16) 42(16)

実現方法

String.FormatSystem.out.printf を使う

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String stringValue = "あ";
        byte [] mojiCode = stringValue.getBytes(StandardCharsets.UTF_16BE);
        System.out.println(mojiCode);        //[B@372f7a8d
        System.out.println(  Arrays.toString(mojiCode)); //[48, 66]
        for (Byte code : mojiCode) {
            System.out.printf("%02x", code); //3042
        }
        System.out.println("");
        StringByteArray stringByteArray = new StringByteArray(mojiCode);
        System.out.println(stringByteArray.toHexString()); //3042
    }
    static class StringByteArray {
        byte[] byteArray;
        public StringByteArray(byte[] byteArray) {
            this.byteArray = byteArray;
        }
        public String toHexString() {
            StringBuffer stringBuffer = new StringBuffer();
            for (byte code : byteArray) {
                String hexString = String.format("%02x", code);
                stringBuffer.append(hexString);
            }
            return stringBuffer.toString();
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】カプセル化 (Encapsulation)

カプセル化とは

  • 使って欲しい機能に関係ないものは見せない
    • ex: テレビ。中の回路はブラックボックスにして、利用者側には電源、画面などの機能のみ見えている
    • 無関係な部分に不用意に触ることがないようにする
  • クラスの機能を実現するための内部的な機能は隠し、使って欲しい機能のみ示す

アクセス修飾子

  • 特定のメンバーを見せるかどうか管理
  • 要件を満たす範囲でできるだけ強い制約を課すこと
  • パッケージ外からのアクセスを意図してない限りパッケージプライベートを基本(Java9以降モジュールがpublic/privateの間を埋めた)
    • public:全クラスからアクセス可能
    • protected:現クラスと派生クラス、同じパッケージクラスのみアクセス可
    • なし:現クラスと同じパッケージクラスからのみアクセス可能
    • private:現クラスからのみアクセス可

フィールドのアクセス権限

  • クラスのデータをフィールドで外部に公開
  • インスタンスフィールドはpublic宣言するべきではない
    • 読み書き許可/禁止制御できない
      • フィールドはオブジェクト状態を管理するための変数
      • 値の取得はしても変更は制限したい
      • フィールドは単に変数なので修飾子でアクセスの可否を決めるとその値を取得変更できる
      • 不変クラス:インスタンス化のタイミングで内部状態固定し変更できないクラス
    • 値の妥当性を検証できない
    • 内部状態の変更に左右
      • ageフィールドがintではなくdoubleに変更された場合など、ageフィールドを参照する全コードが影響する
  • →アクセサーメソッドを使う!
//よくない例
public class Person {
  public String name;
  public int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String show(){
    return String.format("%s(%d歳)\n", this.name, this.age);
  }
}

Accessor Method(アクセサーメソッド)

  • オブジェクトの内部状態(=フィールド)は外部から直接アクセスできないようにして、取得も変更もメソッドを介する仕組みを実現する
  • アリクサーメソッドの名前はget/set+フィールド先頭を大文字
    • メソッドなので例外処理も可能(throw命令など)
    • 取得時に値加工可能
    • getter/setterのみ用意してフィールドを読み取り/書き込み専用にできる(読み取り専用推奨)
public class Person {
  //フィールドはprivate
  private String name;
  private int age;

  //nameフィールドのゲッター
  public String getName() {
    return this.name;
  }
  //nameフィールドのセッター
  public void setName(String name) {
    this.name = name;
  }
  //ageフィールドのゲッター
  public int getAge() {
    return this.age;
  }
  //ageフィールドのセッター
  public void setAge(int age) {
    if (age <= 0) {
      throw new IllegalArgumentException("年齢は正数で指定してください。");
    }
    this.age = age;
  }

  public String show() {
    return String.format("%s(%d歳)です。", getName(), getAge());
  }
}
public class AccessorBasic {

  public static void main(String[] args) {
    var p = new Person();
    p.setName("イチロー");
    p.setAge(47);
    System.out.println(p.show()); //イチロー(47歳)です。
    p.setAge(-30);  //エラー
  }
}

不変クラス

  • オブジェクトを最初に生成したところから一切値(フィールド)が変化しないクラス(推奨)
//クラスそのものをfinal宣言し拡張しない
public final class Person {
  //全部private final(再代入禁止)
  private final String name;
  private final int age;

  //コンストラクタでprivate finalフィールド初期化
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  //ゲッター
  public String getName() {
    return this.name;
  }

  public int getAge() {
    return this.age;
  }
}
public class ImmutableBasic {
  public static void main(String[] args) {
    var p = new Person("サトシ", 10);
    System.out.println(p.getName()); //サトシ
  }
}
  • 不変性が破れてしまう例
    • 参照型の代入は参照そのものを渡すのみ
    • 実引数/戻り値で受け渡した値を変更すると内容はフィールドにも影響
    • 例外としてString型は不変型
import java.util.Date;

public final class Person {
  private final String name;
  private final int age;
  private final Date birth;

  public Person(String name, int age, Date birth) {
    this.name = name;
    this.age = age;
    this.birth = birth;
  }

  public String getName() {
    return this.name;
  }

  public int getAge() {
    return this.age;
  }

  public Date getBirth() {
    return this.birth;
  }
}
//インスタンスに渡した値を後から変更する例
import java.util.Date;
public class Main {
  public static void main(String[] args) {
    var birth = new Date();
    var p = new Person("サトシ", 10, birth);
    System.out.println(p.getBirth());  //Tue Nov 03 11:26:33 UTC 2020
    //インスタンス化の際に渡したオブジェクトを更新
    birth.setDate(15);
    System.out.println(p.getBirth());  //Sun Nov 15 11:26:33 UTC 2020

//インスタンスから取得した値を変更する例
//    var p = new Person("サトシ", 10, new Date());
//    System.out.println(p.getBirth());  //Tue Nov 03 11:29:05 UTC 2020
//    var birth = p.getBirth();
//
//    birth.setDate(15);
//    System.out.println(p.getBirth());  //Sun Nov 15 11:29:05 UTC 2020
  }
}   

不変性が破れないようにする方法

  • 引数/戻り値に受け渡す際に防御的コピーする
    • オブジェクトをそのまま受け渡すのではなく複製したオブジェクトを受け渡す
  • 戻り値として返す際に不変型に変換
    • StringBuilder型のフィールドを外部に引き渡す際にString型に変換する
//コンストラクタ引数birth,birthフィールドからgetTmeメソッドでタイムスタンプ値をとりだし新オブジェクト生成
import java.util.Date;

public final class Person {
  private final String name;
  private final int age;
  private final Date birth;

  public Person(String name, int age, Date birth) {
    this.name = name;
    this.age = age;
    //防御的コピー
    this.birth = new Date(birth.getTime());
  }

  public String getName() {
    return this.name;
  }

  public int getAge() {
    return this.age;
  }

  public Date getBirth() {
    //防御的コピー
    return new Date(this.birth.getTime());
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring-Sessionのセッションをぶった切る

はじめに

Webアプリケーションの特定セッションを外部から切断したくなったことはありませんか?
「危険なヤツ発見! とりあえず切断」
みたいな。
しかもWebアプリケーションには手を入れずに・・・
対象はSpring-Sessionを使ったWebアプリケーションでセッションをRedisに保持しています。
それなら単純に
「Redisからセッション情報を消してしまえ」
と考えた訳です。うまくいくか実験してみます。
もっとスマートな方法を知っている方、ご指摘いただければ幸いです。

制限事項など

本記事の内容は以下の環境で動作確認しております。

  • java version "11.0.7" 2020-04-14 LTS
  • Docker Desktop 2.2.0.5
  • Windows 10 + MINGW32(git-bash) (Macでも動作確認中 多分動くと思います)

Redisを準備

Redisの準備はDockerが簡単です。こちらを利用させていただきました。
docker-composeでredis環境をつくる

$ docker-compose up -d 

で、Redisを起動しておきます。

※もちろん、使用しているOSに直接Redisをインストールしても構いません。

Spring-Sessionアプリケーションの準備

Spring-Sessionアプリケーションがなければ実験できませんが、いい感じに単純なサンプルアプリが見つかりません。そこで簡単なアプリを作製して以下に配置しました。
Sprint-Session-Sample

Spring-Session-Sampleの動作

1.ダウンロードもしくはクローンしたディレクトリでアプリを実行

$ ./gradlew bootRun

Redisのデフォルトポート(6379)に接続して、うまく起動すれば

Started SSSApplication in XXXX seconds

上記の文字列が現れます。

2.http://localhost:8080/ にアクセス

image.png
各フィールドに文字列を入力して [next] ボタンを押すと
セッションを引き継いだ次の画面に入力した文字列が表示されます。
image.png
セッションが(デフォルト30分で)切れると、エラー画面の遷移します。
image.png
外部からの操作で、この状態を再現します。

Redisの観察と操作

Redisのどのようなアイテムが登録されてセッションを管理しているのかを観察します。

  • redis-cliの利用
$ docker exec -it [CONTAINER ID] /bin/bash
root@[CONTAINER ID]:/data# redis-cli
127.0.0.1:6379> (ココにコマンド入力)

※CONTAINER IDはdocker psで確認してください。
まず、Redisをクリアしておきます。

127.0.0.1:6379> flushall

http://localhost:8080/ にアクセスして、Redisを確認すると・・・

127.0.0.1:6379> keys *
1) "spring:session:sessions:0b288446-d209-4ecc-bfc4-7adf405e68a7"
2) "spring:session:sessions:expires:0b288446-d209-4ecc-bfc4-7adf405e68a7"
3) "spring:session:expirations:1604421420000"

3つのアイテムが確認できます。
0b288446-d209-4ecc-bfc4-7adf405e68a7 はHttpSessionのセッションIDで実際に情報が保持されているのは、

1) "spring:session:sessions:0b288446-d209-4ecc-bfc4-7adf405e68a7"`

上記のアイテムの様です。
これを削除してしまえばセッションが切れる筈!

やってみよう

1.http://localhost:8080/ にアクセス

image.png

2. Redis確認とセッション削除

127.0.0.1:6379> keys *
1) "spring:session:sessions:0b288446-d209-4ecc-bfc4-7adf405e68a7"
2) "spring:session:sessions:expires:0b288446-d209-4ecc-bfc4-7adf405e68a7"
3) "spring:session:expirations:1604421420000"

セッション情報を削除

127.0.0.1:6379> del spring:session:sessions:0b288446-d209-4ecc-bfc4-7adf405e68a7 
(integer) 1
127.0.0.1:6379> keys *
1) "spring:session:sessions:expires:0b288446-d209-4ecc-bfc4-7adf405e68a7"
2) "spring:session:expirations:1604421420000"

3.[next]ボタンを押してみる

image.png

セッションを強制切断できました。
残りの2アイテム

127.0.0.1:6379> keys *
1) "spring:session:sessions:expires:0b288446-d209-4ecc-bfc4-7adf405e68a7"
2) "spring:session:expirations:1604421420000"

についてはTTL(有効期限)が設定されているので、とりあえず放置でよいでしょう。

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