Saturday, October 26, 2013

log4j2にログを集める

log4j, slf4j, logback, commons-logging... こんがらがりませんか?僕はこんがらがりました。

ある実験的なWebアプリの開発の過程では、ログを最終的に何に集約するか?logbackか?それとも...?と、迷い、 結局log4j2に集約しました。これはそのメモみたいなものです。

なお、「slf4j+logbackのほうがナウでヤングだろ」といったご意見はスルーです。好みとかポリシーの問題です。 また、いにしえより伝わるlog4j1.xではなく、log4j2.xっていうところもポイントです。 java.util.logging?えーっ?!却下。

1. 前提とか方針とか

  • 新規のJava-Webアプリを開発する。そのコード上で使うLoggerにはlog4j2を使う。
  • ベースはSpringMVCフレームワーク。spring自身は内部でcommons-loggingを使っているので、それもlog4j2に集められるように設定する。
  • Spring以外にもいろいろOSSライブラリを使うが、それが吐くログもできる限りlog4j2に集める。例えば、内部でslf4j-apiを使ってるOSSが最近はとても多い。
  • 設定はlog4j2.xmlに集める。とりあえずWebアプリ内部に同梱する。
  • ビルドツールはmavenを使う

2. mavenのpom.xml

dependenciesタグ内で先に書いてあるほうが優先されるので、ログ出力ライブラリは上のほうに書いておくのが無難です。log4j-coreがもちろんlog4j2の本体。 log4j-jclがcommons-loggingによる出力をlog4j2に中継させるやつ。log4j-slf4j-implはslf4jによる出力を(以下略)

    
        
            org.apache.logging.log4j
            log4j-core
            2.0-beta8
        
        
            org.apache.logging.log4j
            log4j-jcl
            2.0-beta8
        
        
            org.apache.logging.log4j
            log4j-slf4j-impl
            2.0-beta8
        

        ... other dependency...
    

maven使いじゃない人のために、これで実際どんなjarがビルドパスに入ってくれるのか、書いておきます。こんだけ。

log4j-core-2.0-beta8.jar
log4j-api-2.0-beta8.jar
log4j-jcl-2.0-beta8.jar
commons-logging-1.1.1.jar
log4j-slf4j-impl-2.0-beta8.jar
slf4j-api-1.7.5.jar
slf4j-ext-1.7.5.jar
cal10n-api-0.7.4.jar

3. log4j2.xml

log4j2.xmlをsrc/main/resourcesに置く。root(要するにデフォルト値)を設定するほか、 ログ出力を制御したいクラス名、パッケージ名に対して出力レベルや出力先(appender)を設定します。

4. コードを書く

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Foo {
    private static Logger logger = LogManager.getLogger();
    public void bar(String a, String b) {
        logger.debug("a={},b={}", a, b);
    }
}

ログに変数をいれたいときに、logger.debug("a=" + a + ",b=" + b ) などとやってしまいがちですが、 実はこれ、ログ出力メソッドよりも先に+演算子による文字列連結の方が先に実行されますので、性能影響が出ます。 logger.debug()なんて本番環境ではオフにすると思いますが、それでも文字列連結は無駄に実行されてしまうのです。 どの程度の性能差が出るかは Performance - Apache Log4j 2 に詳しく解説されています。 せっかくlog4j2を使うのですから、{}記号によるプレースホルダ機能を使うようにしましょう。

また、log4j1.xのときは、
provate static Logger logger = Logger.getLogger(Foo.class);
のように、ロガー名として自クラスを明示的に与えてloggerを作る必要がありましたが、 log4j2のLogManagerでは省略できます。 log4j2のほうが好みな理由はこれが地味に便利だからかも。

Tuesday, October 1, 2013

Mixer2 as XHTML snippet generator

Mixer2 is the xhtml dedicated template engine. Because Mixer2 use JAXB (Java Architecture for XML Binding) internally, the usage is not only as a template engine, but also, Mixer2 can be used in another way.

Mixer2 has some different functionality.

  1. as a alternative to JSP, Velocity, and other template engine technology
  2. as a XHTML parser
  3. as as XHTML tag snippet generator

In thie post, I introduce "XHTML snippet generator" functionality of Mixer2.

You must fix up your html template as "full html" like "<html>...</html>" in order to use mixer2Engine.loadHtmlTemplate() .

But if you need to output xhtml tag using mixer2Engine.saveToString(), you can get any tag snippet as string. So, you can create some helper class like custom tag of JSP.

For example, If you must use jsp as the base template but also you want to use Mixer2, You can use them combine them.

If you create the new tag instance, use "new" the tag classes. Also you can use TagCreator.

Div div = TagCreator.div();
Span span = TagCreator.span();
span.getContent().add("Hello World !");
span.setStyle("color:red;");
div.getContent().add(span);
String result = mixer2Engine.saveToString(div);
System.out.println(result);

output:

Hello World !

If you create table tag, you can use TableBuilder.

String[][] data = new String[][]{
    // id, name, price
    {"1", "cookie", "2.50"},
    {"2", "candy",  "3.75"}
};

TableBuilder tBuilder = new TableBuilder();
for (String[] row : data) {
    tBuilder
        .addTr()
        .addTd(row[0])
        .addTd(row[1])
        .addTd(row[2]);
}
Table table = tBuilder.build();
String str = mixer2Engine.saveToString(table);
System.out.println(str);

output:

1 cookie 2.50
2 candy 3.75

For more detail of TableBuilder, see Mixer2 official site.

How about it ? Mixer2 and other template technology can exist together. Try Mixer2 !

XHTMLタグスニペット生成器としてのMixer2

Mixer2はXHTML専用のテンプレートエンジンです。 しかし、Mixer2が内部でJAXB(Java Architecture for XML Binding)を使用しているため、 単なるテンプレートエンジンにとどまらない使い方が可能です。
大きく分けて、Mixer2には次のような側面があります。
  1. JSPやVelocityに代わる技術としてのMixer2
  2. XHTMLパーサーとしてのMixer2
  3. XHTMLタグスニペット生成器としてのMixer2
この記事では、3番目の、XHTMLタグスニペット生成器としてのMixer2の使い方を紹介します。
Mixer2は、XHTMLをパース - loadHtmlTemplate() - する場合には フルHTML、つまり<html>...</html>までのHTML全体を与える必要があります。
しかしXHTMLタグを出力 - saveToString() - する場合には 任意のタグのみをString化することが可能です。 この特徴をうまく使えば、JSPのカスタムタグのようなものを 簡単なJavaコーディングのみで作ることができます。
たとえば、ベースとしてJSPを使いつつもMixer2を活用したい場合、下の図のような使い方が可能です。

タグを新規に作る場合は目的のタグ型クラスを new すればよいのですが、 TagCreatorクラスでも可能です。 TagCreatorのほうがeclipse等のIDE上でコード補完が効くので便利です。
Div div = TagCreator.div();
Span span = TagCreator.span();
span.getContent().add("Hello World !");
span.setStyle("color:red;");
div.getContent().add(span);
String result = mixer2Engine.saveToString(div);
System.out.println(result);
output:
Hello World !
table タグを作る場合にはTableBuilderクラスが便利です。
String[][] data = new String[][]{
    // id, name, price
    {"1", "cookie", "2.50"},
    {"2", "candy",  "3.75"}
};

TableBuilder tBuilder = new TableBuilder();
for (String[] row : data) {
    tBuilder
        .addTr()
        .addTd(row[0])
        .addTd(row[1])
        .addTd(row[2]);
}
Table table = tBuilder.build();
String str = mixer2Engine.saveToString(table);
System.out.println(str);
output:
1 cookie 2.50
2 candy 3.75
For more detail of TableBuilder, see Mixer2 official site.
いかがでしたか? できあがったタグオブジェクトをstring化したあとなら jspやvelocityに埋め込むこともできます。 Mixer2と他のテンプレートエンジンの共存は十分に可能です。 ぜひ組み合わせ技も考えてみてください。

Sunday, September 22, 2013

Google Maps API with Mixer2, SpringMVC

Mixer2 - the java/xhtml template engine - is compliant to inline JavaScript. https://github.com/nabedge/googlemap-mixer2-sample is a sample project using Google Maps API, Spring MVC, and Mixer2. I describe the code and some tips.

First, look at the map.html, a template html file for Mixer2. This html is almost a copy of google official hello world sample.

The difference is one code.

before:

center: new google.maps.LatLng(-34.397, 150.644),

after:

center: new google.maps.LatLng(defaultValues.lat, defaultValues.lng),

and one script tag is added.


The point is that the "data" and "program" is separated into each <script> tags.

Next, the controller of Spring MVC, IndexController. Controller create the model having start latitude and longitude values and pass it to view;

    @RequestMapping(value = "/")
    public String map(Model model) {
        log.debug("showing map()...");

        Map latLngMap = new HashMap();
        latLngMap.put("lat", 35.633302);
        latLngMap.put("lng", 139.799652);
        
        model.addAttribute("latLngMap", latLngMap);
        
        return "mapView";
    }

The view is MapView class.

import com.google.gson.Gson;

// snip...

// load html template
String mainTemplate = "classpath:m2mockup/m2template/map.html";
Html html = getMixer2Engine().loadHtmlTemplate(
        resourceLoader.getResource(mainTemplate).getInputStream());
        
// snip...

Map latLngMap = (Map) model.get("latLngMap");

// set default value for google maps api
Gson gson = new Gson();
Script script = html.getHead().getById("defaultValuesJson", Script.class);
script.setContent("var defaultValues = " + gson.toJson(latLngMap) + ";");

You should look that the content of <script id="defaultValuesJson"> tag is replaced by the new json strings using mixer2 tag object and methods.

You get the result of browser:


.....

THE POINTS:

  • You can write map.html on local editor. The JavaScript code can run without deploying application server. You can see the map on browser. If you use JSP and write the code like "center: new google.maps.LatLng(<%=startLat%>, <%=startLng%>)", Your code can not run it as JavaScript !
  • Using Mixer2 as template engine, you should separate "data" and "program" in your JavaScript code on the template and put them into each <script> tags. On the server side, replace only <script> tag having the "data".

Tuesday, September 10, 2013

html5j&JJUGの勉強会でLTしてきた(資料うpあり)

疲れてる&軽く酔ってるので資料うpと、自分なりのメモだけ。

うん、Web標準とHTML5っていうテーマをみんな無視しすぎだと思った。まあ堅いこと言ってもしゃーないけどさ。

ということでやったLTがこちら。

この資料だけじゃ、5月のJJUGでのプレゼンや、1月のSpring勉強会でのプレゼンの焼き直しにすぎないですごめんなさい。肝心なのはこの前半にデモしたWebアプリです。 そのソースコードはこちら。

もっと詳しく聞いてやってもいいぜ、という方は、今月末の第三回 #渋谷Java : ATNDへお越しください。それまでの間は「Mixer2によるダイナミックCSS Sprite」に関する全貌の細かい話はブログ等には書かないつもりです。

そうはいっても、それぞれスケジュールってものがありますので今日の勉強会だって今月末のだって、来れない人だっていますよね。 今日もLT後にいろいろ質問してくださった方がいらっしゃいました。それはそれで大歓迎なんですが、 どうぞメーリングリストのほうもご利用ください。ちょっとした疑問でも、漠然とした質問でも。

Mixer2がらみじゃないことでも、今夜はもうひとつ、いいことがあった気がします。いい気分。おやすみなさい。

Sunday, July 28, 2013

JavaとキャッシュとMixer2

激辛担担麺ランチ食ったら目が覚めるどころかかえって猛烈な睡魔に襲われております。

さて、今日はキャッシュ技術の話。先に言っておきますが、すごく長いです。そして大した結論ではないです。

ソフトウェア、ハードウェアの性能向上にはキャッシュ技術は欠かせません。これは開発言語も分野も問いません。 CPUにだってキャッシュ的な機能要素はあります。WebアプリではDBの検索結果をキャッシュして使いまわすことで DBの負荷を下げ全体の性能を上げるというのは常套手段です。

キャッシュとkey-valueストアとがごっちゃに論じられる場合もあります。実質的には同じようなことなのでどうでもいいんですけどね。 key-valueストアの代表格の一つにmemcachedがありますが、読んで字のごとく"キャッシュ"ですし。

これだけ便利で普及しているキャッシュという技術ですが、いざ使おうとすると結構面倒です。 どんどんキャッシュにつっこんでいたらキャッシュストアがあふれてしまって容量オーバー。よくある話です。 古いものから自動的に消えるようになってるから容量オーバーの心配無しとか言ってたら、 それがキャッシュヒット率の低下を招いていしまってキャッシュ機構の存在意義がなくなったり。 本物のデータのほうを変更したにもかかわらずキャッシュのデータのほうを変更あるいは削除しそこねてバグったり。 また、キャッシュしたいモノとそれにひもづけるキャッシュキーは正確に一対一でないと 間違ったキャッシュデータをキャッシュに保存し、 あるいは間違ったデータをユーザーにお届けしちゃうことになります。 何をキャッシュキーにするか?は慎重に考えねばなりません。

Javaアプリの場合はさらに面倒なことがあります。

キャッシュしたくなるようなデータは、多くのケースでは単なるString(例:JSON形式)だったりintやlongだったりします。その場合ディープコピーかシャローコピーかを意識する必要がありません。しかしその調子でやると、Javaクラスのインスタンス=たとえばいわゆるPOJO的なもの=をキャッシュ対象とする場合の落とし穴に気づかないことがあります。

二人のユーザー(≒二つのスレッド)が同時に同じデータをキャッシュから取得しようとしたとき、 キャッシュは二人に「同じデータ」を返します。 しかしJavaの場合は変数のコピーは基本的にリファレンス(参照)に過ぎないので、 キャッシュから受け取ったデータを一人が加工するともう一人のほうに渡されたデータも変わってしまいます。不思議と言えば不思議、当然と言えば当然の現象です。 それをよしとする要求ならそれでよいのですが、Mixer2のhtmlテンプレートロード機能の性能UPのためにキャッシュを使うという要求なのであれば話は別です。

AさんとBさんがほんの数マイクロ秒差で同じページにアクセスしたとしましょう。Aさん用画面を処理するスレッドがキャッシュから取得したHtml型インスタンスに「こんにちはAさん」を埋め込んだ直後、別スレッドがキャッシュから受け取った同じインスタンス(のリファレンス)に「こんにちはBさん」を埋め込んでしまう可能性が発生するのです。それはまずい。

Javaで実装されたキャッシュ機構として有名なものにEHCacheがあります。そのドキュメントにはこんなことが書いてあります。日本語訳by私なのであまり信用しないように(笑)

Ehcache | Documentation | Using Ehcache

Copy Cache

A Copy Cache can have two behaviors: it can copy Element instances it returns, when copyOnRead is true and copy elements it stores, when copyOnWrite to true.
コピーキャッシュパターンには二つの方法があります:

  • copyOnReadがtrueのとき、エレメントのコピーを戻り値として返す。
  • copyOnWriteがtrueのとき、エレメントのコピーをキャッシュに保存する。
注:エレメント≒キャッシュに保存しているデータのこと

A copy on read cache can be useful when you can't let multiple threads access the same Element instance (and the value it holds) concurrently. For example, where the programming model doesn't allow it, or you want to isolate changes done concurrently from each other.
同じエレメントのインスタンス(とそれが内部に持つ値)に対し、複数のスレッドがアクセスすることを許可したくない場合、キャッシュ読み込み時のコピー設定を使うとよいでしょう。たとえばプログラミングモデルがそれを許さない場合や、エレメントを確実に隔離しつつ同時発生的な変更を可能にしたい場合です。

Copy on write also lets you determine exactly what goes in the cache and when. i.e. when the value that will be in the cache will be in state it was when it actually was put in cache. All mutations to the value, or the element, after the put operation will not be reflected in the cache.
キャッシュ保存時のコピー設定を使うと、どんなデータがいつキャッシュに保存されたのかを厳密に把握することができます。キャッシュに保存されているものは保存した時点の状態のままです。エレメントに対するどんな変更も、それがキャッシュへのput処理の後のことなのであればキャッシュの中のエレメントには反映されません。

A concrete example of a copy cache is a Cache configured for XA. It will always be configured copyOnRead and copyOnWrite to provide proper transaction isolation and clear transaction boundaries (the state the objects are in at commit time is the state making it into the cache). By default, the copy operation will be performed using standard Java object serialization. We do recognize though that for some applications this might not be good (or fast) enough. You can configure your own CopyStrategy which will be used to perform these copy operations. For example, you could easily implement use cloning rather than Serialization.
コピーキャッシュパターンの具体例はXA(分残トランザクション)向きに設定されたキャッシュでしょう。 copyOnReadとcopyOnWriteを常にオンに設定することで、適切なトランザクション隔離と明確なトランザクション境界を提供します (コミット時のオブジェクトの状態はキャッシュに入れるときの状態と同じであるということ)。 デフォルトでは、コピー処理ではJavaオブジェクトのシリアライゼーションが使用されます。もちろん、アプリケーションによってはそれは良い(or速い)方法ではないことを、我々は認識しています。独自のCopyStrategyを設定しておけば、お望みのコピー処理方式を使用することができます。 たとえば、シリアライゼーションではなくクローン処理を使うように簡単に設定できます。

Ehcache | Documentation | Configuration

copyOnRead and copyOnWrite cache configuration

A cache can be configured to copy the data, rather than return reference to it on get or put.
キャッシュからgetまたはputするときにデータのリファレンスではなくコピーを使うかどうかを設定できます。
(途中略)
The default configuration will be false for both options.
デフォルトではcopyOnRead,copyOnWriteの両方ともfalseになっています。

長々と引用してしまいましたが、重要なポイントがさらりと書いてあります。

  • コピーを使うように設定したとしても、ほっとくとserializationを使うので場合によっては性能が悪かったりちゃんとディープコピーできなかったりするかもですよ。ちゃんとCopyStrategyも実装/設定してね。
  • デフォルトでは、ディープコピーじゃなくてリファレンスを、キャッシュから返したりキャッシュに保存したりしますよ。

EHCacheがイケてないということではもちろんありません。 このへんが汎用キャッシュライブラリとしてできる限界なのでしょう。 CopyStragetyパターンに沿ってディープコピーの方式を好きに実装&設定できるのですからむしろ高機能とも言えます。

ところで、キャッシュサーバやそのクライアントライブラリは今やいろんな実装があります。 しかしコレといった共通の規格がありません。そこで、Java言語用の共通APIが規格されつつあります(2013/7現在まだドラフト)。 それがJSR-107 JCacheです。ちなみに、JSR-107の中の人はEHCacheの中の人のシャローコピーです。

さて、ここでようやくMixer2の話になります。

テンプレートエンジンの多くがキャッシュ機能を持っています。velocityしかり、thymeleafしかりです。 同じ画面を表示するために同じテンプレートをパースする処理を繰り返すくらいなら、 パース処理後の状態をキャッシュに保存しておけばそのぶんの処理を省略できる=性能がよくなる、という寸法です。 多くの場合はキャッシュエンジンの内部にMapかなにかでキャッシュを維持しているだけです。

もちろん独自実装も使えるようにするためにインターフェイスを提供していることもあります。

Mixer2もこれらにならって独自のキャッシュインターフェースを提供することも考えたのですが、あえてそうはしませんでした。 代わりに選んだのがJSR-107 JCacheです。このinterfaceクラスを持っているjarファイル名で言うとcache-api-0.8.jar(数字はバージョンなので変わる可能性あり)です。

先日リリースしたmixer2-cachableでは、 EHCacheのような汎用キャッシュライブラリを使うか、あるいは独自に実装するかによらず、とにかくjavax.cache.Cache(通称JCache)として提供されるinterfaceを実装したクラスをプラグインして使えるようになっています。また、同梱したCache実装をデフォルトで自動的に使用可能なので、それでよければそれ以上頭を使う必要もありません。

CachableMixer2Engineクラス経由で提供されるloadHtmlTemplate()やその種のメソッドは、設定したCache実装を通してhtmlテンプレートのロードが可能です。何より重要なのが、必ず、キャッシュのget/putの過程ではHtml型インスタンスのディープコピーを使うように実装済みであることです。しかも、高速かつ正確なcopy()メソッドを使っています。

これによって、たとえばEHCacheを使ったときでもcopyOnWrite/copyOnReadのon/offに関係なく確実に高速ディープコピーが使われるので、キャッシュ上のデータを安全かつ高速に使うことができます。CopyStrategyに沿って実装をどうのこうのと考える必要もありません。

また、CacheableMixer2Engineはロードしようとするテンプレートそれ自体のSHA-1ハッシュを計算してそれをキャッシュのキーとして使っています。キーの生成方法としてはとても安全なのですが、実はそれが性能上の欠点でもあります。せいぜい数十kbyte程度であろうHTMLテンプレート文字列のSHA-1を計算すること自体は大したコストではないのですが、テンプレートがFileに入っている場合、それを全部String等に読み込むコストが発生してしまうのです。 しかしこれは、Mixer2のテンプレートロード機能は、テンプレートの型としてjava.io.FileのほかInputStreamにもStringにも対応しているので、どれであっても同じようにキャッシュ(のキー)を扱えるようにするための措置でもあります。このあたりは今後、キャッシュキー生成の高速性と安全性と簡易性を両立させるうまい実装が思いつけばどうにかなるかもしれません。次のバージョンアップにご期待ください(いつだ?)。

ところで、キャッシュ機構といえばこんなのもあります。

つまり、DIコンテナが、というか、Javaアプリケーションフレームワーク自体がキャッシュ機構を提供しているケースです。 こういうものとMixer2を組み合わせる場合はmixer2-cacheableなんてほっといてフレームワークに合わせるのもよいでしょう。

そんなわけで、Mixer2のテンプレートロード機能にキャッシュを使いたい場合は、以下の3つのパターンにまとめることができます。

  • mixer2-cacheableをデフォルトのまま使う。数十kbyte程度の小さなテンプレートが数百種類、という程度の要求仕様であれば十分でしょう。
  • mixer2-cacheableを使いつつ、cacheオブジェクトはJCache(JSR-107)を実装した別のものを使う。CacheableMixer2Engine.setCache(別のキャッシュインスタンス) のようにすれば差し替えられるようになっています。(DIコンテナでの設定例はこちら)
  • mixer2-cacheableは使わない。Mixer2Engineを継承した別クラスを独自実装し、そこにキャッシュ機構を組み込む。あるいはアプリケーションフレームワーク提供のキャッシュ機構を経由するようにする。

そして究極の選択は、テンプレートのキャッシュなんて気にしないことです。Mixer2のテンプレートロード機能は結構速いです。 かなり大きなテンプレートであっても一桁ミリ秒で済んでしまいます。ならば、100msec単位のコストがかかりがちなほかの機能(外部通信、DB検索等)をチューニングするほうがよほど性能向上を見込めるでしょう。

Saturday, July 27, 2013

mixer2とSpringMVC用のViewResolver

mixer2-cachableとともにmixer2-1.2.10もリリースしました。

mixer2-cachableやその他のキャッシュ実装との連携をしやすくするための手直しが入っているほか、 もう一つ重要な機能が追加されています。 SpringMVC用のViewの基底クラスと、ViewResolverです。

これまで、SpringMVCでmixer2を使う場合にはControllerクラスの各メソッドでHtmlを組み立ててMixer2XhtmlViewに仕込んで直接返す、つまりメソッドの戻り値の型がMixer2XhtmlView、という方法を推奨していました。去年書いた下記の記事なんか、もろにそれです。

ところが、この方式、コントローラ自体がビューのテクノロジに依存することになってしまうため、ビューの差し替えが難しくなる=MVCの分離が容易というSpringMVCの良さを殺してしまう=ということになります。

SpringMVCのコントローラのメソッドの戻り値は、

  • Viewの名前≒ViewResolverが解釈しうるString値
  • ModelAndviewまたはそれを拡張したクラスのインスタンス(org.springframework.web.servlet.ModelAndView)
  • 独自のView実装(つまりorg.springframework.web.servlet.Viewをimplementsしたもの)のインスタンス
のどれでもかまわないという柔軟性の高さは素晴らしいです。しかしそれに甘えすぎるのもよくないわけです。

そこで登場するのがViewResolverインターフェイス(org.springframework.web.servlet.ViewResolver)です。 これをきちんと実装すると、コントローラはViewではなくViewの名前をStringで返すだけでよくなります。あとはViewResolverにおまかせです。

今回、こういったことをきちんと解決するために、 Mixer2XhtmlViewResolver と AbstractMixer2XhtmlView をそれぞれ追加しました。使い方はそれぞれのjavadocの冒頭にあるとおりです。

また、SpringMVC用のアーキタイプで自動生成されるHelloWorldプロジェクトや、 フルーツショップサンプルアプリケーションも、すでにMixer2XhtmlViewResolverを使った形にリファクタリングされています。合わせて参考にしてください。

mixer2-cacheableをリリースしました。

mixer2-cacheableをリリースしました。 ソースはこちら。 ドキュメントはjavadocくらいしかないので、とりあえずここでざっくり書いときます。

maven使いの方はこんな感じ。 (追記:cache-apiとmixer2本体も別途指定が必要ということを忘れてましたので今朝加筆。) それ以外の方はいつものようにhttp://mixer2.org/dist/にあるアーカイブを使ってください。 mixer2-cacheable-1.0.1-jars.zip にはmixer2本体や依存jarも同梱されています。

単なるMixer2Engineのラッパーなので、使い方は簡単です。 いままでorg.mixer2.Mixer2Engine を使っていた部分を CacheableMixer2Engine を使うようにするだけです。

たとえばSpringMVC上でMixer2EngineをこんなふうにDIしていた場合、
変更前:
変更後:
とするだけです。

エンジンが読み込もう(loadHtmlTemplate/checkAndLoadHtmlTemplate)とするxhtmlテンプレート文字列のSHA-1ハッシュ値が自動的にキャッシュのキーとして使用されます。

デフォルトでは、同梱のorg.mixer2.cacheable.SimpleCacheが使用されます。 これはjavax.cache.Cache(JSR-107)仕様に対するごく簡単な実装になっています。 キャッシュの格納先としてConcurrentHashMapを使っているだけです。

JSR-107準拠のキャッシュ実装に差し替えることももちろん可能です。 たとえばSpringMVCを使っている場合、 これでCachableMixer2Engineに指定のキャッシュ実装のインスタンスがDIされます。

気になる性能ですが、これはhtmlテンプレートのサイズによってだいぶ異なります。 だいだい2倍から4倍程度の性能向上は見込めるでしょう。 ただし、mixer2がテンプレートのロードで頼っているJAXBのunmarshalerそれ自体が結構速いので、 たとえば80msecかかっていた処理が10msecになるみたいな劇的なことにはなりにくいです。 もともと6msec程度で済んでいたテンプレートロードが2msecになるとか、そのくらいの効果ととらえるほうがよいでしょう。

Friday, July 26, 2013

syntac highligher 壊れた

このブログのsyntax highliterのセッティングをおかしくしてしまったみたいです。コードのサンプルを貼り付けた記事がなんかぐちゃぐちゃに。。。

Monday, July 22, 2013

mixer2 version1.2.8をリリースしました

mixer2 1.2.8をリリースしました。 このリリースでは、Mixer2Engineに新たに checkAndLoadHtmlTemplate() メソッドを追加しました。

大きな違いはテンプレートのロードに失敗したときの挙動です。たとえば、 本来なら<br />と書くべき改行タグを<br>と書いてしまったxhtmlをロードしようとすると、

  • 従来のloadHtmlTemplate()は、nullを返す。例外情報はログに出るだけ。 これはテンプレートは正確なものが渡されるという前提の場合に有用です。
  • checkAndLoadHtmlTemplate()は、Mixer2JAXBExceptionをスローする。 これはテンプレートが間違っている可能性も考慮しなければならない場合に有用です。

Mixer2JAXBExceptionは単なるJAXBExceptionのラッパーです。 xhtmlのパースエラーの情報を得やすくするために、いくつかのメソッドを追加してあります。 詳しくはjavadocをご覧ください。