WindowsPC同士で有線LAN1本直結でリモートデスクトップ接続する

クロスケーブルを買って接続

ストレートケーブルとクロスケーブルを見分ける方法 | バッファロー

通常のLANケーブル(ストレートケーブル)に対して、 端子のピンの並びが端と端同士で鏡のように左右対称に なっているLANケーブルのこと。 PC同士の接続に使う。ルータやハブには使えない。

IPアドレスを手動で設定する

Windows 10でLANケーブルでPC同士を繋いでファイル共有 - キリウ君が読まないノート

WindowsPCを接続しただけでは、DHCP(自動でIPアドレスが 割り振られる仕組み)が働かない。 通常、ブロードバンドルータや企業ネットワークなどでは DHCPサーバーが担当。)

よくわからなければ、サイトの通りに設定すると間違いがない。

蛇足 プライベートIPアドレスとは

なお、元のLANに接続しなおして「IPアドレスが重複しています」 などのエラーが出たら、即時IPアドレスを自動設定に戻すようにする。 (大目玉くらいます)

リモートデスクトップで接続される側のWindowsリモートデスクトップを有効にする

PC でリモート デスクトップを有効にする | Microsoft Learn

リモートデスクトップで接続される側のWindowsの「パブリックネットワーク」のファイアウォールを切る

直接LANで接続している場合必ずパブリックネットワークになり、 デフォルトではファイル共有およびリモートデスクトップ接続が無効となる。 セキュリティソフトやWindowsファイアウォールの設定を見て、 パブリックネットワークの設定を変える。

Norton Intenet Security リモートデスクトップ許可設定 - KuroNeko666’s blog

リモートデスクトップのWindowsファイアウォール設定 | 日記というほどでも

以上

これで接続できなかったら、pingとかで地道に検証。

謎シチュに至った訳

初学者がUMLだけやっても意味がないのでは?

身も蓋もない言い方

設計は沼であり、大事な仕事・作業・人間生活にかかることであればちゃんと武装して臨むべき。

UMLは可視化ツール

クラスを中心としたソースコードを書けば、対応するUMLは半ば機械的に生成できる。 UMLは地図であって、極地的な地形であってもとりあえずそのままに書けてしまう。

良い設計は、コンセプトを伴ったパターンを持つことが一種指標に思える。 例えば、クラス図は汎化を表現できるが、以下のように指向するとよい設計につながる。

  • あるデスクトップアプリケーションのデータ書き込み処理は、同じデータを固定長レイアウト、JSON形式、CSV形式の3パターンで表現する。
  • 3パターンの形式を決定するのは、書き込み時にGUI指定する。それ以外のファイル書き込み時のハンドリングはすべて共通。
  • それぞれの派生クラスを「生成」し、後続のファイル書き込み処理に、書き込みデータ供給の共通インタフェースを通じて処理をさせる。

こう言葉で書くとながったらしいことを、UMLは記号化してくれる。 しかし、この複雑さを簡略化はしてくれず、説明を端折ると謎の図になる。

実装パターンを豊富に知っているのであれば、思いつくままパズルの要領でつなぎ合わせて6,7個ぐらいの概念を同時に入れたところで依存性の糸の絡まり具合を可視化し、ここは切れるとかこの概念は相性が悪そうだとかを診断するためのツールになる。

初学者にとってのUML

まだ学生の頃にカリキュラムとしてUMLを触れた当時は価値がわからなかった。 簡単なMVCモデルの1画面をクラス図に書いただけで整理の必要が全くなかったのが一番の原因だが、 複雑なものを表現したとしても、知見不足で書いてみたのでは結局わからずじまいだったかもしれない。

そもそも、UML関連の学習コンテンツは説明が悪いことが多い。 「集約とは、集めて持つことである」「包含とは内部にオブジェクトを隠ぺいして持つことである」「依存はそのクラスを使っていることである」 と定義はわかるのが、なんのために依存するとか、この構造を持つことで特定の処理時にパターン化できるとか、ケーススタディ的な学びがUMLと一緒にできる例についに出会うことはなかった。 「設計とかコーディングのときに気づき内省しものにせよ」と寿司職人さながらのマインドはまだしも、UMLは握ってモチベーションに駆られるかの観点から題材の悪さしか印象にない。

設計とUML

UMLは良くも悪くも可視化ツールだ。 なので、パターンとコンセプトのある設計を表現してみて確からしさを検証するための手遊びの道具であり、 実際の実装や他の図と反復しながら多角的に検証したり、豊富なナレッジベースを並べてみて組み合わせを評価するのが一番道具として生きると考えている。 もし、UMLがしっくりこないとか意味が解らないのであれば、少し仕事や勉強から逸れたことをして、思い切り複雑で新しいことをしたときにでも思い出して無理やり書いてみるとどうだろうか。

【ExcelVBA】VBAはクラス指向より手続き型でFATに作りたい

前置き

VBAでもクラスは作成できる。

qiita.com

しかし、フォルダや階層による小分けができない。 例えば設定ファイルの保持やパースのために作ったConfigクラス、特定ファイルの書き出しのために作ったExternalXXXCSVFileクラスやExternalYYYCSVFile、内部のファイルの保持のために作ったTempFileクラス、その他値の保持用だったりビジネスロジックがぐちゃぐちゃに入り組んだりする実態が同じフォルダに入る。 そこそこの規模のアプリを作ろうとしたとき、違う考え方の領域が10個ぐらい、系3,40個のクラスが並列に並ぶことになる。これはWeb系の言語でやっているようなスケーラブル(脳死で拡張可能)なオブジェクト指向なクラスの管理だろうか?

プロジェクト(=Excelブックやxlamファイル)で分ければできるが、全部読み込むようにするのは手間だし分割も面倒、本末転倒だ。

そこそこ小さい実装ならまあいい感じの実装にはなるだろう、しかし個人としては、VBAでクラス指向*1 で設計するのはアンチパターンと位置付けるに至った。

MVCは、1つの標準モジュール/Excelシート/シートVBAを1つの「M」「V」「C」に見立ててみる

Web系に馴染みのある方であればMVCパターンを適用したくなるだろう。 ここで、それぞれの要素をModel(処理の本体)、View(ユーザーインタフェース)、Contoller(MとVの繋ぎ)で考える。(※Web系実装のMVCは通常、Mはステートフル=DBやデータなどの状態を持ち更新処理などがあるが、ここでは基本的にステートレス=データ保持無しのため、実行時パラメータによって完全に決まるものとする。)

V。Excelシートそのもの。 C。Excelシートをクリックしたときに実行するハンドラ。色々な変換処理がある。Mを呼び出す。 M。Cの結果を受けて、何かしら外部ファイルを出力したり作用したりするメソッドのみの存在。単独テストが可能。

V → Cへの繋ぎは、ExcelのRangeやCellといった各種APIで代用となる。ここら辺が特にツライが、データ化すればこっちのもの。FormやDto、Entity、Recordといった実体が欲しいのであればCreateObject("Dictionary.Scripting")で連想配列を使ったり、そこだけクラスモジュール化してもよい。 C → Mへの繋ぎは、V→Cでデータ化したものをそのまま受け渡すだけでよい。

以上、やりたいことは、引数と戻り値だけの標準モジュールで実装できないだろうか。

モジュールとImmutableを基本に設計・実装する

ローカル変数以外に、メモリ中に状態を持たず、処理結果や確保したリソースを引き続き保持したければすべて引数・戻り値を介して取得・保持し、メンバー変数やグローバル変数は必要時以外排除すると、自然とImmutableな実装は達成される。 細かい項目の受け渡しが面倒であれば、そこだけクラス化や連想配列化によりまとめて処理してしまえばよい。ここまで徹底的にやれば、晴れてクラス実装を手放せる。

代わりに、1つのモジュールの責務が肥大化するが、Excel VBAはそもそもスコープが小さくなる傾向にあるので、よほどの場合を除きコントロール可能な範囲に収まるはず。(そもそも、VBAがコントロール不能なぐらい肥大化している場合、もはやExcel VBA以外の実装を用いたほうがメリットが大きいというような事態に陥ってないだろうか?)

*1:「クラス指向」の言い回しは、オブジェクト指向に造詣の深い方にとって違和感の塊だろう、しかしあえて揶揄している。多態を持たず、静的なプログラム構造によって実際の動作および俯瞰図が完成するのであれば、メモリ上に実態を展開し実行時情報を持つというオブジェクト指向の原典的な利点の1つは別になくても、クラスという書式でカプセル化、レイヤ化、関心や責任の分離は実現できる。そしてその3点はC言語のような純手続き型言語でも設計によって十分可能であり、現実に、1970年代に軍事系のC言語の開発プロジェクトで達成されたという研究もある。クラス名のパターンが設計のすべてなのであれば、関数名+引数にとる構造体のグループでほぼ誤差なく手続き型に落とせる。

【Excel VBA】ファイルの絶対パスを取得する

カレントディレクト

Sub 使いないやつ()
    Debug.Print CreateObject("Scripting.FileSystemObject").GetAbsolutePathName("./")
    Debug.Print CurDir
End Sub
  • どっちも、デフォルトだと%UsersProfile%\Documents(マイドキュメント)を指す。
  • Excelのワークブックのパス基準の絶対パス取得は以下の通り。
Public Function AbsPath(String RelativePath) As String
On Error GoTo HANDLING
    Dim DirBK As String
    DirBK = CurDir
    ChDrive Left(ThisWorkbook.Path, 1)
    ChDir ThisWorkbook.Path
    AbsPath = CreateObject("Scripting.FileSystemObject").GetAbsolutePathName(RelativePath)
    ChDrive Left(DirBK, 1)
    ChDir DirBK
    Exit Function
    
HANDLING:
    Debug.Log ("[ERROR]AbsPath():" & ERR.Description)
    ERR.Raise ERR.Number, ERR.Source, ERR.Description, ERR.HelpFile, ERR.HelpContext
    
End Function()
  • FileSystemObjectやChDirを使わないレシピ
Public Function AbsPath(Path As String) As String
On Error GoTo HANDLING

    If (InStr(Path, ":") <> 2) Then
        Path = ThisWorkbook.Path & "\" & Path
    Else
        Path = Path
    End If
    
    
    Dim idx As Long
    Do While InStr(Path, "..\") > 0
        idx = InStr(Path, "..\")
        
        Dim idxPrevSep As Long
        idxPrevSep = InStrRev(Path, "\", idx - 2)
        If (idxPrevSep = 0) Then
            Exit Do
        End If
        
        Path = Left(Path, idxPrevSep - 1) & Mid(Path, idx + 2)
    Loop
    
    Path = Replace(Path, ".\", "")
    AbsPath = Path
    
    Exit Function
    
HANDLING:
    Debug.Log ("[ERROR]AbsPath():" & ERR.Description)
    ERR.Raise ERR.Number, ERR.Source, ERR.Description, ERR.HelpFile, ERR.HelpContext
    
End Function

【Excel VBA】エラー処理(例外処理)をちゃんと書く

概要

  • VBAでOn Errorを書くと、どこでエラーになっているのかマジでわからなくなる。
  • 付属のデバッガ以外の例外機構/エラーレポーティングが貧弱であるため、全てを手で実装しなければならない。
  • ある程度の規模のアプリケーションで実践できたので、共有。

結論(テンプレート)

Worksheet / ThisWorkbook の書き方テンプレート

' /////////////////////////////////////////////////////////////////
' // 何かのボタンがクリックされたときのイベントハンドラ 
' /////////////////////////////////////////////////////////////////
Private Sub BtnA_click()
On Error Goto HANDLING
    ' メソッドに入ったら必ずロギング
    Call ModuleUtil.Log("Sheet1.BtnA_click() - start")

    Application.ScreenUpdating = False
    
    ' 入力値検証
    If Range("A2").Value2 = "" Then
         ' 注:ここでは安直にRaiseしているけど、やりたい内容によって変えてよい
         Err.Raise Description:="〇〇が空欄です"
    End If

    ' 本処理
    call ExecMain()

    ' メソッド終わったらロギング
    Call ModuleUtil.Log("Sheet1.BtnA_click() - end")
    Application.ScreenUpdating = True
    Exit Sub

' エラーハンドリング
' ------------------------
HANDLING:
    ' エラー内容をユーザーに通知する。独自のヘルパメソッドを介す。
    Call ModuleUtil.MessageBox(Err.Description)
    ' どこのエラーかしっかり記録する。独自のヘルパメソッドを介す。
    Call ModuleUtil.Log("[ERROR] Sheet1.BtnA_click():" & Err.Description)
    ' 例外は握りつぶす

    Application.ScreenUpdating = True
    
End Sub

' /////////////////////////////////////////////////////////////////
' // 本処理
' /////////////////////////////////////////////////////////////////
Private Sub ExecMain()
On Error Goto HANDLING
    ' メソッドに入ったら必ずロギング
    Call ModuleUtil.Log("ExecMain() - start")

    ' 何かのメイン処理をここで行う
    ' 試しにErrorを起こさせてみる
    Call Mid("", -1)

    ' メソッド終わったらロギング
    Call ModuleUtil.Log("ExecMain() - end")
    Exit Sub

HANDLING:
    ' どこのエラーかしっかり記録する。独自のヘルパメソッドを介す。
    Call ModuleUtil.Log("[ERROR] Sheet1.ExecMain():" & Err.Description)
    ' Errorを呼び出し上位に伝搬させる
    Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext
End Sub

ヘルパメソッドを実装するモジュールのテンプレート

' /////////////////////////////////////////////////////////////////
' // メッセージボックスを出力する
' // 引数省略時は、なぜか、Call で呼ばないとスタックが壊れる
' /////////////////////////////////////////////////////////////////
Public Function MessageBox(Text As String, Optional buttons As Long = 0, Optional title As String = "-1") As Long
On Error GOTO HANDLING
    If (DisabledMsgBox()) Then
        Call Log(Text)
        Exit Function
    End If
    
    Dim argTitle As String
    If (title = "-1") Then
        argTitle = ThisWorkbook.Name
    Else
        argTitle = title
    End If
        
    MessageBox = MsgBox(Text, buttons, argTitle)
    
    Exit Function
    
HANDLING:
    Call Log("[ERROR]ModuleUtil.MessageBox():" & " " & Err.Description)
    Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext
End Function


' /////////////////////////////////////////////////////////////////
' // ログを出力する
' /////////////////////////////////////////////////////////////////
Public Sub Log(Text As String)
    Dim StrNow As String
    StrNow = Format(Now(), "yyyy/mm/dd hh:nn:ss")
    Debug.Print "[" & StrNow & "] " & Text
    
    If (EnabledLog()) Then
        Dim ts As Object
        Set ts = CreateObject("Scripting.FileSystemObject").OpenTextFile(GetLogFilePath(), 8, True)
        With ts
            .WriteLine "[" & StrNow & "] " & Text
            .Close
        End With
    End If
    
End Sub

ヘルパメソッド

    Call ModuleUtil.MessageBox(Err.Description)
    Call ModuleUtil.Log("[ERROR] Sheet1.BtnA_click():" & Err.Description)
  • 今回携わったアプリは自動実行モード(CUI)と手動実行モード(GUI)を使い分けるため、それぞれのモードで動作を切り替える必要があった。
  • そのため、過剰にラップしている。この部分は、要件次第で、MsgBoxやDebug.Print直呼びでも構わない。

  • ただ、実運用を考えると、ログファイルへの出力機構は用意したほうが追跡性あがる。

  • 年月日時分秒の出力はマジで助かる。

開始/終了ログ

    Call ModuleUtil.Log("Sheet1.BtnA_click() - start")
    Call ModuleUtil.Log("Sheet1.BtnA_click() - end")
  • 最悪なくてもいいけどあったらうれしい。具体例は記事の最後に。

Errを捕捉したときの処理

最上位呼出階層

HANDLING:
    Call ModuleUtil.MessageBox(Err.Description)
    Call ModuleUtil.Log("[ERROR] Sheet1.BtnA_click():" & Err.Description)

それ以外の中間処理

HANDLING:
    Call Log("[ERROR]ModuleUtil.MessageBox():" & " " & Err.Description)
    Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext
  • とりあえずなんでもログに出しておく。本番運用で泣く。
  • 余裕があれば、引数に何渡されたか全部出しておいてもよいぐらい。
  • VBA標準関数(Mid、MkDirとか)やCreateObjectしたAPI(Scripting.xxxとか)呼ぶようなやつはとりあえず書いていたほうがよい。入力値次第ですぐにErrorを吐くAPIがあまりにも多い。
  • Erl() でエラー発生した行番号取れるよって記事を見たけど手元の環境では「0」しか取得できなかった。

以上全盛りしたらどうなるか

  • テキストログやイミディエイトウィンドウに以下が出る。
[2022/08/21 00:55:01] -------- [AUTORUN START] 〇〇〇.xlsm ---------
[2022/08/21 00:55:01] Sheet1.btnABC_click() - start
[2022/08/21 00:55:01] Sheet1.Validate() - start
[2022/08/21 00:55:01] Sheet1.Validate() - end
[2022/08/21 00:55:01] Sheet1.Generate() - start
[2022/08/21 00:55:14] Save: C:\xxx\sample.txt
[2022/08/21 00:55:15] Sheet1.Generate() - end
[2022/08/21 00:55:15] FIN.
[2022/08/21 00:55:15] Sheet1.btnABC_click() - end
[2022/08/21 00:55:15] Sheet2.btnABC_click() - start
[2022/08/21 00:55:15] Sheet2.Validate() - start
[2022/08/21 00:55:15] [ERROR] Sheet2.Validate():引数の数が一致していません
[2022/08/21 00:55:15] [ERROR] Sheet2.btnABC_click():引数の数が一致していません
[2022/08/21 00:55:16] AUTORUN RESULT: NG
[2022/08/21 00:55:16] -------- [AUTORUN END] 〇〇〇.xlsm ---------
  • 「引数の数が一致していません」とだけメッセージが出る状況に比べてだいぶマシ。

メモ:システムエンジニアの評価指標

HIGH OUTPUT MANAGEMENTを読み、今の自分がどのような尺度でシステムエンジニアを評価できるかを試し書き。 結論、やや古めのWebアプリケーションエンジニア・アーキテクトに関する評価表になった。 何か本を読むなり手を動かすなりで知識を取り入れて更新していきたい。

設計者(システムエンジニア)としての指標

  • 指標(グランドデザイン)
    • 業務領域に関する知見の深さ。
    • 様々なミドルウェア・ソフトウェアの特性の把握。
    • システムを構築する要素の物理(サービス)的・論理(サービス)的観点の知見。(実例を知れば知っているほどよい)
    • 運用時、各アクター(ユーザー、特権ユーザー、ヘルプデスク、エンジニア)がそれぞれできることと権限制御を含めたデザインができる能力。
    • 外部システムとの依存関係の構築に関する知見。
    • 個人情報やシステム監査など、社会的に要求される仕様に関する知見。
  • 指標(詳細デザイン)
    • 機能分割に関する知見。ユーザーの問題領域(ドメイン)と実装に落とした時のフローに関する知識。
    • アーキテクチャ設計パターンに関する知識。(レイヤ化、MVC、CQRSなど。各問題点の理解があればなお良い)
    • 実装パターンに関する知識。(キャッシュによる高速化、遅延評価戦略など)
    • エラー時のユーザーや保守エンジニアの行動に関するデザイン。
    • 機能変更する際、何を犠牲にするか・できないかを想定する能力。(例:パラメータ設計。変更が想定される機能に関しては、複数値を受け取れるように配列での受け取りやインタフェース定義を工夫する。それ以上の要求が来た場合は別の手段を・・・などの切り分け)

Webエンジニア(フロント)の指標

  • HTML・CSSデザインに関する知見・習熟度。テンプレ的な配色比率やレイアウト(ex:グリッドレイアウト)に関する知見。(鮮度あり。長めに見て直近5年)
  • UI・UX視点での知見。(ex:PC向けサイトをスマホで見るとどうなるか?)
  • 素材をベースとした画像加工技術やロゴなどの画像制作技術。
  • CSS命名規則など、中長期に渡るCSSの保守に関する知見。(鮮度あり。長めに見て直近5年)
  • JavaScriptの習熟度・DOMなどの機構の理解度。
  • SPA(Single Page Application)や各フレームワークに関する習熟度。
  • WebAPIの呼び出しなどに関する知見。
  • 実装が及ぼす性能・処理速度への影響に関する知見。
  • デバッグやプロファイルによるアプリケーションの挙動観測技能。
  • ほか特定ケースの技能。

Webエンジニア(バック)の指標

  • HTTPプロトコルとWebサーバーの仕組みに関するコンピュータ・サイエンス観点の知識。(リクエスト、レスポンスがそれぞれ何なのか。ブラウザはどう受けるのか)
  • データストア(データベースなど)の更新特性に関する知見。
  • 認可・認証に関する知見。
  • トランザクション設計・エラーハンドリングにおける知見。
  • 一般的な脆弱性対策に対する知見。(○○インジェクション、認証認可なしでパラメータ次第で不正操作できてしまうかの検出)
  • 実装が及ぼす性能・処理速度への影響に関する知見。
  • アプリケーション・サーバーが実稼働したときの物理構成に関する知見。(リバースプロキシやロードバランサ、CDNなど)
  • デバッグやプロファイルによるアプリケーションの挙動観測技能。
  • ほか特定ケースの技能。

テスト計画者・テスターとしての指標

  • 一般的なテスト工程に関する知見。(単体テスト結合テスト、総合(システム)テストはどんな性質の品質を検証するか?)
  • テスト工程において期待されていることや作用の理解。(品質確認、ビジネスとしての品質保証、メンバー間の認識統合など)
  • 仕様に適合しているかを検証するテストだけでなく、QA(品質保証)観点でのテストに関する知見があるか。
  • テスト自動化における前提・弊害の理解。CI(Continuus Integration)実運用の知見。
  • 境界値テスト・最大値・ゼロデータなどの一般的なテスト観点の知識。
  • 正常系(想定している仕様に適合したケース)、準正常系(想定していない状態だが正常に稼働すると保証するケース)、異常系(処理継続困難となるケース)の理解。
  • 状態を持つ実装(JavaScriptを駆使した動的な機能、スタンドアロンexeによる実装など)における検証の知識。(機能を連続して動かした時や、戻ったときに不正な表示や挙動をとるかの検証)

開発作業管理者・リーダーとしての指標

  • WBS粒度での作業進捗管理の知識。
  • 課題・ボトルネックの検出能力と問題解決手法(エスカレーション含む)の選択能力。
  • 課題に対するリカバリプランの立案・提示能力。
  • チームメンバーに対するメンターとしての技能。
  • プロジェクト内他チームとのコミュニケーションのとり方。(適切・不適切な外部委任?協力的・過剰干渉的?コントロールはできそう?)
  • プロジェクト外とのコミュニケーションのとり方。(各自の関心領域やスコープを把握し手を打っているか?)

SpringMVC + JSP の環境構築とサンプル実装

前置き

  • この記事は、ローカルPCに開発環境構築をすること、サンプルプログラムを実装することを焦点にあてた記事です。
  • 実際にアプリケーション開発しサーバーで稼働させるには、セキュリティ観点や開発をスケールさせるための設計のために いくつか留意すべきことがあります。そちらについては、別途文献をご参照ください。

構築する開発環境

ゴール:下記構成のSpringBootを用いたWebアプリケーションの開発環境構築.

なお、できるのであれば lombok も導入するとコーディングが捗ります。

実行構成について

  • GradleによりWARファイルをビルドしてTomcat配下に配置。Tomcatが検知してオートデプロイ
  • STSのリモートデバッグ機能を用いて、Tomcatプロセスに繋いでデバッグ

    • SpringBootは単独実行でWebサーバーとして稼働するが、JSPは諸事情あって動かない。かなり面倒くさくビルドも遅くなるが、Tomcatに直接配備する必要がある。
    • JSPは多方面に脆弱であるので、できれば別のテンプレートエンジンを使ったほうが良い。

Oracle DB インスタンスについて

Oracleのローカル開発環境構築は割愛。 なお、この環境構築時にはDockerForWindows、およびOracle公式のDockerImageを用いて、ローカルPC上に構築した。

https://itedge.stars.ne.jp/docker_image_oracle_database_19c/

Oracleの単体稼働に2GBのメモリが必要なため、8GBのPCで開発するには若干パワー不足になる。

必要なソフトウェアのダウンロード

JDK 11

Javaの実行ランタイムと開発用のライブラリ・ツールがまとまったセット。 自分の環境用のインストーラを入手。今回は、Windows向けのものを想定する。

https://www.oracle.com/java/technologies/javase-jdk11-downloads.html

STS

統合開発環境。アプリケーション開発に必要。 予めプラグインを組み込んでセットアップ済みのEclipseが配布されている。 自分の環境用のzipファイルを入手。今回は、Windows向けのものを想定する。

https://spring.io/tools

Tomcat 9

JavaServlet用のWebサーバー。 自分の環境用のzipファイルを入手。今回は、Windows向けのものを想定する。

https://tomcat.apache.org/download-90.cgi

JDKのインストールとセットアップ

  1. 入手したJDKインストーラを実行してJava11をインストールする。(特別設定の変更は不要)

  2. インストール後、コマンドプロンプトかターミナルを起動し、PATHに通っているjavaのバージョンを確認する。

> java -version
java version "11.0.6" 2020-01-14 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.6+8-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.6+8-LTS, mixed mode)

Tomcatのインストールとセットアップ

  1. 開発環境の環境変数を変更し「JAVA_HOME」にJDKのインストールパスを設定する。 ここでは、 C:\Program Files\Java\jdk-11.0.6 を設定したとする。 設定は、Windowsであれば 設定 > 環境変数を編集 を検索し、ユーザー環境変数に設定する。

  2. 入手したTomcatのバイナリを解凍する。ここでは、解凍し C:\opt\apache-tomcat-9.0.34 に配置したとする。

  3. 解凍したディレクトリのbin配下にコマンドプロンプトやターミナルを起動し、 下記コマンドを実行してTomcatデバッグ用ポートありで起動する。

./catalina jpda start
  1. ブラウザを起動し、http://localhost:8080 にアクセスしてTomcatの管理画面が表示されるか確認する。

STSのインストールとセットアップ

STSの解凍と起動

  1. STSを解凍し、配置しておきたいフォルダに配置する。

  2. SpringToolSuite4.exe を実行する。

Note:メモリ不足の場合、 SpringToolSuite4.ini からJVM引数を編集する。

  1. workspace(ファイル配置場所)を聞かれたら、とりあえず新しいフォルダ先を指定する。 Eclipseのデフォルトに従うなら、 (SpringToolSuite4.exeがあるフォルダ)/workspace

Gradleプラグインのインストール

  1. STS上部メニュー HelpEclipse Marketplace... をクリック
  2. Eclipse MarcketPlaceウィンドウの Search タブで、 Find:Gradle を入力してEnter
  3. Gradle ID Pack x.x.x ~Install
  4. ライセンスの確認ダイアログが表示されたら、「~Agree All~」みたいなラジオボタンを選択して「Finish」
  5. STSを再起動する

文字コードの設定

デフォルトのエンコーディングを、Java標準の UTF-8 に設定する。

  1. STS上部メニュー Window > Preferences をクリック
  2. General > Workspace をクリック
  3. Text file encoding から、 OtherUTF-8 を選択
  4. Apply and Close をクリック

オプション:STSの設定

下記、設定しておくと便利なもの。 もし、開発プロジェクトで標準の環境を作りたい場合、これらを事前設定する手順を追加しておくとよいです。

STS日本語化

保管アクション

  • https://lifeinprogram.com/2018/11/25/post-355/
  • Javaでは、「Organize imports」(自動インポート)や「Format all lines」 + GoogleStyleフォーマッターによる 自動フォーマットがあればだいたい標準化できる。
  • checkStyle は静的解析ツールで、小うるさいことが多い。 新規プロジェクトでレビュー負荷を何がなんでも下げるんだ、という意志を貫きとおす自信がなければ、温かみのある手法に準じるのも一興。

サンプルプロジェクトの作成

サンプルプロジェクト作成

  1. STS左側 Package Explorer を左クリック > New > Spring Starter Project をクリック

  2. 下記内容を入力して「Next」

Service URL:任意
Name: プロジェクト名。ここでは「sample」とする

Type:「Gradle(Buildship3.x)」
Packaging:「War」
Java Version:「11」
Language:「Java」

Group: mavenがらみの設定。例えば google なら com.google となっている。「org.pack」とする。
Artifact: mavenがらみの設定。通常プロジェクト名をいれる。ここは「sample」とする
Version: ビルドスクリプトで参照できる。とりあえず0.0.1-SNAPSHOT でよい。
Description: 説明文。
Package: Javaパッケージの接頭辞。ここでは「org.pack.sample」とする。
  1. 定番のフレームワーク依存関係の入力し、Finish

ここでは、下記をチェック。この内容は、 build.gradle に反映される。

  • SQL > MyBatis Framework
  • SQL > Oracle Driver
  • Web > Spring Web

サンプルアプリケーションの実装(共通部分、Mybatis用設定)

下記に従って、ソースコードを変更、または新規作成する。

編集: src/main/java/org/pack/sample/SampleApplication.java

アプリケーションのエントリーポイント(実行起点)となるソースコード

  • @ComponentScan を追加、サブパッケージをスキャンしてDIできるようにする
  • @EnableAutoConfiguration を追加、JavaConfig機能を有効にする。
package org.pack.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@SpringBootApplication
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }

}

新規: src/main/java/org/pack/sample/config/MyBatisConfiguration.java

DI時にマニュアルでインスタンス作成することでいろいろカスタマイズするため設定コード。 このコードはMyBatisに特化。

  • MyBatisの定義クラス Mapper のパッケージを指定
  • ついでに色々要求されたので、いい感じのを記述
package org.pack.sample.config;

import java.sql.Driver;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

@Configuration
public class MyBatisConfiguration {
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
        configurer.setBasePackage("org.pack.sample.mapper");
        configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
        return configurer;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
          SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
          factoryBean.setDataSource(dataSource);
          return factoryBean.getObject();
    }

    @SuppressWarnings("unchecked")
    @Bean
    public DataSource dataSource(DataSourceProperties properties) throws ClassNotFoundException {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass((Class<? extends Driver>) Class.forName(properties.determineDriverClassName()));
        dataSource.setUrl(properties.determineUrl());
        dataSource.setUsername(properties.determineUsername());
        dataSource.setPassword(properties.determinePassword());
        Properties connectionProperties = new Properties();
        connectionProperties.setProperty("autoCommit", "false");
        dataSource.setConnectionProperties(connectionProperties);
        return dataSource;
    }
}

編集:src/main/resources/application.properties

全体の設定ファイル。

spring.profiles.active=local

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

mybatis.mapper-locations=classpath*:/com/example/demo/mapper/**/*.xml

編集:src/main/resources/application.properties

ローカル環境用の設定ファイル。Tomcatのcontext.xmlの記述で切り替え可能。

参考: https://exceptionblend.wordpress.com/2013/02/11/springframework-profile/

spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
spring.datasource.username=system
spring.datasource.password=password
spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver

サンプルアプリケーションの実装(個別実装)

新規:src/main/resources/org/pack/sample/mapper/HealthCheckMapper.xml

MyBatisのMapper向けのSQL定義ファイル。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.pack.sample.mapper.HealthCheckMapper">
    <select id="select" resultType="Long">
        SELECT 1 FROM dual
    </select>
</mapper>

新規:src/main/resources/org/pack/sample/mapper/HealthCheckMapper.xml

MyBatisのMapper向けのJavaファイル。

package org.pack.sample.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface HealthCheckMapper {
    long select();
}

新規:src/main/java/org/pack/sample/controller/LoginController.java

Webアプリケーションのリクエストを直接処理する。

package org.pack.sample.controller;

import org.jboss.logging.Logger;
import org.pack.sample.mapper.HealthCheckMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/login")
public class LoginController {
    Logger logger = Logger.getLogger(LoginController.class);
    
    @Autowired
    private HealthCheckMapper healthCheck;

    @RequestMapping(value = "",  method = RequestMethod.GET)
    public String index() {
        long n = healthCheck.select();
        
        logger.info("helthckeck:" + n);
        
        return "login";
    }

    @RequestMapping(value = "",  method = RequestMethod.POST)
    public String login(LoginForm form, Model model) {
        LoginModel loginModel = new LoginModel();
        
        final String loginid = "user";
        final String password = "pass";
        
        if (loginid.equals(form.loginid) && password.equals(form.password)) {
            loginModel.message = "ログインできました.";
        } else {
            loginModel.message = "ユーザーまたはパスワードが違います.";
        }
        
        loginModel.loginid = form.loginid;
        loginModel.password = form.password;
        
        model.addAttribute("loginModel", loginModel);
        
        return "login";
    }

    /**
    * ログインフォームのリクエストパラメータ。本当は別ファイルに書くべき
    */
    public class LoginForm {
        private String loginid;
        private String password;
        
        public String getLoginid() {
            return loginid;
        }
        public void setLoginid(String loginid) {
            this.loginid = loginid;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
    }
    /**
    * ログインフォームのレンダリングに利用するパラメータ。本当は別ファイルに書くべき
    */
    public class LoginModel {
        private String message;
        private String loginid;
        private String password;
        
        public String getMessage() {
            return message;
        }
        public void setMessage(String message) {
            this.message = message;
        }
        public String getLoginid() {
            return loginid;
        }
        public void setLoginid(String loginid) {
            this.loginid = loginid;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
    }
}

新規:src/main/webapp/WEB-INF/views/login.jsp

HTMLテンプレート。

webappディレクトリが、WARファイルを配置した時のルートになる。この下に、jsやcssなどを配置する。

また、WEB-INF配下はTomcatの仕様でブラウザから直接アクセスできなくなる。

<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>

<form action="login" method="POST">
    <div>${loginModel.message}</div>
    <table>
        <tr>
            <td>ユーザ</td>
            <td><input type="text" name="loginid" value="${loginModel.loginid}"></td>
        </tr>
        <tr>
            <td>パスワード</td>
            <td><input type="password" name="password" value=""></td>
        </tr>
    </table>
    <button type="submit">ログイン</button>
</form>

</body>
</html>

Gradleの依存関係更新

build.gradle または プロジェクト自体 を右クリック > Gradle > Reflesh Gradle Project で build.gradle の解析と Maven central リポジトリからの依存ライブラリの取得を実施する。

ビルドスクリプトの修正とTomcatへの配備

  1. build.gradleを編集し、下記とおりにする
plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
    id 'war'
}

group = 'org.pack'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
// ▽追加
def warName = 'sample.war'
def localTomcatWebapps = 'C:\\opt\\apache-tomcat-9.0.34\\webapps'
// △追加

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.2'
    runtimeOnly 'com.oracle.ojdbc:ojdbc8'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

// ▽追加
war {
    enabled = true
    archiveName "${warName}"
}

task deployLocal(type: Copy) {
    from "build/libs/${warName}"
    into "${localTomcatWebapps}"
}
// △追加

test {
    useJUnitPlatform()
}
  1. コマンドプロンプトまたはターミナルを起動してサンプルプロジェクト直下に移動、下記コマンドを実行する
gradlew war deployLocal

動作確認

  1. STSでLoginController.java を開き、止めたい場所の行番号をダブルクリックしてブレイクポイントを設定する。
  2. STSでプロジェクトを右クリック>Debug>Debug Configuration.. を選択。
  3. Remote Java Application の新しい構成を追加し、 Project sampleHost localhostPort 8000 で起動。
  4. http://localhost:8080/sample/login にアクセスし動作確認する。

参考委文献