torutkのブログ

ソフトウェア・エンジニアのブログ

Java読書会「Spring徹底入門 第2版」を読む会(第1回)を終えて

Java読書会BOF主催「Spring徹底入門 第2版」を読む会(第1回)が10月20日(日)に開催されました。

www.shoeisha.co.jp

前回の書籍(基礎からのサーブレット/JSP入門 第5版)は、参加人数が2-3人と少し寂しい読書会でした。今回は7人の参加で開催となり、前回の書籍の読書会に比べると賑わった会となりました。

Springの書籍

今回の課題図書となった「Spring徹底入門 第2版」(2024年5月刊)のほかに、類書で「プロになるためのSpring入門 ――ゼロからの開発力養成講座:書籍案内|技術評論社(2023年7月刊)」があります。書籍の対象とするSpring Frameworkのバージョンが、「Spring徹底入門 第2版」が5系 である一方「プロになるためのSpring入門」が6系となっています。

書籍のボリュームは、「Spring徹底入門 第2版」が752ページに対して「プロになるためのSpring入門」が384ページとほぼ倍のボリュームとなっています。

目次構成を見ると、「Spring徹底入門 第2版」は、Springの概要紹介の薄い1章の後は、2章でDIとAOPの詳細な説明が開始され、頭から読んで手を動かしながら徐々に詳しく解説していくる体裁ではなく、リファレンス的な構成になっています。開発環境についてはAの付録で記載、Hello World的なサンプルは13章の Spring Bootの章で初めて出てきます。 「プロになるためのSpring入門」は、第1部基本編と第2部詳細編と2部構成で徐々に詳しく解説していくスタイルのようです。

入門としての読みやすさは「プロになるためのSpring入門」で、独学でも読めそうです。一方、読書会向き(議論が活発になる書籍)ということではSpring徹底入門なのかなとの感触です。入門本で700ページ越えもサイズが強烈です。

PDFの入手

書籍サイトでは電子書籍の購入で、一見PDF版が見当たらず、KindleKobo、Hontoに誘導されるように見えますが、PDF版を翔泳社から購入可能です。今回、電子書籍購入して参加の方の多くが、PDF版がないと思いKindle版を購入したと言っていました。次の図の操作でPDF版の購入に辿れました。

電子書籍(PDF版)購入の流れ

Spring Boot開発環境

書籍で解説されるコード断片を実行しようとする場合、DIコンテナを含めて各フレームワーク・ライブラリが動く環境が必要です。簡単にSpringアプリケーションを動かすのがSpring Bootの機能なので、まずはSpring BootでSpringアプリケーションを作って動かせるようにします。

書籍では、A.付録において、Spring Frameworkを使うアプリケーションの開発環境として、EclipseベースのSTS(Spring Tool Suite)の準備が説明されています。

Spring Bootの開発では、WebサービスのSpring Initializr へアクセスし、設定を入力するとプロジェクトの雛形がダウンロードできるので、これを展開してIDEに読ませるという流れになります。設定ではビルドツールにMavenかGradleを選択できます。

IntelliJ IDEAの場合、有償のUltimate版なら Spring Initializrにアクセスしなくてもプロジェクトの作成からSpring Bootを選択して雛形を生成できます。と言っても、プロジェクトの作成では裏でSpring Initializrをアクセスしているようです。無償のCommunity版の場合は、Spring Initializrにアクセスして生成した雛形を読み込んで使用する方法となります。

Spring Bootを使ってビルドしたアプリケーション(JAR形式)は、Webサーバーが組み込まれるので、JARファイルを実行するだけでWebサーバーを用意しなくてもWebブラウザからアクセスが可能になります。デフォルトではembedded TomcatらしきクラスファイルがJARに含まれていました。

IntelliJ IDEA(Ultimate版)でSpring Bootプロジェクト生成

新規プロジェクトを生成します(File > New > Project)

プロジェクト名、言語、ビルドツール、パッケージ名などを選択・入力します。

Dependencies で、Spring Webを選択します。これを選択することで、SpringのWeb機能の基本的な振る舞いを利用できるようになります。ブラウザにHelloメッセージを表示する最初のプログラムはこのライブラリを使用します。

アプリケーションのmainクラス(エントリーポイント)とテストクラスの雛形が生成されました。 生成されたmainクラスを実行すると、Webサーバー機能が動作し、デフォルトではデフォルトでは 8080ポートで待ち受けます。Webブラウザからlocalhost:8080に接続すると次のエラーが発生します。これは、ルート/にマッピングされる処理が未定義のため 404エラーとなっている場合のメッセージです。

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Mon Oct 28 00:00:55 JST 2024
There was an unexpected error (type=Not Found, status=404).

Hello World! とメッセージを表示するWebアプリケーションとするために、生成されたmainクラスに@RestControllerアノテーションを付与し、HTTPのGETメソッドでルート/を指定された際にメッセージを返却するメソッドを@GetMappingアノテーションを付与して定義します。

package com.torutk.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {

    @GetMapping("/")
    String hello() {
        return "Hello World!";
    }

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

}

Hello World的なWebアプリとして単一クラスで完結させるのに、REST APIを扱う@RestControllerを使用しました。 コントローラのメソッドにREST APIのメソッド(GET)をマッピングし、メソッドの戻り値をAPIの戻り値にラップします。戻り値の型をStringとしているので単純な文字列が返却されますが、通常はリソースクラスを定義して返却します。

通常のWebアプリケーション用の@Controllerは、別途Viewに相当する仕組みを用意するか、@ResponseBodyを使うなどひと手間かかります。

史跡巡り 桶狭間合戦場・大高城・鷲津砦・丸根砦

先日、遅めの夏休みを取って、名古屋方面に一人旅をしてきました。 織田信長の足跡をたどって、初日は桶狭間合戦場と、大高城、鷲津砦、丸根砦の跡を巡りました。

地図に、大高城と鷲津砦、丸根砦、そして桶狭間合戦場と伝えられる場所をマークしました。また当時は大高城付近まで海となっており、地図で平地(緑色)の大半は海と考えられます。 出展:国土地理院ウェブサイト デジタル標高地形図 濃尾平野周辺をもとに、城・砦跡、合戦場をマークして記載

大高城は織田方から今川方のものになっており、鷲津・丸根の砦は大高城に対抗するため織田方が築いた砦です。桶狭間合戦の日に鷲津砦・丸根砦は今川方の攻撃を受け落城、今川義元は本隊を率いて沓掛城から大高城へ入る途中、桶狭間付近で織田信長の奇襲を受けて受けて打ち取られ、今川方は撤退していきました。

桶狭間合戦場の場所は、実際には特定されておらず、上述地図に記載した場所のほかに、旧東海道沿いの場所があるようです。

桶狭間合戦場公園

  • 小さな公園となっていて、織田信長今川義元の像などがあります。最寄り駅は名鉄有松駅で、徒歩25分またはバス10分ほどになります。

鷲津砦

  • JR東海道線 大高駅のすぐ北側にあります。小山の途中が跡地となっていますが、特に遺構などはないようです。

丸根砦

  • 鷲津砦跡から徒歩10分弱、尾根伝いに歩くと至ります。

大高城

  • 鷲津砦跡から徒歩20分、砦と違ってそこそこの大きさです。
経路

今回は、名古屋駅から名鉄で有松駅に向かい、有松駅からバスで桶狭間寺前で下車、NPO法人が運営する桶狭間古戦場観光案内所に寄ってから桶狭間公園とそこから数十mほどにある今川義元本陣跡(住宅の前に看板だけある)、そこから有松駅方面に徒歩で向かい、旧東海道沿いの有松伝統的建造物群保存地区を見ながら有松駅に着きました。

有松伝統的建造物群保存地区

名鉄有松駅からバスでJR大高駅に向かい、大高駅のすぐそばにある鷲津砦跡から丸根砦跡を回り、そこから大高城へ歩いて向かいました。

Webまわりの技術知識の獲得のための書籍購入

Java読書会BOFで「基礎からのサーブレット/JSP 第5版」を読みすすめていると、Webアプリケーションの技術知識として増やしたいことがいくつかでてきました。

  • Webページの見栄えをよくするには、HTMLに加えてCSSCascading Style Sheets)が必要
  • Webページに動きを伴わせるには、JavaScriptが必要か、Javaだけでもできるかを知りたい
  • HTTP/2やHTTP/3について知識をえておきたい

そこで、2、3冊書籍を買って読み始めました。

まずはHTMLとCSSの本から選んだのが「1冊ですべて身につくHTML&CSSとWebデザイン入門講座」(SBクリエイティブ刊 2024年3月)

JavaScriptをブラウザ上でどのように使うのか、フロントエンド全般に書かれた「フロントエンドの知識地図 ~ 一冊でHTML/CSS/JavaScriptの開発技術が学べる本」(技術評論社刊 2023年11月)

HTTP全般について、HTTP/2、HTTP/3も解説のある「Real World HTTP 第3版」(オライリージャパン刊、2024年4月)

H2 databaseでのレコード登録時間

Java読書会の宿題より

Java読書会「基礎からのServlet/JSP 第5版」を読む会(第3回)の宿題で、int(32bit整数)を超える行のテーブルを作成して、一気にintを超える行の更新をした時の戻り値を調べてみよう、の取り組みです。

H2データベースへのデータ登録に要する時間

primary keyもunique属性もない単一カラムのテーブルを定義

CREATE TABLE bigdata (
    id int
);
単純 insert

32bit符号付整数値の最大値がおよそ21億なので、30億行のレコードを登録します。 まずは、単純に INSERT INTO bigdata(id) values (1); のようにデータをインサートするクエリを30億回実行します。

  • 方法1 自動コミットが有効(デフォルト)
  • 方法2 自動コミットを無効にし、100万行ごとにcommit

方法1は、100万行のinsertに20秒を要しました。30億行のinsertに推定17時間要する見積です。自動コミットが有効だと、insertを実行するごとにコミットが働くので遅いだろうと想定していました。

方法2で、自動コミットを無効として、一定量毎にcommitをしてみました。 ところが、方法2でも、100万行のinsertに20秒を要し、方法1とほとんど処理時間が変わりません。

バッチinsert

次は、バッチ登録を実施します。複数のinsert文をバッチ化してからバッチを実行します。100から1000個のisertをバッチにまとめます。

     PreparedStatement ps = conn.prepareStatement("insert into bigdata values(?)");
       :       
    for (int i = 0; i < 1000; i++) {
        ps.setInt(1, count);
            ps.addBatch();
    }
    ps.executeBatch();

バッチ登録の場合、自動コミットはオフとし、最後にcommitします。*1

さて、期待していた登録速度ですが、なんと方法3でも100万行のinsertに20秒を要し、方法1、2とほとんど同じでした。

ここまでの結果から、H2 databaseはSQLのリクエストのオーバーヘッド、自動コミットのオーバーヘッドがほとんどなく、クエリの処理数がほぼそのまま処理時間になっているものと推測します。

すると、クエリの処理数を減らして登録行数を増やすことができれば処理時間を短縮できるのではと考えます。

複数行を1つのinsertで

ここで、insert文は、複数行のデータを登録することができます。

insert into bigdata(id) values (1), (3), (5), (7), (11), (13);

このinsert文は、6行のレコードを登録します。

  • 方法4 自動コミットを無効にして、10行のデータを1つのinsert文で登録する

100万行の登録が2-3秒と短縮することができました。

  • 方法5 自動コミットを無効にして、100行のデータを1つのinsert文で登録する

100万行の登録が1秒とさらに短縮することができました。これであれば、30億行の登録に50分の見積もりです。

実行環境
実行結果

*1:30億行となると、さすがに小分けにcommitした方が良いかも

Java読書会「基礎からのサーブレット/JSP 第5版」を読む会(第3回)

Java読書会BOF 主催の「基礎からのサーブレット/JSP 第5版」を読む会(第3回)を4月27日(土)に開催しました。

今回は、読書会で読んだ範囲からいくつかトピックを記載します。

第3回を開催して、Chromeの振る舞いを知る

サーブレットが、シングルインスタンスでマルチスレッドでアクセスされる例において、

p.161

1つのブラウザ、たとえばChromeで同じサーブレットに対して2枚のウィンドウ(またはタブ)を開いた場合には、片方のウィンドウの処理が終了するまで、もう片方の処理は待機するので、カウンタの値が不適切になりません。

との記述がありました。試してみたところ、ウィンドウを2枚開いて、同じサーブレットにアクセスしてみましたが、ほぼ同時にアクセスでき、カウンタの値が不適切になりました。(ここで不適切とは、サーブレットクラスのフィールドにカウンタを保持し、アクセス時にフィールドの値を取得し数秒ウェイトしてから値をインクリメントしてフィールドに代入するコードのため、マルチスレッドでは複数が同じ値を取得することがあるという意味)

試したブラウザは、SafariFirefoxでしたが、Chromeを使ったところ不適切な更新は発生せず、2枚目のウィンドウがサーブレットにアクセスするのが遅い動きをしていました。 Edgeでも不適切な更新が発生せず、どうやらChromiumエンジンが関与しているようです。

サーブレットのURLに、リクエストパラメータを追加し、その値を2枚のウィンドウで変えてみたところ、不適切な更新がChromeでも発生しました。このことから、同一のURLに対するアクセスは、先にアクセスした方が完了するまで次のアクセスが待たされているようでした。

デベロッパーツールで調べてみると、2つ目のウィンドウからのアクセスは数秒間Stalledとなっていました。ぐぐってみたところ、Chromeは同一URLへのアクセスが並行して発生した場合、キャッシュコントロール上1つ目のリクエストが復帰するまで2つ目のリクエストの送出を待機する仕組みがあるとのことです。 なので、クエリパラメータを付加してその値をかえることでこの仕組みを回避することができていたのでした。

フィルターがCSSなどのファイルの読み込みを阻害

サーブレットフィルタの解説で、次のようにServletResponseのsetContentTypeを各サーブレットに記述するのは冗長なため、フィルタで記述する例がありました。

@WebFilter(urlPatterns={"/*"})
public class EncodingFilter implements Filter {
    public void doFilter(...) {
        :
        response.setContentType("text/html; charset=UTF-8");

このサンプル(フィルタ)を使用していた環境で、HTMLファイル(hello.html)にcssファイルを適用してみたところ、cssが反映されないという事態が生じました。

ブラウザのデベロッパーツールで調査したところ、次のエラーが発生していました。

[Error] Did not parse stylesheet at 'http://localhost:8080/book/css/book.css' because non CSS MIME types are not allowed in strict mode.

フィルタのURLパターンが /* のため、CSSファイルもフィルタのURLパターンにふくまれてしまい、cssファイルが本来 text/css というMIMEタイプで送信されるべきところ、text/htmlとなってしまったためです。

対応策としては、次が思い浮かびます。

  • サーブレットJSPのURLを、CSSや画像ファイルなどのリソースとは別な要素、例えば/servlet/xxx のように割り振り、フィルタのURLパターンを /servlet/* のように指定する
  • サーブレットのURLを、xxx.servletのように拡張子パターンが適用できるように命名し、フィルタのURLパターンを、.servlet.jsp のように指定する

URLパターンの指定では、ワイルドカードの使用は /パス要素/* のようにスラッシュで区切った後ろに指定することはできますが、/パス要素の途中* のように文字列の途中から後をワイルドカード指定することができません。

拡張子を指定する場合、パス要素を指定することができません。

リソースのクローズ

データベースへのアクセスで、サーブレットからJNDIでデータソースを取得し、JDBCでデータベースへアクセスします。

この際に、JNDIの InitalContext、JDBCのConnection、PreparedStatement、ResultSetを取得しています。これらは使用が終わったらcloseを読んでリソースを解放する必要があります。

書籍のサンプルでは、次のようなコードとなっています。

public void doGet(...) {
    :
    try {
        InitialContext ic = new InitialContext();
        DataSource ds = (DataSource) ic.lookup("java:/comp/env/jdbc/book");
        Connection con = ds.getConnection();
        PreparedStatement st = con.prepareStatement("select * from product");
        ResultSet rs =st.executeQuery();
        while (rs.next()) {
           :
        }
        st.close();
        con.close();
    } catch (Exception e) {
        e.printStackTrace(out);
    }
    :
}

このコーディングでの問題点は次です。

  • tryブロック内の処理途中で例外が発生した場合、たとえば executeQueryの実行中に例外が発生すると、その後のcloseを呼び出すことなく catch節に処理が移行してしまうため、リソースリークが生じる
  • ResultSet、InitialContextに対するcloseの呼び出しがない
    • JDBCAPI規定上は、Statementのcloseを呼び出すと、そのStatementが生成するResultSetもcloseされるとありますが、JDBCの実装が必ずそうなっているとは限らない
    • JNDIのInitialContextは、closeしない場合のリーク発生有無が明示されていませんが、必要と想定

是正案としては次があります。

  • tryブロックではなく、finallyブロックでcloseを呼び出す
  • try-with-resource構文を用いる
try-finallyで実装する案

従来のtry catchで確実にクローズするように finallyブロックでcloseを呼び出す

InitialContext ic = null;
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
   :
} catch (Exception e) {
   :
} finally {
    try {
        rs.close();
    } catch (Exception e) {
    }
    try {
        st.close();
    } catch (Exception e) {
    }
    try {
        con.close();
    } catch (Exception e) {
    }
    try {
        ic.close();
    } catch (Exception e) {
    }
}    

finallyブロックで、それぞれのリソースにたいしてcloseを呼びます。 それぞれのclose呼び出しをtry-catchで個別に囲っているのは次の理由です。

  • closeメソッドも例外をスローする可能性があるため、例外をスローしたとしても後続のcloseをきちっと呼び出すように制御フローを組んでいる
  • 元のtryブロックで例外が発生し、tryブロックから外へ例外をスローする場合、finallyブロックの中でcloseが例外をスローし、その例外をfinallyブロックから外に出すと、tryブロックの例外がfinallyブロックの例外に上書きされてしまうため

try-catch-finallyで、finallyブロックの中でクローズ対象のインスタンスを参照するには、インスタンス変数が try-catch-finallyの外側で宣言されている必要があります。 その場合、宣言時に実体が存在しないので初期値としてnullを入れています。finallyブロックでは、クローズ対象のインスタンスがnullである場合が想定されるので、上述のようにcloseをtryブロックで実行し、catchでNullPointerExceptionを含めて捕捉するか、次のように nullチェックをしてからcloseを呼び出します。

  • nullチェックをしてからcloseを呼び出し、closeのシグネチャで宣言される例外をcatchする
} finally {
    if (rs != null) {
        try {
            rs.close();
        } catch (SQLException e) {
        }
    }
    :

close()を覆うtry文で、SQLExceptionをcatchしても、何かできることは特にないので、これをcatch (Exception e) とすれば、NullPointerExceptionも捕捉できるので、nullチェックをしなくてもよいかなと思います。  

try-witch-resourceで実装する案

try-with-resourceを使うと大分改善できます。

  • close呼び出しの記述が不要(finallyブロックの記述を省略)
  • 複数のリソースを使用しているときに、1つのcloseで例外が発生しても、残りのリソースに対してcloseが呼ばれる
  • tryブロックで発生した例外を tryブロックの外にスローする場合、closeで例外が発生しても上書きすることはない

ですが、クローズ対象のクラスがAutoCloseableをimplementsしている必要があります。JDBCの例では、JNDIのInitialContextがAutoCloseableでないのでやっかいです。

    InitialContext ic = null;
    DataSource ds = null;
    try {
       ic = new InitialContext();
       ds = (DataSource) ic.lookup("java:comp/env/jdbc/book");
    } catch (Exception e) {
        // エラー中断の処理
    }
    try (
        Connection con = ds.getConnection();
        PreparedStatement st = con.prepareStatement("select * from product");
        ResultSet rs = st.executeQuery();
    ) {
        // 
    } catch (Exception e) {
        // エラー中断の処理
    }

PreparedStatementにパラメータをセットする場合、try () の中に記述できないのでもう少し複雑なコードになります。

    try {
        InitialContext ic = new InitialContext();
        DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/book");
        try (
            Connection con = ds.getConnection();
            PreparedStatement st = con.prepareStatement("select ? from product")
        ) { 
            String column = request.getString("column");
            st.setString(1, column);
            try (ResultSet rs = con.executeQuery()) {
                while (rs.next()) {
                    :
                }
            }
    } catch (NamingException | SQLException e) {
        // エラー中断の処理
    }
参考

JPCERT CC FIO04-J. 不要になったリソースは解放する

Java読書会「基礎からのServlet/JSP第5版」を読む会(第1回)

新しい本「基礎からのServlet/JSP第5版」

今月のJava読書会BOF主催のJava読書会は、「基礎からのサーブレット/JSP 第5版」を読む会(第1回)を開催しました。

最新のServlet 6.0/JSP 3.1に対応し、Tomcat 10.1とOpenJDK 21で動かしながら学んでいく内容です。

概要

前回は洋書でしたが、今月からは和書です。が、参加人数が3名と少ない状況でした。(3連休の合間だったせいもあるか、宣伝不足か、読書会による勉強はニーズが少なくなっているのか、など憶測色々ありました)

この書籍では、メインはWindows上でOpenJDK 21とTomcat 10.1、H2 Database 2.1を使ってServletJSPのプログラミングを学んでいこうという内容です。

Tomcatは、最後に触ったのが20年前か或いはもっと前か、懐かしいというかほとんど覚えていないです。

今時点で書籍サポートサイトには正誤表は記載ありませんが、読書会をすると毎回誤植を見つけるよねと話して読み進めていたら、いくつか発見しました。

読書会の進め方は、例によって、本文、表、図、ソースコードを朗読して進めました。

昼食は、近くの生ハラミ焼肉屋さんで焼肉ランチを食べました。

TomcatとJetty

本書はTomcatを使っていますが、書籍では他のJakarta EEアプリケーションサーバとして、WildFly、Jetty、GlassfishWeblogic Serverが紹介されています。 JettyとTomcatの違いは?と議題になり、Tomcatはサーバープログラムをまずインストールして動かし、そこにサーブレットJSPを使うプログラムを展開(デプロイ)する使い方、JettyはサーブレットJSPを使うプログラムにWebサーバーが含まれる使い方となるということでした。

開発環境構築

書籍は、Windows環境での動かし方を記載し、付録でMacOSLinuxの環境に触れています。書籍は、サンプルプログラムと一緒の作業ディレクトリにOpenJDK、tomcat、H2 Databaseを展開したアーカイブファイルを作り、書籍サイトからダウンロードして展開してねという形です(200MB超過のアーカイブファイル)。

MacOSでの環境(書籍とは別)

MacOSでは、Homebrewにtomcat 10.1が用意されており、brew install tomcat で 10.1.19がインストールされました。OpenJDKもH2 DatabaseもHomebrewでインストール可能です。

Homebrewでインストールしたら、catalina runでサーバーが起動しました。簡単ですね。デプロイは、/opt/homebrew/opt/tomcat/libexec/webapps/ ディレクトリの下にアプリケーションのディレクトリ(本書ではbook)を作成、その下のWEB-INF/classesにパッケージに対応してディレクトリ・クラスファイルを配置します。

Tomcat周りのメモ
  • examplesアプリケーションは、tomcatをインストールしたマシンからのみアクセス可能。META-INFのcontext.xmlに、リモートアクセスの許可が 127...*、::1、0:0:0:0:0:0:0:1に限定されているため
  • ROOT にTomcatのようこそ画面を表示するアプリケーションがある
  • Java Platform Module Systemに対応しているのだろうか?

Java関連書籍「ソフトウェア設計のトレードオフと誤り」

正月休みの時にぶらっと本屋さんに寄った時に、Java読書会BOFの次の読書会の候補本になりそうかと買ってみました。

題名 ソフトウェア設計のトレードオフと誤り
著者 Tomasz Lelek, Jon Skeet
訳者 渋川よしき ら訳
出版 オライリー・ジャパン 2023年5月刊行
ISBN 978-4-8144-0031-7
価格 4180円(税込)

プログラム、アプリケーション、システムを作成するときに遭遇する設計上の選択を、トレードオフとして紹介しています。課題は、プログラミング・コード上のもの(例:シングルトン・パターンの実装方法、継承とコンポジションの使い分け、例外)から、共通ライブラリ、バージョニング、APIの柔軟性と複雑性、最適化、日付と時間、データのローカリティ、メモリかディスクか、サードパーティライブラリの利用、分散システムの一貫性、アトミック、配信、DIフレームワーク、リアクティブプログラミング、関数プログラミングの使いどころなどが並んでいます。 コードはJavaで書かれています。

読み始めたばかりですが、最初の方に出てくるシングルトン・パターンでは、同期をgetInstanceでかける方法、ダブルチェックロッキング、スレッドローカル変数での実現(厳密にはシングルトンではなくなりますが)を列挙し、パフォーマンスを含めてトレードオフしています。

日付と時間についてかなり深い記述がありますが、著者の一人JonがNoda Time(.NET用の日時ライブラリ)の開発者なのでうなづけます。 なお、Noda Timeのライブラリ名から推測できる通り、JavaのJoda Timeライブラリの.NETポーティングです。Joda Timeは、javaのdate & time APIの元になったライブラリです。