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 では JSP を Java に変換する 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();
genStringAsCharArray
が mappedfile
と同じように文字リテラルをまとめるので、genStringAsCharArray
と trimSpaces
を指定すればよいです。
とりあえずエラーをでなくしたければ genStringAsCharArray
だけでよいです。
まとめ
実は JspServlet のパラメータがこの記事のメインです。 調べ方が悪かったのか JspServlet のパラメータに関する日本語の情報が見つからなかったのと、web.xml に記載されてる英語の説明では具体的なイメージがつかめなかったので、実際に動かして確認してみた次第です。
他にもいっぱいパラメータがあるのでまた調べるかもしれないです。 必要に迫られない限りやらないと思いますが...
あと、同じ JSP でも WebSphere では同様のエラーは発生しなかったりするのでそこらへんどうなってるのでしょうかね。