Swift:VaporによるSQLiteの操作(Ubuntu)

今回は、Vapor を使って SQLite を操作する方法(CRUD)を紹介します。モジュールは FluentSQLite を使います。



SQLite3 のインストールとデータベースの作成

まず、SQLite3 をインストールします。Swift や Vapor Toolbox のインストールは、Swift:AjaxとVaporの連携(Ubuntu)を参考にしてください。

OS:Ubuntu 18.04


$ sudo apt update
$ sudo apt install sqlite3

 

今回使用する FluentSQLite では、フィールド名(カラム名) id の整数型フィールドが必須になるので、予め次のようなテーブルを作成しておきます。

 

データベース名:sample.sqlite、テーブル名:users

$ sqlite3 sample.sqlite
sqlite> create table users (id integer, name text);
sqlite> .exit



Vapor アプリケーションの作成

今回も、Vapor Toolbox を使わずに、Swift に標準搭載されている Swift Package Manager だけで作成します。

 

〈実行形式のパッケージの作成〉

$ mkdir fluentsqlite
$ cd fluentsqlite
$ swift package init --type executable

 

続いて、Vapor Docs: Fluent Models を参考に、main.swift と Package.swift を次のように作成します。

データベースは let sqlite = try SQLiteDatabase(・・・) の所で、テーブルは User クラスの entity で設定します。

 

main.swift

import FluentSQLite
import Vapor

var services = Services.default()

services.register(NIOServerConfig.default(hostname:"127.0.0.1", port:8080))

try services.register(FluentSQLiteProvider())

var databases = DatabasesConfig()
let sqlite = try SQLiteDatabase(storage:.file(path:"/home/tomato/sqlite/sample.sqlite"))
databases.add(database:sqlite, as:DatabaseIdentifier<SQLiteDatabase>.sqlite)
services.register(databases)

let application = try Application(config:Config.default(), environment:Environment.detect(), services:services)

final class User {
  static let entity = "users"
  var id:Int?
  var name:String
  init(id:Int? = nil, name:String) {
    self.id = id
    self.name = name
  }
}

extension User:Content {}
extension User:SQLiteModel {}

User.defaultDatabase = DatabaseIdentifier<SQLiteDatabase>.sqlite

let router = try application.make(Router.self)

router.get("create") { request -> Future<[User]> in
  let user1 = User(id:1, name:"mimi")
  let _ = user1.create(on:request)
  let user2 = User(id:2, name:"nana")
  let _ = user2.create(on:request)
  let user3 = User(id:3, name:"toto")
  let _ = user3.create(on:request)
  return User.query(on:request).all()
}

router.get("readAll") { request -> Future<[User]> in
  return User.query(on:request).all()
}

router.get("read") { request -> Future<[User]> in
  return User.query(on:request).filter(\User.name == "mimi").all()
}

router.get("update") { request -> Future<[User]> in
  let user = User(id:1, name:"momo")
  let _ = user.update(on:request)
  return User.query(on:request).all()
}

router.get("delete") { request -> Future<[User]> in
  let user = User(id:3, name:"toto")
  let _ = user.delete(on:request)
  return User.query(on:request).all()
}

try application.run()
 

注)Future<[User]> は、Future<Array<User>> のことです。
注)"let _ = ・・・" は、戻り値があるけれど、戻り値を使わない場合の表記法です。もちろん、"let _ =" を外しても構いません。

 

Package.swift

import PackageDescription

let package = Package(
  name:"fluentsqlite",
  dependencies:[
    .package(url:"https://github.com/vapor/fluent-sqlite.git", from:"3.0.0"),
    .package(url:"https://github.com/vapor/vapor.git", from:"3.1.0"),
  ],
  targets:[
    .target(name:"fluentsqlite", dependencies:["FluentSQLite", "Vapor"]),
  ]
)
 

Vapor Framework 及び Fluent SQLite の最新バージョンは、Vapor releases 及び Fluent SQLite releases で確認してください。

 

ファイルの配置は以下の通りです。


  /home/tomato/fluentsqlite/Sources/fluentsqlite/main.swift
  /home/tomato/fluentsqlite/Package.swift
  /home/tomato/sqlite/sample.sqlite



Vapor アプリケーションのビルドと起動

Package.swift のあるディレクトリで以下のコマンドを実行してください。

 

デバッグビルドと起動〉

$ swift package tools-version --set-current
$ swift build
$ .build/debug/fluentsqlite

 

ビルドに成功し、アプリケーションを起動したら、ブラウザのアドレスバーに以下のように入力してください。


http://127.0.0.1:8080/create


http://127.0.0.1:8080/readAll


http://127.0.0.1:8080/read


http://127.0.0.1:8080/update


http://127.0.0.1:8080/delete

 

以上が CRUD の基本的な使い方です。
次にテーブルのセルの選択の仕方です。まず、filter でレコード(行)を選択し、続いて map や flatMap でフィールド(列)を選択します。次の例は、flatMap を使って、選択したフィールドを更新して保存する例です。

 

 filter(・・・).first() を使った場合

router.get("filter_first") { request -> Future<User> in
  let futureUser = User.query(on:request).filter(\User.id == 1).first().flatMap(to:User.self) { user -> Future<User> in
    user!.name = "momo"
    return user!.save(on:request)
  }
  return futureUser
}
 

 

 filter(・・・).all() を使った場合

router.get("filter_all") { request -> Future<User> in
  let futureUser = User.query(on:request).filter(\User.id == 1).all().flatMap(to:User.self) { users -> Future<User> in
    users[0].name = "momo"
    return users[0].save(on:request)
  }
  return futureUser
}
 

データの更新はよくやることなので、もう少し簡潔に記述できると嬉しいんですが、Future パターンにしようとするとこういう記述になるんでしょうか。

map と flatMap の違いは、map の場合はクロージャの戻り値の型が何でもいいのに対し、flatMap の場合は戻り値の型が Future<T> だという点です。 flatMap は、Future から Future への map で、Future 型を変更しないことから、このように命名されています。


instance_of_Future<A>.flatMap(to:B.self) { a -> Future<B> in
  return instance_of_Future<B>
}
 

 

以上のプログラムでは戻り値の型を明記していますが、Swift には型推論があるので適宜省略してください。



感想

Ubuntu 18.04 でもあっさり動作してくれました。 実戦で使えそうな感触を得たので、私は実戦で使います!^^

 


参考サイト

 

Swift:AjaxとVaporの連携(Ubuntu)

最近、Swift の Web Framework の一つである Vapor を始めました。今回、Ajax と Vapor を連携させるプログラムを作成したので紹介します。
どんなプログラムかというと、HTML のinput要素に二つの数値を入力し、それらを Ajax の post でサーバに送信し、Vapor で受け取って、それらの足し算を行い、その結果を返すという単純なプログラムです。 単純ですが、これができると色んなことに応用できるんです。



Swift と Vapor Toolbox のインストール

OS:Ubuntu 18.04


$ sudo wget -q https://repo.vapor.codes/apt/keyring.gpg -O- | sudo apt-key add -
$ echo "deb https://repo.vapor.codes/apt $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/vapor.list
$ sudo apt update
$ sudo apt install swift vapor

$ swift --version
$ vapor --version

Swift 4.2 と Vapor Toolbox 3.1.10 がインストールされます。



Vapor アプリケーションの作成

今回は、Vapor Toolbox を使わずに、Swift に標準搭載されている Swift Package Manager だけで作成します。

 

〈実行形式のパッケージの作成〉

$ mkdir webapp
$ cd webapp
$ mkdir Public
$ mkdir Resources
$ cd Resources
$ mkdir Views
$ cd ..
$ swift package init --type executable

 


webapp(パッケージ名)
 ├── Package.swift
 ├── Public
 ├── README.md
 ├── Resources
 │   └── Views
 │       └── ajax.html
 ├── Sources
 │   └── webapp(ターゲット名)
 │       └── main.swift
 └── Tests
     ├── LinuxMain.swift
     └── webappTests
         ├── webappTests.swift
         └── XCTestManifests.swift

緑色のディレクトリやファイル以外は自動で生成されます。今回、Public ディレクトリは使いません。

生成された main.swift と Package.swift を下記のプログラムに書き換えます。

 

main.swift

import Vapor

var services = Services.default()
services.register(NIOServerConfig.default(hostname:"127.0.0.1", port:8080))
let application = try Application(config:Config.default(), environment:Environment.detect(), services:services)

struct RequestContent:Content {
  var operation:String!
  var input1:Double!
  var input2:Double!
}

struct ResponseContent:Content {
  var output:Double!
}

let router = try application.make(Router.self)

router.get(String.parameter) { request -> Future<View> in
  let requestParameter = try request.parameters.next(String.self)
  return try request.view().render(requestParameter)
}

router.post(RequestContent.self, at:"calculate") { request, requestContent -> ResponseContent in
  var output:Double!
  if requestContent.operation == "add" {
    output = requestContent.input1 + requestContent.input2
  }
  return ResponseContent(output:output)
}

/*
router.post("calculate") { request -> Future<ResponseContent> in
  let futureResponseContent = try request.content.decode(RequestContent.self).map(to:ResponseContent.self) { requestContent -> ResponseContent in
    var output:Double!
    if requestContent.operation == "add" {
      output = requestContent.input1 + requestContent.input2
    }
    return ResponseContent(output:output)
  }
  return futureResponseContent
}
*/

try application.run()
 

上記の router.post に関してですが、コメント文の方は Future パターンで、非同期で処理を実行するので全体として処理が速くなると思います。 今回の場合は大した違いはないですが、重い処理の場合は Future パターンを使ったほうがいいと思います。

 

Package.swift(UTF-8で保存してビルドするとエラーになりました???)

import PackageDescription

let package = Package(
  name:"webapp",
  dependencies:[
    .package(url:"https://github.com/vapor/vapor.git", from:"3.1.0"),
  ],
  targets:[
    .target(name:"webapp", dependencies:["Vapor"]),
  ]
)
 

Vapor Framework の最新バージョンは、Vapor releases で確認してください。

 

次に、ajax.html を Resources/Views の直下に置いてください。

   request.view().render("ajax.html")

とすると、Vapor は Resources/Views から ajax.html を読み込もうとするからです。
もちろん、

   request.view().render("/home/tomato/webapp/Resources/Views/ajax.html")

のように絶対パスで指定することもできます。この作業は、ビルドの後でも構いません。

 

ajax.html(UTF-8で保存してください)

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Ajax</title>
</head>
<body>

<form>
  <input id="input1" type="text"> + <input id="input2" type="text">
  <input type="button" value="=" onclick="calculate('add', 'input1', 'input2', 'output');">
  <output id="output"></output>
</form>

<script src="http://code.jquery.com/jquery-3.3.1.min.js"></script>

<script>
function calculate(operation, input1, input2, output) {
  var url = "calculate";
  var request = {};
  request["operation"] = operation;
  request["input1"] = document.getElementById(input1).value;
  request["input2"] = document.getElementById(input2).value;
  var callback = function (response) { document.getElementById(output).innerHTML = response["output"]; };
  $.post(url, request, callback);
}
</script>

</body>
</html>
 

注)$.post は、Ajax の関数です。

 

ファイルの配置は以下の通りです。


  /home/tomato/webapp/Sources/webapp/main.swift
  /home/tomato/webapp/Package.swift
  /home/tomato/webapp/Resources/Views/ajax.html



Vapor アプリケーションのビルドと起動

Package.swift のあるディレクトリで以下のコマンドを実行してください。 ビルドコマンドを実行すると、Package.swift に記述されている url から必要なファイルをダウンロードし、ビルドを開始します。

 

デバッグビルドと起動〉

$ swift package tools-version --set-current
$ swift build
$ .build/debug/webapp



〈リリースビルドと起動〉

$ swift package tools-version --set-current
$ swift build -c release
$ .build/release/webapp

 

注意1

上記のコマンド swift package tools-version --set-current は、swift build を実行したとき、


   error: manifest parse error(s):
   error: argument 'targets' must precede argument 'dependencies'
    targets:[

が出た場合の対処方法です。これは、swift package tools のバージョンが現行のものと一致していないことによるエラーなので、swift build を実行して、エラーが出なければ必要ありません。

 

注意2

main.swift があるディレクトリ(/home/tomato/webapp/Sources/webapp)に Package.swift を置くと次のようなエラーが出ます。


   error: no such module 'PackageDescription'
   import PackageDescription

そのときは Package.swift をそのディレクトリから削除してください。

 

サーバ側で Vapor アプリケーションを起動したら、次はクライアント側です。ブラウザのアドレスバーに次のように入力してください。


http://127.0.0.1:8080/ajax.html

 

すると、次のような画面が表示されます。

+

 

数値を入力し、=ボタンをクリックして結果が表示されたら成功です。

以上は Vapor アプリケーションがローカルにある場合の設定です。リモートサーバにある場合は、main.swift の中の "127.0.0.1" をリモートサーバのIPアドレスに変更してください。



補足

ブラウザのアドレスバーに次のように入力した時、


http://127.0.0.1:8080/aaa/bbb/ccc

 

このリクエストメッセージのリクエストラインは、次のようになります。


GET /aaa/bbb/ccc HTTP/1.1

注)/aaa/bbb/ccc の部分は、RFC 7230 によると、リクエストターゲット(request-target)と呼ぶそうです。以前は、リクエスURIと呼ばれていたものです。

 

このリクエストターゲット /aaa/bbb/ccc の ccc は、a.html であったり、b.html であったりするので、変数(パラメータ)として受け取れるようにしておくと便利です。 Vapor では次のようにします。


router.get("aaa/bbb", String.parameter) { request -> Future<View> in
  let ccc = try request.parameters.next(String.self)
  ・・・・・・・・・・
}
 

 

或いは、次のようにしても構いません。


router.get("aaa", "bbb", String.parameter) { request -> Future<View> in
  let ccc = try request.parameters.next(String.self)
  ・・・・・・・・・・
}
 



また、次のようなリクエストラインを持つリクエストメッセージを送信するには、


POST /xxx HTTP/1.1

 

ajax.html の url に、このリクエストターゲットをセットします。これが、$.post の引数(url)になります。


var url = "xxx";
 

 

このリクエストを Vapor で受け取るには、main.swift の router.post の引数に、このリクエストターゲットを設定してください。


router.post("xxx") { request in
  ··········
}
 

 

或いは、次のようにしても構いません。


router.post(Abcde.self, at:"xxx") { request, abcde in
  ··········
}
 



感想

今時のWebフレームワークを使うと、簡単にWebサーバの機能やアプリケーションサーバの機能を作り込めることがわかりました。驚きです!
これからサーバサイドで Swift の時代がやってきそうですね。

それから、当初は Kitura を試そうと思っていたのですが、うまくいきませんでした。まだ、Ubuntu 18.04 には対応していないようです。 興味のある方は、Server-Side Swift: Kitura vs Vapor をお読みください。 Kitura と Vapor の比較をしています。

 


参考サイト

 

MathJaxをオフラインで利用する方法

MathJax をオフライン(Windows 環境)で利用する方法を紹介します。
まず、下記のサイトから Windows 版 Git をダウンロードし、インストールしてください。

  Git downloads

Git をインストールしたら、コマンドプロンプト(cmd.exe)を起動し、次のように入力します。


C:\>git clone git://github.com/mathjax/MathJax.git MathJax
 

すると、上記の例では、Cドライブの直下に MathJax フォルダが作成され、その中に MathJax.js がインストールされます。
最後に、html ファイルの script タグの中で、MathJax.js のパスを指定すればOKです。

sample.html

<!doctype html>
<html>
<head>
<script type="text/javascript" src="file:///C:/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
</head>
<body>
\[ax^{2}+bx+c=0\]
\[x=\frac{-b\pm\sqrt{b^{2}-4ac\;}}{2a}\]
</body>
</html>
 

参考サイト

 

JavaScript:ポップアップウィンドウの作成

アニメーションを使ってポップアップウィンドウ(ポップアップボックス)を自作する方法を紹介します。

 

popup1.html

<html>
<head>
<style>
.overlay {
  display:none;
  position:fixed;
  top:0;
  left:0;
  width:100%;
  height:100%;
  background-color:rgba(0, 0, 0, 0.2);
  padding-top:200px;
}
.box {
  margin:auto;
  padding:20px;
  width:500px;
  background:-webkit-linear-gradient(top, #0390f0 0%, #61b3ff 100%);
}
.popup {
  -webkit-animation:expansion 0.5s;
}
@-webkit-keyframes expansion {
  from {
    -webkit-transform:scale(0);
  }
  to {
    -webkit-transform:scale(1);
  }
}
</style>
</head>
<body>
<input type="button" value="popup" onclick="document.querySelector('.overlay').style.display='block';">
<div class="overlay">
  <div class="box popup" style="position:relative;">
    <div style="font-family:'MS Gothic'; font-size:27px; position:absolute; top:0; right:0;">
      <strong onclick="document.querySelector('.overlay').style.display='none';">&times;</strong>
    </div>
    <header style="font-family:'MS Gothic'; font-size:25px;">
      タイトル
    </header>
    <div>
      <p>
        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
      </p>
    </div>
    <footer>
    </footer>
  </div>
</div>
</body>
</html>
 

 

popup2.html

<html>
<head>
<style>
#box {
  width:0;
  height:0;
  font-size:0;
  background-color:#555eee;
  -webkit-transition-property:width, height, font-size, background-color;
  -webkit-transition-timing-function:linear, linear, linear, linear;
  -webkit-transition-duration:0.5s, 0.5s, 0.5s, 0.5s;
}
#box.expanded {
  width:350px;
  height:200px;
  font-size:20px;
  background-color:#555eee;
  padding:20px;
}
.centering {
  position:absolute;
  top:50%;
  left:50%;
  transform:translate(-50%, -50%);
}
</style>
</head>
<body>
<input type="button" value="open" onclick="$add();">
<input type="button" value="close" onclick="$remove();">
<div id="box" class="centering">
  <p>
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
  </p>
</div>
<script>
function $add() {
  document.querySelector("#box").classList.add("expanded");
}
function $remove() {
  document.querySelector("#box").classList.remove("expanded");
}
</script>
</body>
</html>
 

 


参考サイト

 

JavaScript:カレンダーの作成

JavaScript で作成した簡易カレンダーです。

“<”、“>”をクリックすると、月が変わります。

 



calendar.html

<html>
<head>
<style>
#calendar {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  -webkit- transform: translate(-50%, -50%);
}
/*
table {
  width: 100%;
  height: 100%;
}
*/
</style>
</head>
<body>
<div id="calendar"></div>
<script src="./calendar.js"></script>
<script>createCalendar("#calendar");</script>
</body>
</html>
 

 

calendar.js

var $date = new Date();
var $year = $date.getFullYear();
var $month = $date.getMonth();

function createCalendar(id) {
  $id = id;
  var first = new Date($year, $month, 1);
  var last = new Date($year, $month + 1, 0);
  var first_day = first.getDay();
  var last_date = last.getDate();
  var counter = 1;
  var calendar = "<table border='1' bordercolor='#111eee' bgcolor='#555eee' style='font-family:\"MS Gothic\"; color:#000000; border-collapse:collapse;' cellpadding='2'>";
  calendar += "<caption style='font-size:20px'>";
  calendar += "<strong style='color:blue;' onclick='decrease()'>&lt; </strong>"
  calendar += $year + "/" + ("00" + ($month + 1)).slice(-2)
  calendar += "<strong style='color:blue;' onclick='increase()'> &gt;</strong>"
  calendar += "</caption>";
  var week = ["日", "月", "火", "水", "木", "金", "土"];
  for (var j = 0; j < 7; j++) {
    calendar += "<th height='35' width='50'>" + week[j] + "</th>";
  }
  for (var i = 0; i < 6; i++) {
    calendar += "<tr height='35' align='center'>";
    for (var j = 0; j < 7; j++) {
      var bgcolor = ($year === new Date().getFullYear() && $month === new Date().getMonth() && counter === new Date().getDate()) ? "#eee555" : "";
      var date = (i == 0 && j < first_day || counter > last_date) ? "&nbsp;" : counter++;
      calendar += "<td bgcolor=" + bgcolor + ">" + date + "</td>";
    }
    calendar += "</tr>";
    //if (counter > last_date) break;
  }
  calendar += "</table>";
  document.querySelector(id).innerHTML = calendar;
}

function decrease() {
  if ($month-- == 0) {
    $year--;
    $month = 11;
  }
  createCalendar($id);
}

function increase() {
  if ($month++ == 11) {
    $year++;
    $month = 0;
  }
  createCalendar($id);
}
 

 


参考サイト

 

JavaScript:オブジェクトのメソッドを onclick で呼び出す方法

オブジェクトのメソッドを onclick で呼び出す方法を紹介します。

“++”をクリックすると、数値が増加します。

 

プログラムは以下のようになります。

method_call_by_onclick.htm

<html>
<body>

<div id="index"></div>

<script>
var index1 = new Index(0);
var index2 = new Index(0);
var index3 = new Index(0);

function Index(index) {
  this.index = index;
  this.span2;
  this.increase = function () {
    this.span2.innerHTML = ++this.index;
  };

  var that = this;
  var create = function () {
    var div = document.createElement("div");
    document.querySelector("#index").appendChild(div);

    var span1 = document.createElement("span");
    span1.onclick = function () {that.increase();};
    span1.innerHTML = "++";
    div.appendChild(span1);

    that.span2 = document.createElement("span");
    that.span2.innerHTML = that.index;
    div.appendChild(that.span2);
  };

  create();
}
</script>

</body>
</html>
 

 

ここからは、テスト用コードです。無視してください^^

 

function_call_by_onclick.htm(idの指定なし)

<html>
<body>

<script>
var $index = 0;
var $div = document.createElement("div");
document.body.appendChild($div);

function create() {
  var index = "";
  index += "<span onclick='increase()'>++</span>";
  index += "<span>" + $index + "</span>";
  $div.innerHTML = index;
}

function increase() {
  $index++;
  create();
}

create();
</script>

</body>
</html>
 

 

function_call_by_onclick.htm(idの指定あり)

<html>
<body>

<div id="index"></div>

<script>
var $index = 0;

function create(id) {
  $id = id;
  var index = "";
  index += "<span onclick='increase()'>++</span>";
  index += "<span>" + $index + "</span>";
  document.querySelector(id).innerHTML = index;
}

function increase() {
  $index++;
  create($id);
}
</script>

<script>
create("index");
</script>

</body>
</html>
 

 

test.htm

<html>
<body>
<script>
var index = new Index();
index.show();
function Index() {
  this.index;
  this.show = function () {
    document.write(this.index);
  };
  var that = this;
  var set = function () {
    that.index = 0;
  };
  set();
}
</script>
</body>
</html>
 

 


参考サイト

 

Java:フレームワークの自作

ここでは、抽象クラスとリフレクションの二通りの方法で超簡易フレームワークを作成する方法を紹介します。
家で例えるなら、フレームワークは骨組みの部分です。言い換えると、共通部分です。同じ骨組みでも内装や外装の異なる家を複数建てたい場合がありますよね。 そういう場合は、あらかじめ共通の骨組みを作っておいて、内装や外装を後からカスタマイズできるようにしておくと便利です。 フレームワークには、更に、発生したイベントに応じて、ユーザが登録した関数(コールバック関数)を呼び出すイベント駆動の機構が備わっています。
下記の例では、Frameworkクラスが骨組みで、Modelクラスが内装や外装に相当します。MVCモデルで言えば、Frameworkクラスがコントローラで、Modelクラスがモデル、標準入出力がビューに相当します。


ライブラリとフレームワークの違い
ライブラリでは、ユーザ定義関数(user-defined function)がライブラリを呼び出すのに対し、 フレームワークでは、イベントが発生したときにフレームワークがユーザ定義関数を呼び出します。


1.抽象クラスを使ったフレームワーク

抽象クラスを使ったフレームワークはオーソドックスな方法ですが、弱点は、メソッド名やメソッド数がフレームワーク側で決まり、ユーザ側ではそれらを決定できないことです。 HttpServlet は抽象クラスを使って作られています。

 

D:\pkg\framework\Framework.java

package framework;

import java.util.Scanner;

public abstract class Framework {
  protected Framework() {
    Scanner scanner = new Scanner(System.in);
    while (true) {
      String event = scanner.nextLine();
      switch (event) {
        case "event1":
        handle1();
        break;

        case "event2":
        handle2();
        break;

        case "event3":
        handle3();
        break;

        default:
        System.err.println("exception");
      }
    }
  }

  protected abstract void handle1();
  protected abstract void handle2();
  protected abstract void handle3();
}
 

 

Frameworkクラスのコンパイル

D:\pkg\javac0.bat(ファイル名をjavac.batにすると暴走します)

set JAVA_HOME=D:\sdk\jdk
set path=%path%;%JAVA_HOME%\bin
javac D:/pkg/framework/*.java
 

 

次にユーザ側のプログラムです。Frameworkクラスを継承し、コールバック関数 handle1、handle2、handle3 を実装(登録)して Modelクラスを完成させます。

 

D:\src\model.java

import framework.Framework;

class Model extends Framework {
  protected void handle1() {
    System.out.println("action1");
  }

  protected void handle2() {
    System.out.println("action2");
  }

  protected void handle3() {
    System.out.println("action3");
  }
}
 

 

D:\src\main.java

class Main {
  public static void main(String... args) {
    new Model();
  }
}
 

 

ModelクラスとMainクラスのコンパイル

D:\src\javac0.bat(ファイル名をjavac.batにすると暴走します)

set JAVA_HOME=D:\sdk\jdk
set path=%path%;%JAVA_HOME%\bin
javac -cp D:/pkg *.java
 

 

Mainクラスの起動

D:\src\java0.bat(ファイル名をjava.batにすると暴走します)

set JAVA_HOME=D:\sdk\jdk
set path=%path%;%JAVA_HOME%\bin
java -cp .;D:/pkg Main
 



〈実行〉

event1[Enter]
action1
event2[Enter]
action2
event3[Enter]
action3


2.リフレクションを使ったフレームワーク

Javaのリフレクションは、実行時に、文字列(変数名や関数名)を通して、変数の読み書きや関数の呼び出しを行うための機能です。 この機能を使うと、switch文を使わずに、登録したメソッドをメソッド名(文字列)で呼び出すことができます。 リフレクションでフレームワークを作成すると、ユーザ側でメソッド名やメソッド数を自由に決めることができます。 Apache Struts はリフレクションを使って作られています。

 

D:\pkg\framework\Framework.java

package framework;

import java.lang.reflect.Method;
import java.util.Scanner;

public class Framework {
  public Framework() {
    Scanner scanner = new Scanner(System.in);
    while (true) {
      try {
        String event = scanner.nextLine();
        String stringClass = event.split(":", 0)[0];
        String stringMethod = event.split(":", 0)[1];
        Class<?> clazz = Class.forName(stringClass);
        Method method = clazz.getMethod(stringMethod);
        method.invoke(clazz.newInstance());
      }
      catch (ReflectiveOperationException e) {
        e.printStackTrace();
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}
 


注)catch (Exception e) {} を外すと、捕捉できない例外が発生したときにループを抜けてしまいます。

次にユーザ側のプログラムです。メソッド名はユーザ側で自由に決めることができます。

 

D:\src\Model.java

public class Model {
  public void handle1() {
    System.out.println("action1");
  }

  public void handle2() {
    System.out.println("action2");
  }

  public void handle3() {
    System.out.println("action3");
  }
}
 

 

D:\src\main.java

import framework.Framework;

class Main {
  public static void main(String... args) {
    new Framework();
  }
}
 



コンパイルや実行は、抽象クラスの場合と同じです。


〈実行〉

Model:handle1[Enter]
action1
Model:handle2[Enter]
action2
Model:handle3[Enter]
action3


参考サイト