Tomcat における JSP の 65535 問題の解決法

65535 問題とは、巨大な JSP ファイルからできた .java ファイルがコンパイルされるときにでるアレです。

具体的には次のエラーが出ます

An error occurred at line: [79] in the generated java file: [filepath]
The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit

御託はいらない人向け

以下御託

何が原因か

JVM の仕様に 1 メソッド 64k までという制限があります。 また、JSP ファイルに記載された内容のすべては .java ファイルに変換される際に _jspService メソッドに集約されます。 JSP にロジックが含まれていたり、コンポーネント化が不十分であると、_jspService が膨れ上がってしまいます。

対策

リファクタリング

素直にリファクタリングしましょう。 ロジックは JSP に含めないようにしたり、ひとつの JSP に詰め込むのではなくコンポーネント化すべきです。

Tomcat の設定

根本的な解決とはならないのでオススメはしませんが、一時的な対応策として紹介しておきます。

Tomcat では JSPJava に変換する JspServlet に web.xml でパラメータを設定できます。 _jspService メソッドを小さくするようなパラメータは以下の3つ。

  • genStringAsCharArray (default : false)
  • mappedfile (default : true)
  • trimSpaces (default : false)

web.xml に上記パラメータを次のように設定します。

    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>genStringAsCharArray</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>mappedfile</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>trimSpaces</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>

これらを設定した場合に、以下の JSP_jspService メソッドがどのように変換されるかみていきます。

<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<ul>
<%
  for(int i = 0; i < 5; i++) {
    int j = i * 2;
%>
  <li><%= j %></li>
<% } %>
</ul>
</body>
</html>

初期設定では以下のように変換されます。

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
// 中略
      out.write("\r\n");
      out.write("<!DOCTYPE html>\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<meta charset=\"utf-8\">\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("<ul>\r\n");

  for(int i = 0; i < 5; i++) {
    int j = i * 2;

      out.write("\r\n");
      out.write("  <li>");
      out.print( j );
      out.write("</li>\r\n");
 } 
      out.write("\r\n");
      out.write("</ul>\r\n");
      out.write("</body>\r\n");
      out.write("</html>");
// 中略
  }
genStringAsCharArray

out.write() に渡される文字リテラル_jspService の外で static char[] で宣言され、それを参照するようになります。

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
// 中略 
      out.write(_jspx_char_array_0);

  for(int i = 0; i < 5; i++) {
    int j = i * 2;

      out.write(_jspx_char_array_1);
      out.print( j );
      out.write(_jspx_char_array_2);
 } 
      out.write(_jspx_char_array_3);
// 中略
  }
  static char[] _jspx_char_array_0 = "\r\n<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta charset=\"utf-8\">\r\n</head>\r\n<body>\r\n<ul>\r\n".toCharArray();
  static char[] _jspx_char_array_1 = "\r\n  <li>".toCharArray();
  static char[] _jspx_char_array_2 = "</li>\r\n".toCharArray();
  static char[] _jspx_char_array_3 = "\r\n</ul>\r\n</body>\r\n</html>".toCharArray();
mappedfile

JSP の 1 行をひとつの out.write に変換していたものが、スクリプトレットが出現するまでをひとつの out.write に変換するようになります。

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
// 中略 
      out.write("\r\n<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta charset=\"utf-8\">\r\n</head>\r\n<body>\r\n<ul>\r\n");

  for(int i = 0; i < 5; i++) {
    int j = i * 2;

      out.write("\r\n  <li>");
      out.print( j );
      out.write("</li>\r\n");
 } 
      out.write("\r\n</ul>\r\n</body>\r\n</html>");
// 中略 
  }
trimSpaces

余分なタブ、スペース、改行が除かれるように変換されます。

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
// 中略 
      out.write("<!DOCTYPE html>\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<meta charset=\"utf-8\">\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("<ul>\r\n");

  for(int i = 0; i < 5; i++) {
    int j = i * 2;

      out.write("<li>");
      out.print( j );
      out.write("</li>\r\n");
 } 
      out.write("</ul>\r\n");
      out.write("</body>\r\n");
      out.write("</html>");
// 中略 
  }
全部のせ

単純にこれらすべてを指定するのが最強というわけでもないです。

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
// 中略 
      out.write(_jspx_char_array_0);

  for(int i = 0; i < 5; i++) {
    int j = i * 2;

      out.write(_jspx_char_array_1);
      out.print( j );
      out.write(_jspx_char_array_2);
 } 
      out.write(_jspx_char_array_3);
// 中略 
  }
  static char[] _jspx_char_array_0 = "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta charset=\"utf-8\">\r\n</head>\r\n<body>\r\n<ul>\r\n".toCharArray();
  static char[] _jspx_char_array_1 = "<li>".toCharArray();
  static char[] _jspx_char_array_2 = "</li>\r\n".toCharArray();
  static char[] _jspx_char_array_3 = "</ul>\r\n</body>\r\n</html>".toCharArray();

genStringAsCharArraymappedfile と同じように文字リテラルをまとめるので、genStringAsCharArraytrimSpaces を指定すればよいです。

とりあえずエラーをでなくしたければ genStringAsCharArray だけでよいです。

まとめ

実は JspServlet のパラメータがこの記事のメインです。 調べ方が悪かったのか JspServlet のパラメータに関する日本語の情報が見つからなかったのと、web.xml に記載されてる英語の説明では具体的なイメージがつかめなかったので、実際に動かして確認してみた次第です。

他にもいっぱいパラメータがあるのでまた調べるかもしれないです。 必要に迫られない限りやらないと思いますが...

あと、同じ JSP でも WebSphere では同様のエラーは発生しなかったりするのでそこらへんどうなってるのでしょうかね。