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クラスにもそれらしい記述は無い)

理由は不明。。。