ThymeleafでJava SE 8のDate and Time APIを使う方法

やりたいこと

Thymeleafのビューで、java.time.LocalDateなどJava SE 8で入った日時クラス(Date and Time API)を使いたい。

Date and Time APIについてはこちらをどうぞ。

Java日付時刻APIメモ(Hishidama's Java8 Date and Time API Memo)

(蓮沼さんの資料は何故か見つからなかった・・・)

Thymeleafの拡張機能を追加

Thymeleafには、Date and Time APIを利用するための拡張機能があります。

https://github.com/thymeleaf/thymeleaf-extras-java8time

本体に入っていないのは恐らく、Thymeleaf本体はJava SE 6でコンパイルされているからだと思われます。

thymeleaf/CONTRIBUTING.markdown at 3.0-master · thymeleaf/thymeleaf · GitHub

pom.xmlに依存性を追加します。

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    <version>3.0.0.RELEASE</version>
    <scope>compile</scope>
</dependency>

Dialectの追加

TemplateEngineをnewしている場所で、Java8TimeDialectを追加します。

templateEngine = new TemplateEngine();
templateEngine.addDialect(new Java8TimeDialect());

ビューからの利用

こんなクラスがあって、

public class Employee {
    ...
    private java.time.LocalDate joinedDate;
    // getter/setter
}

ThymeleafでjoinedDateをフォーマットして表示するには、下記のようにします。

<tr th:object="${employee}">
    ...
    <td th:text="*{#temporals.format(joinedDate, 'yyyy-MM-dd')}">2020-01-01</td>

簡単ですね!

その他の機能

こちらのREADMEに、一通りの機能が説明されています。

https://github.com/thymeleaf/thymeleaf-extras-java8time

JJUG CCC 2016 SpringでアクションベースMVCの発表してきた&楽しんできた #jjug_ccc

発表してきた

「ネクストStruts/Seasar2としてのJava EEアクションベースMVC入門」というタイトルで発表してきました。

165人の部屋が、ほぼ満席となるくらいの方々にお越しいただきました。ありがとうございます。

やっぱり、StrutsSeasar2からの移行をどうするかは、皆さん喫緊の課題だと思います。

内容はJava EE 8のMVC 1.0、そしてEE 7における代替としてJersey MVCとRESTEasy HTMLです。

ビューは頑張ってThymeleafをリリースされたばかりの「3」にしたり、

クライアント側のロケールに合わせて画面のメッセージや検証エラーメッセージを国際化したりしました。

MVC 1.0のサンプルのみ、検証エラーメッセージの国際化には対応していません。現在、MVC 1.0の仕様策定で検討中のためです。

資料

コード

GitHub - MasatoshiTada/jjug-action-based-mvc: JJUG CCC 2016 Spring「ネクストStruts/Searar2としてのJava EEアクションベースMVC入門」のサンプル

楽しんできた

テスト自動化のまわりみち(@irofさん)

テスト自動化などの前に、テスト技法なりテスト項目書の書き方なり、基本的なことをきちんとやりましょう、ということだと理解しました。

例えば、「◯◯が正しいこと」ではなく「◯◯の値が『10』になっていること」と書くとか。

「正しいこと」なんて、そりゃJUnitで表現できませんよね・・・。

テスト技法の研修などもやったりする身としては、身が引き締まる思いでした。

Thymeleaf 3を使ってみよう!(@bufferingsさん)

Thymeleaf公式ドキュメントの和訳も出されている椎葉さんのセッションでした。

アクションベースMVCのサンプルを作る時に一通りドキュメントを読んだので、概ね知っている内容が多かったのですが、

初めて知ったことや、いまひとつ理解できていなかったことが理解できたことも多かったです。

さらに、椎葉さんは実業務でもThymeleafを使っていらっしゃるということで、デザイナーさんとの協業の話も非常に参考になりました。

Spring BootでBootした後に作るWebアプリケーション基盤(エムスリー吉田さん)

Spring Boot + Spring MVCの実用的な使い方のお話でした。

特に例外処理やロギングなどのお話が印象的でした。スライドはアップされないのかな?

Spring Framework/Bootの最新情報とPivotalが進めるクラウドネイティブなアプローチ(@makingさん)

最初に、Spring Boot 1.4およびSpring Framework 4.3の新機能のお話でした。

テスト関連の機能が特に印象的でした。テストしやすいフレームワークっていいよなあ・・・。Java EEにも頑張ってほしい。

後半は、Cloud FoundryやConcourse CIなどの紹介をしつつ、「クラウドネイティブ」なアプリケーションとは何か、

それをCloud Foundryではどう実現しているか、というお話でした。

以前にCloud Foundryワークショップにも参加したので、もっと触れていきたいなあと思います。

Seasar2で作った俺たちのサービスの今(@jukutyoさん)

Seasar2(Cubby + S2Dao)からSpring MVC + Domaへ移行するお話でした。

段階的に移行していて、今は同一WARファイルの中にSpringとSeasar2が同居している状態だそうです。

確かに、一度にまるっと移行することは、人的・時間的リソースや、サービスを維持・発展させなければならない、

といった制約もあって現実的でないこともあると思いますので、こういった実例を知れたのは非常に良かったです。

余談ですが、じゅくちょーさんは元塾講師ということで、塾講師っぽい喋り方が印象的でした。

僕も学生時代は塾講師のバイトをずっとしていたので、なんだか懐かしい気持ちでしたw

マイクロフレームワークenkan(とkotowari)ではじめるREPL駆動開発(@kawasimaさん)

システムエンジニアアドベントカレンダーで世の中を驚かせた、川島さんの発表でした。

川島さんとしては色々な問題意識をお持ちで、このenkanを作るに至ったそうです。

確かに、XMLアノテーションになっても、設定が面倒なのはあるし、CoCと分かりやすさはトレードオフです。

最後の方はチュートリアルをやってみる時間がありましたので、やってみました。

最初にMavenでのJARのダウンロード祭りがありますが、それでも5分くらいあれば、このチュートリアルは終えられました。

運営について

今回から昼休みが90分になったり、午後にも2セッションごとに40分の休憩があったり、

懇親会の開始時間が早まったり、新たな取り組みがありました。

特に午後の長めの休憩は、非常に嬉しかったです。

10分休憩だけで午後に6セッションとか7セッションとか続くと、かなり体力的にキツかったですし、

休憩時間が短いとセッション間の移動も混雑して大変でした。

所々に長めの休憩があることで、体力も回復できるし、余裕を持って移動できるし、

いろんな方とお話しすることもできるし、個人的にはいいことずくめでした。

幹事やボランティアスタッフの皆様、ありがとうございました!

おまけ

このタイムテーブル、とても役に立ちました!

@YujiSoftwareさん、ありがとうございます!

http://yujisoftware.github.io/jjug-ccc/2016-Spring/

EclipseLinkとHibernateではTemporalType.DATEなフィールドの型が違う

かなり久々のJPAネタ。

こんなエンティティクラスがあって、

@Entity
public class Employee implements Serializable {
    @Id
    @Column(name = "emp_id")
    private Integer empId;
    
    @Temporal(TemporalType.DATE)
    @Column(name = "joined_date")
    private java.util.Date joinedDate;
    

joinedDateというフィールドはjava.util.Date型、そして@Temporal(TemporalType.DATE)が付いています。

これをEclipseLinkで実行すると、joinedDateにはjava.util.Date型のインスタンスが代入される。

一方、Hibernateで実行すると、joinedDateにはjava.sql.Date型のインスタンスが代入される。(java.sql.Datejava.util.Dateのサブクラスです)

Webアプリのビューでそのまま表示すると、Payara(JPA実装がEclipseLink内包)だと「Wed Apr 01 00:00:00 JST 2015」という形式なのに、

WildFly(JPA実装がHibernate)だと「2015-04-01」という形式になったので、アレ?と思って、

employee.getJoinedDate().getClass().getName()してクラス名を表示したらこんな感じになってました。

jug-action-based-mvcプロジェクトにテストコードを追加しました。

EmployeeDaoのテストコードを追加 · MasatoshiTada/jjug-action-based-mvc@2edd12b · GitHub

Payara Microの実行可能JAR(Uber JAR)がとても簡単な件

先日、Payara 4.1.1.162がリリースされました!

その組み込みサーバー版であるPayara Micro 4.1.1.162には、「Uber JAR」というSpring BootやWildFly Swarmのような単体で実行可能なJARを作る機能が追加されました。

Payara 4.1.1.162 がリリースされました - GlassFish Japan

従来のPayara Microでは、一旦アプリケーションを普通にWARにして、Payara Micro起動→WARをデプロイする必要がありました。

今回の新機能により、本当に単体で実行可能なJARを作れるように

手順は蓮沼さんのブログに書かれてある通りなのですが、自分でもやってみてあまりに簡単で感動したので、このブログにも書くことにしました。

手順

普通にWebアプリケーションを作る

本当に普通に作ってください。pom.xmlのpackageは「war」です。

組み込みじゃない据え置きのGlassFish/Payaraにデプロイするアプリケーションと全く同じ形で作ってください。

下記は、今月のJJUG CCC 2016 Springで紹介する予定の、MVC 1.0のサンプルアプリケーションです。

アプリケーション側は全く変えることなく、据え置きサーバー・実行可能JARの両方に対応できます。

jjug-action-based-mvc/jjug-mvc10 at master · MasatoshiTada/jjug-action-based-mvc · GitHub

そして、MavenでWARファイルにビルドします。

cd jug-mvc10
mvn clean package

jjug-mvc10/targetフォルダにjjug-mvc10-1.0-SNAPSHOT.warが作成されます。

実行可能JARを作成する

上記のWARファイルを元に、Payara Microで実行可能JARを作ります。

あらかじめ、Payara Micro 4.1.1.162のJARは下記からダウンロードし、適当なフォルダに保存してください。

Payara Server & Payara Micro - Downloads

そして、次のコマンドを実行します。

java -jar ~/Java/ap-server/payara-micro-4.1.1.162.jar\
 --deploy target/jjug-mvc10-1.0-SNAPSHOT.war\
 --outputUberJar target/jjug-mvc10.jar

--outputUberJarというオプションがポイントです。

こうすると、targetフォルダにjjug-mvc10.jarというJARファイルが作成されます。これが実行可能JARです。

ちなみに、このオプションがない場合、これまでのPayara Microと同じ挙動になります。

-jar の後は先ほどダウンロードしたPayara MicroのJARファイルのパス、--deployの後はWARファイルのパスです。

ちなみに、jar tf target/jjug-mvc10.jarとすると、実行可能JARの中身を確認することができます。

とても長いので全部は載せませんが、Payara Microの中身が解凍・再パッケージされていることが確認できます。

META-INF/services/javax.enterprise.inject.spi.CDIProvider
META-INF/services/org.glassfish.tyrus.core.ComponentProvider
META-INF/services/javax.management.remote.JMXConnectorProvider
(中略)
META-INF/
META-INF/MANIFEST.MF
__cp_jdbc_ra.rar
__dm_jdbc_ra.rar
__ds_jdbc_ra.rar
__xa_jdbc_ra.rar
META-INF/configuration/
META-INF/hk2-locator/
META-INF/maven/
META-INF/maven/org.glassfish.main.concurrent/
META-INF/maven/org.glassfish.main.concurrent/concurrent-connector/
org/
org/glassfish/
org/glassfish/concurrent/
org/glassfish/concurrent/config/
META-INF/configuration/context-service-conf.xml
META-INF/configuration/managed-executor-service-conf.xml
META-INF/configuration/managed-scheduled-executor-service-conf.xml
META-INF/configuration/managed-thread-factory-conf.xml
META-INF/maven/org.glassfish.main.concurrent/concurrent-connector/pom.properties
META-INF/maven/org.glassfish.main.concurrent/concurrent-connector/pom.xml
org/glassfish/concurrent/config/ConcurrencyResource.class
org/glassfish/concurrent/config/ContextService$ContextServiceConfigActivator.class
org/glassfish/concurrent/config/ContextService$Duck.class
org/glassfish/concurrent/config/ContextService.class
org/glassfish/concurrent/config/ContextServiceInjector.class
org/glassfish/concurrent/config/LocalStrings.properties
(以下略)

一番最後の方に、自分で作ったWARファイルが見えます。

...
META-INF/deploy/
META-INF/deploy/jjug-jersey-mvc-1.0-SNAPSHOT.war
META-INF/deploy/payaramicro.properties

META-INF/deployに直接WARファイルが入ってるんですね!

実行する

java -jar target/jjug-mvc10.jar

これで実行できます。

後はブラウザで開いてださい。

http://localhost:8080/jjug-mvc10-1.0-SNAPSHOT/

ちなみに、僕はここまでの処理は1つのシェルスクリプトにまとめて、実行する時はそれを叩くだけにしています。

jjug-action-based-mvc/build-jar.sh at master · MasatoshiTada/jjug-action-based-mvc · GitHub

まとめ

WARに固めてから実行可能JARにする、というのが少し違和感があるかもしれませんが、アプリケーション側に何も変更なく実行可能JARが出来るというのは非常に面白いと感じました。

ぜひご自分でも試してみてください!

Jersey MVCでThymeleafを使おう!

この記事について

JJUG CCC 2016 Spring「ネクストStruts/Seasr2としてのJava EEアクションベースMVC入門」の補足記事です。

Jersey MVCで、JSPの代わりのビューとしてThymeleafを使う方法を解説します。

Jersey MVCの基本については、下記の記事を参照してください。

Java EE 7でもアクションベースMVC! -MVC 1.0への移行を睨んだJersey MVCの活用- #javaee - Java EE 事始め!

環境

  • Payara Web ML 4.1.1.162
  • Thymeleaf 2.1.4.RELEASE

ソースコード

jjug-action-based-mvc/jjug-jersey-mvc at master · MasatoshiTada/jjug-action-based-mvc · GitHub

TemplateProcessorクラスを作る

Jersey MVCで公式にサポートされているビューはJSP/Freemarker/Mustacheの3種類です。

それ以外のビューを使うには、Jersey MVCで提供されているTemplateProcessorインタフェースを実装する必要があります。

Jersey MVCには、TemplateProcessorを実装したAbstractTemplateProcessorという抽象クラスがありますので、これを継承して作成します。

クラスの作成、リクエストとレスポンスの取得

@Provider
public class ThymeleafTemplateProcessor extends AbstractTemplateProcessor<String> {

    @Inject
    private javax.inject.Provider<Ref<HttpServletRequest>> requestProviderRef;
    @Inject
    private javax.inject.Provider<Ref<HttpServletResponse>> responseProviderRef;
    // 後に続く

まず、AbstractTemplateProcessorを継承し、クラスには@Providerを付加します。

次に、フィールドインジェクションでHttpServletRequestHttpServletResponseを取得するのですが、@Providerが付加されたクラスはJAX-RSの仕様では、デフォルトでシングルトンと定められています。

なので、直接HttpServletRequestHttpServletResponseをインジェクションすると、スレッドセーフでない可能性があると考えました。

僕はスレッドセーフか否かといったあたりはあまり詳しくないので、直接取得でも大丈夫な可能性もありますが・・・。

そこで、Jersey MVCJSPTemplateProcessorである下記のクラスを参考に作りました。

jersey/JspTemplateProcessor.java at 2.x · jersey/jersey · GitHub

これで本当にスレッドセーフになっているのかは、僕には判断がつかないので、この点はご自分で判断してください。

TemplateEngineの作成

    // 続き
    private TemplateEngine templateEngine;

    @Inject
    public ThymeleafTemplateProcessor(Configuration config, ServletContext servletContext) {
        super(config, servletContext, "html", "html");
        TemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix((String) config.getProperty(MvcFeature.TEMPLATE_BASE_PATH));
        // setSuffix()は指定しない
        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
    }
    // 後に続く

フィールドにTemplateEngine(Thymeleafのクラス)を宣言し、コンストラクタ内で初期化します。

ポイントは、setSuffix()という、通常は拡張子を指定するメソッドを呼んでいないことです。

これは、MVC 1.0との互換性のために、コントローラーメソッド拡張子を指定したいからです。

メソッドのオーバーライド

    // 続き
    @Override
    protected String resolve(String templatePath, Reader reader) throws Exception {
        return templatePath;
    }

    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {
        HttpServletRequest httpServletRequest = requestProviderRef.get().get();
        HttpServletResponse httpServletResponse = responseProviderRef.get().get();
        httpServletResponse.setCharacterEncoding("UTF-8");
        WebContext webContext = new WebContext(
                httpServletRequest, httpServletResponse, 
                super.getServletContext(), httpServletRequest.getLocale());
        Object model = viewable.getModel();
        if (model instanceof Map) {
            Map<String, Object> variables = (Map<String, Object>) model;
            webContext.setVariables(variables);
        } else {
            Map<String, Object> variables = new HashMap<>();
            variables.put("model", model);
            webContext.setVariables(variables);
        }
        try (Writer writer = new OutputStreamWriter(out)) {
            templateEngine.process(viewable.getTemplateName(), webContext, writer);
        }
    }
}

resolve()は、引数のtemplatePathをそのまま返せばOKです。

writeTo()が、最終的にレスポンスを書き込むメソッドです。

webContext.setVariables()は、ビューに渡す値をマップ形式で指定します。

コントローラーメソッドViewableに渡された値は、viewable.getModel()で取得できます。

この値がMapだった場合は、それを直接webContext.setVariables()に渡し、そうでない場合はJersey MVCデフォルトのmodelという名前でMapにputしています。

こうすることで、ビューから値を参照するときに、${model.employee.id}のようにmodelを付けなくてもいいようにしています。

writeTo()の最後では、templateEngine.process()を呼んでいます。これが、Thymeleafのレスポンスを書き出しています。

自作Viewableクラスの作成

これはThymeleafを使う上では必須ではないのですが、MVC 1.0との差異をなるべく吸収するために作りました。

前述のブログの通り、Jersey MVCには「/」で始める絶対パスと「/」で始めない相対パスがあり、それぞれビューの保存フォルダが異なります。

ビューの保存フォルダがMVC 1.0とほぼ同じなのは絶対パスですが、MVC 1.0(というか現在のOzark)では「/」で始めるとコンテキストルートからのパスになってしまいます。

なので、「/」で始めない相対パスで指定しつつ、かつビューの保存フォルダが絶対パスになるように、Viewableのサブクラスを自作しました。

public class ThymeleafViewable extends Viewable {

    public ThymeleafViewable(String templateName) throws IllegalArgumentException {
        super(templateName);
    }

    public ThymeleafViewable(String templateName, Map<String, Object> models) throws IllegalArgumentException {
        super(templateName, models);
    }

    @Override
    public Map<String, Object> getModel() {
        return (Map<String, Object>) super.getModel();
    }

    @Override
    public String getTemplateName() {
        String templateName = super.getTemplateName();
        if (templateName.startsWith("/") == false) {
            return "/" + templateName;
        } else {
            return templateName;
        }
    }

    @Override
    public boolean isTemplateNameAbsolute() {
        return true;
    }
}

コードを読んでいただければ大体わかると思いますが、getTemplateName()が必ずスラッシュで始まるパス(=絶対パス)を返すようにし、かつisTemplateNameAbsolute()trueを返して、Jersey MVC絶対パスと認識させます。

また、コンストラクタはビューのパスとMapを引数で受け取るようにします。

コントローラーメソッド

    @GET
    @Path("index")
    public ThymeleafViewable index() throws Exception {
        return new ThymeleafViewable("employee/index.html");
    }
    @GET
    @Path("result")
    public ThymeleafViewable result(@BeanParam EmployeeIdForm form) throws Exception {
        Integer id = Integer.valueOf(form.getId());
        Employee employee = employeeService.findByEmpId(id).orElse(null);
        HashMap<String, Object> models = new HashMap<>();
        models.put("employee", employee);
        return new ThymeleafViewable("employee/result.html", models);
    }

パスは「/」で始めない、MVC 1.0と同じ形式にしています。

また、拡張子「.html」を明示的に指定しています。これもMVC 1.0と同じです。

ビュー

<p th:if="${employee == null}">該当する社員はいませんでした。</p>
<table border="1" th:unless="${employee == null}">
    <tr><th>社員ID</th><th>氏名</th><th>入社年月日</th><th>部署ID</th><th>部署名</th></tr>
    <tr th:object="${employee}">
        <td th:text="*{empId}">99999</td>
        <td th:text="*{name}">Taro Yamada</td>
        <td th:text="*{joinedDate}">2020-01-01</td>
        <td th:text="*{department.deptId}">99</td>
        <td th:text="*{department.name}">Admin</td>
    </tr>
</table>

modelを指定せず、直接employeeで値を参照していることが分かります。

これにより、Jersey MVCからMVC 1.0に変更した際に、ビューを全く変更しなくて済むようにしています。

まとめ

TemplateProcessorと自作Viewableにより、極力MVC 1.0と同じ感覚で、ビューとコントローラーを実装できるようにしました。

参考にしたWebサイト

@bufferingsさんのブログ。

jersey-thymeleaf using ViewProcessor - Mitsuyuki.Shiiba

@backpaper0さんのコード。

sealion/ThymeleafProvider.java at master · backpaper0/sealion · GitHub

Jersey MVC + Thymeleafしたら文字化けた時の対策

環境

  • OS X 10.11.4
  • Google Chrome
  • Payara Web ML 4.1.1.161.1 (Jersey 2.22.1)
  • Thymeleaf 2.1.4.RELEASE

プログラム概要

@bufferingさんの記事を参考に、Thymeleaf用のTemplateProcessorを作成。

@Provider
public class ThymeleafTemplateProcessor extends AbstractTemplateProcessor<String> {

    @Inject
    private javax.inject.Provider<Ref<HttpServletRequest>> requestProviderRef;
    @Inject
    private javax.inject.Provider<Ref<HttpServletResponse>> responseProviderRef;

    private TemplateEngine templateEngine;

    @Inject
    public ThymeleafTemplateProcessor(Configuration config, ServletContext servletContext) {
        super(config, servletContext, "html", "html");
        TemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix((String)config.getProperty(MvcFeature.TEMPLATE_BASE_PATH));
        templateResolver.setSuffix(".html");
        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
    }

    @Override
    protected String resolve(String templatePath, Reader reader) throws Exception {
        return templatePath;
    }

    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {
        HttpServletRequest httpServletRequest = requestProviderRef.get().get();
        HttpServletResponse httpServletResponse = responseProviderRef.get().get();
        WebContext webContext = new WebContext(
                httpServletRequest, httpServletResponse, 
                super.getServletContext(), httpServletRequest.getLocale());
        Object model = viewable.getModel();
        if (model instanceof Map) {
            Map<String, Object> map = (Map) model;
            webContext.setVariables(map);
        } else {
            Map<String, Object> variables = new HashMap<>();
            variables.put("model", viewable.getModel());
            webContext.setVariables(variables);
        }
        templateEngine.process(viewable.getTemplateName(), webContext, 
                httpServletResponse.getWriter());
    }

}

リソースクラス。

@Path("employee")
@RequestScoped
@Produces(MediaType.TEXT_HTML)
public class EmployeeResource {
    
    @GET
    @Path("index")
    public Viewable index() throws Exception {
        return new Viewable("/employee/index");
    }
}

Thymeleafのビュー。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>入力画面</title>
    <link rel="stylesheet" href="../../css/style.css"/>
</head>
<body>
<p>社員IDを入力してください(77, 88, 99で例外発生)</p>

<!-- 検証エラーメッセージの表示 -->
<ul class="error" th:if="${violations != null}">
    <li th:each="violation : ${violations}" th:text="${violation.message}">ダミーのメッセージ</li>
</ul>

<form action="./result" method="get">
    社員ID:<input type="text" name="id" value="" th:value="${param.id == null} ? '' : ${param.id[0]}"/>
    <input type="submit" value="検索"/>
</form>
</body>
</html>

現象

画面の全日本語メッセージが文字化けている。

f:id:MasatoshiTada:20160501145927p:plain

原因と対策

レスポンスヘッダーやブラウザが現在開いている文字コードを見ると、正しくUTF-8になっていた。

よって、そもそもレスポンスを書き込む際に文字コードが正しく設定されていないっぽい。

TemplateProcessorにhttpServletResponse.setCharacterEncoding("UTF-8");を追加したら、正しく表示された。

f:id:MasatoshiTada:20160501150201p:plain

@Provider
public class ThymeleafTemplateProcessor extends AbstractTemplateProcessor<String> {

    // 中略
    
    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {
        HttpServletRequest httpServletRequest = requestProviderRef.get().get();
        HttpServletResponse httpServletResponse = responseProviderRef.get().get();

        // これを追加
        httpServletResponse.setCharacterEncoding("UTF-8");

        WebContext webContext = new WebContext(
                httpServletRequest, httpServletResponse, 
                super.getServletContext(), httpServletRequest.getLocale());
        Object model = viewable.getModel();
        if (model instanceof Map) {
            Map<String, Object> map = (Map) model;
            webContext.setVariables(map);
        } else {
            Map<String, Object> variables = new HashMap<>();
            variables.put("model", viewable.getModel());
            webContext.setVariables(variables);
        }
        templateEngine.process(viewable.getTemplateName(), webContext, 
                httpServletResponse.getWriter());
    }

}

ちなみに

Ozark + Thymeleafだと、この記述が無くても正しく表示された。

(ViewEngineクラスにもそれらしい記述は無い)

理由は不明。。。

Cloud Foundryワークショップに参加してきました! #cfws

2016-04-05(火)に開催された、Pivotalさん主催のCloud Foundryワークショップに参加してきました!

クラウド関連のことはあまり知識がないので、クラウドの世界ってどんなものなんだろう?ということ知りたかったというのが動機です。

講師は@makingさん。冒頭は、3月のJJUGナイトセミナーの資料でCloud Foundryの概要説明でした。

理解できた範囲で要約すると、

  • IaaS上にPaaSを構築・管理するためのソフトウェア群
  • Pivotal社単独ではなく、「Cloud Foundry Foundation」という団体が仕様を策定している
  • Pivotal Web Servicesは、AWS上にCloud FoundryでPaaSを構築済みのもの。アカウントを作成すればすぐに利用できる
  • PCF Devは、ローカルPC上にCloud Foundry環境を構築するもの
  • Cloud Foundry上へのアプリケーションのデプロイなどは、CLIツールをインストールしてcfコマンドで行う

その他、Cloud Foundry内部の仕組みや、Blue-Green Deployなどの説明もありました。 (なんか理解が間違ってたらツッコミください・・・)

その後、下記の資料でハンズオン。

GitHub - Pivotal-Japan/cf-workshop: Cloud Foundry Workshop

Cloud Foundryワークショップ資料 - Qiita

(上記のGitHubとQiitaは、両方同じ資料です)

特に印象深かったのは、「5. スケールアウト」と「7. Blue-Greenデプロイ」です。

例えば、より多くのクライアントに対応するためにインスタンス数を増やして負荷分散したいという場合は、ローカルPCから下記のコマンドを叩くだけ。

$ cf scale -i 4 hello-redis-tada

上記だとインスタンス数が4になります。かかった時間も、ものの数秒でした。で、ロードバランシングとかはCloud Foundry内のRouterがよろしくやってくれる、と。

Blue-Greenデプロイは、名前だけは聞いたことがあったものの、どんなものかはよく知りませんでした。

新しいバージョンのアプリケーションをデプロイする際に、ダウン時間をなるべくゼロに近づけるための手法だと理解しました。

これも、Cloud Foundryを使えば非常に簡単ですね。

クラウドって実際に触ったことがない」という人にはとてもオススメのワークショップです!

これから何回か開催予定ということなので、是非参加してみてはいかがでしょうか。

ハンズオンはボリュームがそこそこありますので、当日は余裕を持って話を聞きたいという方は「3. 簡単なアプリケーションをデプロイ」くらいまでは事前に予習しておくといいかもしれません。