Kotlin で SpringBoot (2.0.0.M6) で DI した時のメモ
Java では普通に使ってたのだけど、Kotlin で試して見たメモ。
サンプルは単純に設定を読むだけのコード.
package net.white.azalea.todo.utilities.security import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.stereotype.Component @Component @ConfigurationProperties(prefix = "crypt") class CryptConfig(var secret: String = "value")
地味にハマったのが下記。
- コンストラクタ引数に初期値を与えないと、なぜか Spring が「引数入りのコンストラクタみつかねーよ」とゴネて落ちる
どうやら Kotlin の生成するコンストラクタをコンストラクタとして認識できない模様。
そのため、初期値を与えてクラスフィールド化してやれば、コンストラクタインジェクションではなく、フィールドインジェクションとして辛うじて認識してくれる。 - 上記がわかると、var も指定してやらないと「setter ねーよ」といってコケる。
- 試した感じ、コンストラクタインジェクションを諦めて、クラス変数に
@Inject
するくらいが一番分かりやすい。
まだまだ kotlin での運用は微妙臭い印象。
個人的に、Kotlin で Spring はやらなくていいかなという気持ち。
わんくま勉強会(109回メモ:終了まで随時更新)
PowerShell とコマンドライン
- はるにゃさん
歴史的な話。
2003 年から開発開始、2006 年公開、Windows10 の Creators Update で Win+R のデフォルトシェルになった。
コマンドプロンプトは、そもそも DOS からずっと動いてた。
拡張されてきたくせに実は仕様書がない(過去の仕様があるにはある)。
バッチファイルの正式には cmd が正しく、bat は下位互換でのこされた…はずがずっと生きてる。
exe/com が実行バイナリだが、com はもう殆ど残ってない。
dir /s /b g:¥Windows¥System32¥*.exe
とかで探す。
- if 文と比較演算が大きく変化
DOS だと基本コマンド(シェル系)と、実行コマンド exe に分かれる。 シェルコマンドは、if - else しか書けない、要するに elseif がないし、利用可能な比較演算子が==, EQU, NEQ,GER, GEQ, LES, LEQ
くらい。
対して、PS だと-eq, -ne, -gt, -ge, -lt, -le
と意味が分かりやすくなった。 - DOS シェルの FOR が死ぬほど分かりづらかったが、PS で foreach (コマンドラインだと For-Each のみ)で書ける。
- pause 消えた
- goto / call / exit この辺りは Function に変わった
call の代わりに Start-Process で扱うなど
ただ、パワーシェルスクリプトは署名がつかないとデフォルトでは実行できない。
Set-ExecutionPolicy
で実行ポリシー指定する事で先ずは実行できる。
パワーシェルは言語寄りなので、細かい事できるようだ。
ただし、起動速度はどうあがいても DOS の方が早い、現状では DOS から PowerShell を呼び出して利用するのがオススメ。
LT
- Marp 紹介
- 5分でわかる MR
MR の定義説明。
RDB と数学
- うづきさん
正規形さえしていない非正規形データとは、以下のような感じ。
- カラムにCSVとか複数の値
なんというか蕁麻疹湧きそうなクソデータである。
(発表が死ぬほど香川推しなんだが(汗)
ここから第四正規形まで…(面倒なので略。知らない人は調べて)
RDB はリレーションによる 1:N 関係のデータを作る。
これは n 項関係という数学モデルとマップできる。
N項関係(二つ以上の直積集合の部分集合)、直積集合(単純列挙)という事らしい。
直積集合 を S1 = (a, b), S2 = (c, d)
として、S1 x S2 = (a, c), (a, d), (b, c), (b, d)
となり、この部分集合 (a, d), (b, c)
N 項関係というらしい。
この条件で見ると
- 第一正規形、様々な種類のデータ(名前と職業等)がごちゃ混ぜとなっているものは、単純値と呼ぶ
ある集合値の特定組み合わせが決まると、他の値も決定できるというような従属性を関数従属性という。
例えば、郵便番号が決定すれば市区町村まで断定できる。要するに郵便番号に対して都道県市区町村名は関数従属なわけだ。
こうした関係が保たれているのが第3正規形。
DB で言えばプライマリキー決まれば、値がが取れると言える。
多値従属性…(時間がない
代数学の話題
- IIJIMAS さん
集合の定義、基本の話から開始。A={x: x: 正整数}
d:X → Y
X に対してただひとつの Y が対応する物を写像。
また、x ≠ z => f(x) ≠ f(z)
の時単射という。
y∈Y
において、f(x) = y
が言える時、 f を X から Y の上への全射という。
直積: X,Y の要素に対して (x,y) という順序づけられた組み合わせ全体の集合を直積(XxY)という。
二項演算:ρ: SxS → S
を二項演算という。
群:含まれる任意 2 要素でいくつかのルールが成り立つ 整数Z、有理数Q、複素数C、実数R
環:足し算で前後可換、乗法で結合律 (ab)c=a(bc)
が成り立つ。分配律a(b+c) = ab+ac
が成り立つ。
…数学記号が入力しきれない…
k-means を紐解く
- XENO@小柴
クラスタリングアルゴリズムのひとつ。
つっても、分かりやすい話は、平面にデータマップして、大まかに「K 個グループあるはずだ!」という想定の元、画面上に K 個の移動可能なポインタをランダムに置く。
で、ポインタと各要素の平均距離を計算していって、ポインタの位置をずらしていき、移動しなくなったら完成。
で、ポインタから見て近い要素は同一グループとみなすと…
Marp がやたら便利な件
会社で業務外に勉強会を開きたくなったのでその資料
誤解を招きそうな表現ならどこかにありそう…。 指摘あると助かるかな…
SpringBoot2 でたお
今気づいた。
見てさわればわかるけど、SpringBoot2 の名前がある。
対応しているバージョンは Spring 5 らしい。
公式を表面だけなぞっても Reactive Web に対応したらしい。
非同期万歳!
というのは多分自分みたいなオタクだけ…。
悲しいけど、非同期をきちんと理解できるプログラマは案外少ないのです。
DI 入門者向けの話(DI が生まれる背景)
[日記][Tips]DI 入門者向けの話(DI が生まれる背景)
Dependency Injection 日本語訳で、外部依存注入の話をする。
前提知識
オブジェクト指向には多態性なるものがある。
これは、Java でいうところのインターフェースに依存する作りをすることで、その実装を変更できるという論理だ。
割と具体的にコードに落とすと
class AppendHello { public String wrap(String name) { return "Hello, " + name + " name count are " + this.countValue(name); } public int countValue(String name) { return name == null || name.equals("") ? 0 : name.size(); } } public class Main { AppendHello wrapper = new AppendHello(); public void main() { // static でないのはサンプルだから String result = wrapper.wrap("Azalea"); System.out.println(result); // ここは変更なし return result; } }
ってしてしまうと、Main はあまりに AppendHello にべったりしすぎてしまい、AppendHello でなにか修正を受けると影響がモロに出てしまう。
そこで、本当に必要なインターフェースを用意し、Main から依存するのをインターフェースにしてしまえば、何かの理由で AppendHello に手が入っても、Main 処理に影響を及ぼさないし、何よりインターフェースを継承した別のもので代用できる。
これが多態性だ。
interface StringWrapper { String wrap(String value); } class HelloWrapper implements StringWrapper { @Override public String wrap(String name) { return "Hello, " + name + " name count are " + this.countValue(name); } public int countValue(String name) { return name == null || name.equals("") ? 0 : name.size(); } } public class Main { StringWrapper wrapper = new HelloWrapper(); public void main() { String result = wrapper.wrap("Azalea"); System.out.println(result); return result; } }
例えば、HTML でラップするものに差し替えるとしても
class HtmlWrapper implements StringWrapper { @Override public String wrap(String name) { return String.format("<p>%s</p>", name); } } public class Main { StringWrapper wrapper = new HtmlWrapper(); // ここだけ修正 public String main() { String result = wrapper.wrap("Azalea"); // ここは変更なし System.out.println(result); return result; } }
つまり、メソッドの呼び出し方と、その応答データの種類に事前に制約をかけて、そのルールでクラスを実装する限り、使用している Main に修正の手が伸びない。
置き換えて色々な事に使用できる。
そういうことだ。
問題点と当初考えられた解決法
しかし、これには半端に2つ問題が発生する。
1: コンパイル時点で固定になる
それは new した時点で固定となり、処理の入れ替えが効かない事である。
public class Main { // ↓ここで new するやつ固定じゃん StringWrapper wrapper = new HtmlWrapper(); public void main() { System.out.println(wrapper.wrap("Azalea")); } }
つまり、入れ替えしたければ再コンパイルしなければならないという事だ。
2: テストしにくい
- Main.main の挙動が、HtmlWrapper の実装に依存しているため、Main.main の応答結果をテストしたければ、最初に HtmlWrapper の動作を考慮しなければならないという問題がある(仮にこれがデータベースを使うものだと考えてみればいい…表面的に関連性のわからないレコードを大量にテスト準備で用意するのはあまりに大変だ)。
- HtmlWrapper は別クラスなので、何らかの理由で修正が入るかもしれない、つまりこのテストは HtmlWrapper が修正されるたびに書き直さなければならないのだ。
- HelloWrapper 等に置き換える旅にテストを変更しなければならない。これもアフォかと。
オブジェクト指向的に頑張って解決(古典的手段)
この問題を解決するために、オブジェクト指向界ではデザインパターンという名前のあるアルゴリズムのごとく、「こんな時にはこういうデザイン(設計)パターン(型)を適用する」といった設計パターンを作り上げた。
上記であれば、FactoryMethod パターンだ。
interface StringWrapper { String wrap(String value); } class HelloWrapper implements StringWrapper { @Override public String wrap(String name) { return "Hello, " + name + " name count are " + this.countValue(name); } public int countValue(String name) { return name == null || name.equals("") ? 0 : name.size(); } } abstract class Main { abstract protected StringWrapper getWrapper(); // 依存部分だけ切り出す public void main() { String result = this.getWrapper().wrap("Azalea"); System.out.println(result); return result; } } public class HelloMain extends Main { @Wrapper protected StringWrapper getWrapper() { return new HelloWrapper(); } }
このようにすることで、
- Main だけみれば StringWrapper 部分は切り離されているため、使うべき所で継承して用意すればよい。
- Main.main は StringWrapper の実装には依存していないので、テスト時には都合のいい偽物にでも置き換えできる。
他にも、 "AbstractFactory":https://ja.wikipedia.org/wiki/Abstract_Factory_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3 なんかもあるので、見てみるといい。
オブジェクト指向だけでの限界
こうする事で、そこそこ動的に入れ替えが効くようにはなるがいくつか問題が起きる。
- クラス爆発
Main に対し、各実装を含む HelloMain とか HtmlMain とか、ともかくクラス数が増える。
それは保守性や可読性に対してマイナスだ。 - 仕組みのためのコード量が多い
実際に業務上使用したいコードという訳ではないのに、汎用性を求めるための追加コードがやたらと増える。
そのコードが混ざると、「本来要件的に解決したいコード」が、「仕組みのために必要なコード(非業務要件)」に埋もれてしまい、保守性が下がる。
(こうした、仕組み上書かなければならない、業務的に意味のない定型コードは「ボイラープレート」と呼ばれる)
インジェクション
そこで、そうした依存は外から設定注入(インジェクション)できるようにすればいいのではないかと考える。
つまり、必要なオブジェクトは使う時に外から指定してやれば良いのではないかと考える。
その案のもっとも原始的方法は、「依存するオブジェクトをコンストラクタで受け取ってしまう」である。
要するにクラスの中で new しなけりゃいいんだろというなんとも簡単な話。
public class Main { StringWrapper wrapper; public Main(StringWrapper wrapper) { this.wrapper = wrapper; } public void main() { System.out.println(wrapper.wrap("Azalea")); } }
このクラスだけ見れば、これで問題ない様に見える。
しかし使う側で固定化問題が起こる訳だ。
public UseMain { // 使う側でこうなってしまう // 結局フレキシブルになりきれない Main mainClass = new Main(new HelloWrapper()); public String useMain() { String res = mainClass.main(); // 何か後続処理 } }
ならどうするか?
そう考えた時に行き着く技術がある。
リフレクション である。
リフレクションを使ったインジェクション
まずリフレクションについて少し話そう。
通常、Java のオブジェクトとはクラスを書いて、new して初めて利用できる。
このオブジェクトの定義をクラス、new してできた実体をインスタンスと呼んでいる。
ここでいう所のクラスとは何かを考えると、インスタンスが実際に動かすことができるメモリ上のプログラムなら、それを形作る、もしくはその挙動を定義するクラスとは、すなわちメモリ確保時に使用する設計図だ。
普段は new しかしないが、そこに設計図がある。
なら、その設計図から出来上がったインスタンスの中身は、つまりメモリの形が把握できるということ。
なら、設計図を見ながらムリヤリでも書き換えることができるのではないか?
実際にそれを行う機能が「リフレクション」と呼ばれる機能群だ。
例えば、setter のない private な文字列フィールドを持つクラスを考える。
class Example { private String name; public Example(String value) { this.name = value; } public String getName() { return this.name; } }
普通に考えたら、name はコンストラクタからしか設定することができない。
だが、リフレクションを使うとここに値を設定できてしまう。
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; class Example { private String name; public Example(String value) { this.name = value; } public String getName() { return this.name; } } public class Main { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { // new を使わず設計図からムリヤリインスタンス作成 Example example = Example.class.getDeclaredConstructor(String.class).newInstance("Hoge"); System.out.println(example.getName()); // Hoge が表示 // name の設計図を取得 Field field = Example.class.getDeclaredField("name"); // セットアクセス権限を設定 field.setAccessible(true); // example の name をムリヤリ変更 field.set(example, "Update"); System.out.println(example.getName()); // Update が表示 } }
この機構を使い、設定ファイルか何かでクラスの外からの入力を制御すれば、なんとコードを一切いじらずに実装を変更、挙動を変えることができるのではないかと考える。
クラスの外(外部)から、依存する処理(依存)を流し込む(注入)ことで外部依存注入、DI となるわけだ。
この機能を使ったライブラリを、「public static void main」で初期化し、残りの全てのインスタンス生成してもらえば、あとは全てのクラスで new による直接的な依存がなくなる…すなわち、疎結合とテスタビリティの確保ができるようになる。
気が向いたら続く。
MyBatis の定義がクソ面倒なので、生成を狙う
A5:SQL Mk-2 - フリーの汎用SQL開発ツール/ER図ツール .. 松原正和 使って作ったテーブルのマッピングを書くのがだるくなったので、自動生成を狙った。
まずはこんな感じの ER を書いて、CSV でエクスポートする。
一応設定は UTF-8 で吐く。
そうすると、a5m2_COLUMNS.csv
なるファイルが出来上がる。
TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME,LOGICAL_NAME,ORDINAL_POSITION,COLUMN_DEFAULT,IS_NULLABLE,DATA_TYPE,KEY_POSITION,DESCRIPTION Example,,example_entity,id,ID,1,,NO,BIGINT IDENTITY,1, Example,,example_entity,name,名前,2,,NO,NVARCHAR(64),, Example,,example_entity,price,価格,3,,NO,@INT,, Example,,example_entity,example_field,その他適当,4,,NO,"@DECIMAL(10, 3)",,"これが 複数行" Example,,seller,id,ID,1,,NO,BIGINT IDENTITY,1, Example,,seller,example_entry_id,商品ID,2,,NO,BIGINT,, Example,,seller,name,店名,3,,NO,NVARCHAR(64),, Example,,seller,created_at,作成ビ,4,CURRENT_TIMESTAMP,NO,@DATETIME,, Example,,seller,updated_at,更新日,5,,YES,@DATETIME,,
あとはこれを自動生成できりゃいい。
ちなみに SQL Server 用に設定したけど、他のものを使いたければちょっと改変すればいい。
Python3 で読み込んで、マッピング java bean (Lombok) と mapping 用 XML を吐き出した。
import csv def to_camel_name(name): components = name.split('_') return components[0] + "".join(x.title() for x in components[1:]) def to_upper_camel_name(name): components = name.split('_') return "".join(x.title() for x in components[:]) def starts_with(string_value, arr): for v in arr: if string_value.startswith(v): return True return False class Field: def __init__(self, name, id, dataType, description): self.name = name self.id = id self.dataType = dataType self.description = description def db_type(self): stn = self.dataType.lower() convert = { 'INTEGER' : ['@int', 'int'], 'BIGINT' : ['bigint', '@bigint'], 'DECIMAL' : ['@decimal', 'decimal'], 'VARCHAR' : ['@char', 'char', 'varchar'], 'NVARCHAR' : ['nchar', 'nvarchar'], 'BOOLEAN' : ['bit'], 'TIMESTAMP': ['date', 'datetime', 'timestamp', '@datetime'] } for key in convert.keys(): if starts_with(stn, convert[key]): return key return None def type_name(self): stn = self.dataType.lower() convert = { 'Integer' : ['@int', 'int'], 'Long' : ['bigint', '@bigint'], 'BigDecimal': ['@decimal', 'decimal'], 'String' : ['@char', 'char', 'varchar', 'nchar', 'nvarchar'], 'Boolean' : ['bit'], 'Date' : ['date', 'datetime', 'timestamp', '@datetime'] } for key in convert.keys(): if starts_with(stn, convert[key]): return key return 'Object' def get_field_definition(self): changed_type = self.type_name() field_name = to_camel_name(self.id) str = '' str = str + f' /**\n' str = str + f' * {self.name}.\n' str = str + f' *\n' str = str + f' * {self.description}.\n' str = str + f' */\n' str = str + f' private {changed_type} {field_name};\n\n' return str class_field = {} # クラス辞書 try: with open('a5m2_COLUMNS.csv', 'r') as csvfile: spamreader = csv.reader(csvfile, delimiter=',', quotechar='"') for row in spamreader: if len(row) == 0: continue class_name = to_upper_camel_name(row[2]) field = Field(row[4], row[3], row[8], row[10]) if class_name in class_field: class_field[class_name].append(field) else: class_field[class_name] = [field] # 起こりそうな例外をキャッチ except FileNotFoundError as e: print(e) except csv.Error as e: print(e) for class_name in class_field.keys(): file = open(f'{class_name}.java', 'w', encoding='UTF-8') file.write('\nimport lombok.*;') file.write('\nimport java.util.Date;\n') file.write('\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n') file.write('public class ' + class_name + ' {\n') for field in class_field[class_name]: file.write(field.get_field_definition()) file.write('}\n') file.close() file = open(f'{class_name}.mapping.xml', 'w', encoding='UTF-8') file.write(f'<resultMap id="{class_name}Map" type="{class_name}">\n') for field in class_field[class_name]: cur_db_type = field.db_type() db_str = ('jdbcType="' + cur_db_type + '"') if cur_db_type != None else '' file.write(f' <result property="{to_camel_name(field.id)}" column="{field.id}" {db_str} />\n') file.write('</resultMap>\n') file.close()
このスクリプトは好きに使って。
CSV の1行目呼んで変なの出るけどご愛嬌で!