jspcは、jspファイルのコンパイル(javaソースの生成)を行うオプションタスク。
(org.apache.tools.ant.taskdefs.optional.jsp.JspC extends
MatchingTask)
Antタスクを使って事前にコンパイルしておくことで、JSPのコンパイルエラーが実行前にチェックできる。
実体はorg.apacheのjasperなので、Tomcat系列(TomcatやJBoss)向け。 WebLogicではwlappcやwljspcを使う。
|
||
|
属性 | 説明 |
---|---|
destdir | 生成されたjavaソースを置くディレクトリー |
srcdir | jspファイルが置いてあるディレクトリー |
compiler | コンパイラーの種類。Ant1.6.5の場合、「jasper」または「jasper41」が指定できる |
タグ | 説明 | 備考 |
---|---|---|
<webapp> | ウェブアプリケーション全体をコンパイルしたい場合に指定する | ここで指定したディレクトリー内にはWEB-INFが必須 |
webappsというディレクトリーの下にあるjspファイルをコンパイルしてtemp/jspcというディレクトリに置きたいつもり。
<?xml version="1.0" encoding="Shift_JIS"?> <project name="jspc" default="jspc" basedir=".." > <target name="jspc"> <jspc destdir="temp/jspc" srcdir="webapps"> <include name="**/*.jsp" /> </jspc> </target> </project>
これを実行すると、以下のような例外が発生する。
[jasperc] java.lang.NoClassDefFoundError: org/apache/jasper/JspC [jasperc] Exception in thread "main" BUILD FAILED
どうやらjspcというタスクはjaspercを呼ぶための単なるラッパーらしく、実行にはjasper本体である「org.apache.jasper.JspC」というクラスが必要。
これはTomcatやJBossをインストールすると入っているので、そのパスをjspcに指定してやる。
(ここではjasper.class.pathという名前にした)
Tomcat5.5の場合、本体はTOMCAT_HOME/common/lib/jasper-compiler.jarに入っているようだ。
その他にもloggerを使っていたりするので、必要そうなライブラリーを一通りパスに入れる。
<property name="tomcat.home" location="C:\apache-tomcat-5.5.23" /> <path id="jasper.class.path"> <path path="${java.class.path}" /> <fileset dir="${tomcat.home}/common/lib"> <!-- jasper-compiler.jar --> <include name="*.jar" /> </fileset> <fileset dir="${tomcat.home}/server/lib"> <include name="*.jar" /> </fileset> <fileset dir="${tomcat.home}/bin"> <!-- LogFactory --> <include name="*.jar" /> </fileset> </path>
JBoss4.2.3の場合、JBOSS_HOME/server/default/deploy/jboss-web.deployer/jbossweb.jarに入っているようだ。
しかし他のクラスにも依存しているらしく、別のライブラリーも含めてやらないと使えない。
<property name="jboss.home" location="C:/jboss/jboss-4.2.3.GA" /> <path id="jasper.class.path"> <path path="${java.class.path}" /> <fileset dir="${jboss.home}/server/default/deploy/jboss-web.deployer"> <include name="jbossweb.jar" /> </fileset> <fileset dir="${jboss.home}/server/default/deploy/management"> <include name="**/applet.jar" /> </fileset> <fileset dir="${jboss.home}/server/default/lib"> <include name="**/*.jar" /> </fileset> </path>
<target name="jspc"> <jspc destdir="temp/jspc" srcdir="webapps" classpathref="jasper.class.path"> <include name="**/*.jsp" /> </jspc> </target>
↓実行結果
[jasperc] org.apache.jasper.JasperException: Unrecognized option: -v0. Use -help for help.
BUILD SUCCESSFUL
「SUCCESSFUL」と表示されているが、実際はjasperから例外が発生しているので、何も上手くいっていない!
-vというのはverboseのレベルを表すjasperのオプションらしく、jspcのverbose(デフォルトは0)を変えてやると値が変わる。
<jspc destdir="temp/jspc" srcdir="webapps" verbose="1" classpathref="jasper.class.path">
<include name="**/*.jsp" />
</jspc>
↓
[jasperc] org.apache.jasper.JasperException: Unrecognized option: -v1. Use -help for help.
いずれにしてもエラーになる理由は、どうやらTomcat5では「-v」というオプションは引数(値)を持たないらしく、「-v0」とかだと誤ったオプションとして認識される為らしい。
一応Ant1.6.5でもTomcat5系のときは-vを使わないようなロジックになってはいるようなのだが…
その判定方法は、「org.apache.jasper.tagplugins.jstl.If
」というクラスが存在しているかどうか。存在してればTomcat5系と認識して-vを付けないようになっている。
しかしTomcat5.5では「org.apache.jasper.tagplugins.jstl.core.If
」というクラスに変わったらしいんだなー。だからTomcat5系と判断されず、-vを使おうとしてしまう。
こりゃAntがバージョンアップするまでどうしようも無さそう…(苦笑)
(ここで試しているAnt1.6.5は、Eclipse3.2にデフォルトで入っているバージョン。
Eclipse3.4に入っているAnt1.7.0でも駄目だった。[2008-11-29])
どうもjspcタスク(内のJasperCクラス)は、Tomcat4までしか対応していない(Tomcat5.xには正式対応していない)っぽい。
という訳で、jspcを使うのではなく、直接jasperを呼ぶのが良さそう。
Tomcat5.5やJBoss4.2.3等に入っているorg.apache.jasper.JspCのタスク。ここでは便宜上jasperタスクと呼ぶことにする。
jasper属性 | 説明 | 備考 | ||
---|---|---|---|---|
jasper オプション |
jspc属性 | jspcネストタグ | ||
outputdir | 生成されたjavaソースを置くディレクトリー | -d | destdir | |
package | -p | package | ||
verbose | 0より大きい数値が入っていれば、verboseモード | -v | ||
uriroot | jspファイルが置いてあるディレクトリー | -uriroot | uriroot | |
-webapp | webapp(で指定したディレクトリー) | |||
-uribase | uribase | |||
ieplugin | ||||
-webinc | webinc | |||
webxml | -webxml | webxml | ||
-mapped | mapped | |||
jspfiles | 個別にjspファイルを指定したい場合は、ここにカンマ区切りで列挙する 。 | |||
javaencoding | jspファイルのエンコーディングの指定 | -javaEncoding | ||
trimspaces | JSPディレクティブの空行を消すかどうか
。 (空行は結局jspから変換されたjavaプログラム内で出力している為、 javaソース生成時点で空行有無の指定を行う) |
-trimSpaces | ||
compile | trueにすると、変換したjavaソースのコンパイル(javac)も行う。 javaソースと同じ場所にclassファイルが作られる。 コンパイルエラーはjspファイルに対して表示される。 |
-compile | ||
classpath | -classpath | |||
CompilerSourceVM | デフォルトは1.4 | -source | ||
CompilerTargetVM | デフォルトは1.4 | -target |
webappsというディレクトリーの下にあるjspファイルをコンパイルしてtemp/jspcというディレクトリ ーに置く例。
<?xml version="1.0" encoding="Shift_JIS"?> <project name="jspc" default="jspc" basedir=".." > 〜 <target name="jasper2"> <taskdef name="jasper" classname="org.apache.jasper.JspC" classpathref="jasper.class.path" /> <jasper uriroot="webapps" outputdir="temp/jspc" javaencoding="MS932" trimspaces="true" /> </target> </project>
なお、「WEB-INF」というディレクトリーは「WEB_002dINF」という名前(パッケージ)に変換される。[2008-09-21]
jspファイル内でタグリブ (カスタムタグ)を使っている場合、jasperはそのチェックもしてくれる。[2008-09-28]
jspファイル内でtaglibディレクティブを指定しているので、そのtldファイルを読み込める必要がある。
特にweb.xml経由でtldファイルの場所を指定している場合、そのweb.xmlはチェック対象ディレクトリーの中にある必要がある。
(warタスクではweb.xmlは他の場所にあってもいいので、そういう構成にしているとjasperではチェックできない。
jasperにはwebxmlという属性もあるが、これはweb.xmlの内容がjasperによって編集される場合の出力先の指定らしい)
webapps/sample.jsp:
<%@ page contextType="text/html;charset=Shift_JIS" %> <%@ taglib uri="/sample-taglib" prefix="sample" %> <html> 〜 </html>
webapps/WEB-INF/web.xml:
<?xml version="1.0" encoding="Shift_JIS"?> <web-app> <taglib> <taglib-uri>/sample-taglib</taglib-uri> <taglib-location>/WEB-INF/tld/sample.tld</taglib-location> </taglib> </web-app>
tldファイルを見つけられない場合はエラーが発生する。
org.apache.jasper.JasperException: JSP ファイル "/sample-taglib" が見つかりません
jspファイル内で使われているカスタムタグの属性とtldファイル内での宣言が一致していない場合、エラーが発生する。[2008-09-28]
org.apache.jasper.JasperException: file:C:/〜/webapps/sample.jsp(11,0) TLDによると、タグ empty の属性 dataname は無効です
webapps/sample.jsp:
〜 <sample:empty dataname="サンプル"/> 〜
webapps/WEB-INF/tld/sample.tld:
〜 <tag> <name>empty</name> 〜 <attribute> <name>dataName</name> </attribute> </tag> 〜
この例の場合、「dataname」の「n」の大文字小文字が異なるので、エラー。大文字でも小文字でもいいので一致させる必要がある。
tldファイル内で宣言されているクラス (タグハンドラークラス)がjasperから見つけられないとエラーになる。[2008-09-28]
org.apache.jasper.JasperException: file:C:/〜/webapps/tag.jsp(10,0) タグ "sample:empty" にタグハンドラクラス "sample.EmptyTag" をロードできません
webapps/WEB-INF/tld/sample.tld:
〜 <tag> <name>empty</name> <tagclass>sample.EmptyTag</tagclass> 〜 </tag> 〜
つまり、jasperのクラスパスにタグリブのclassを含める必要がある。
jasperタスクにはclasspathという属性があるが、ここにEmptyTagが入っているクラスパスを指定しても読み込まれない…。
(classpath属性は、変換したjavaソースをコンパイルするときに使う指定のような気がする)
jasper自身を宣言するクラスパスの中にこのクラスパスを指定してやるといいようだ。
<path id="jasper.class.path"> <path path="${java.class.path}" /> 〜 <path location="classes" /> ← sample/EmptyTag.class が入っている場所 </path>
tldファイル内で宣言されている属性名とタグハンドラークラスのセッターメソッド名が一致していないとエラーになる。[2008-09-28]
org.apache.jasper.JasperException: file:C:/〜/webapps/tag.jsp(11,0) 属性 dataname のsetterメソッドが見つかりません
webapps/WEB-INF/tld/sample.tld:
〜 <tag> <name>empty</name> <tagclass>sample.EmptyTag</tagclass> <attribute> <name>dataname</name> </attribute> </tag> 〜
public class EmptyTag extends TagSupport { private String dataName; public void setDataName(String s) { dataName = s; } 〜 }
セッター名は、tldファイルで指定された属性名の先頭1文字を大文字にしてsetを付けたメソッドでなければならない。
上記の例では「dataname」の「n」の大文字小文字が異なっている。
属性名が「dataname」なら「setDataname()」、「dataName」なら「setDataName()」が正しい。
(ちなみに、WebLogic8.1では大文字小文字が異なっていてもエラーにならずに呼び出せる)
jasperによって変換されたjavaソースは、javacタスクによってコンパイルすることが出来る。[2008-09-21]
ただ、(JSP用の)サーブレットというソースになっているので、サーブレット系のライブラリーが必要となる。
temp/jspcに置かれたjspソースをコンパイルしてtemp/classesというディレクトリーに置く例。
<target name="javac_jsp"> <javac destdir="temp/classes" classpathref="jasper.class.path"> <src path="temp/jspc" /> </javac> </target>
WebLogicでは、JSPClassServletという、事前コンパイルされたJSPを呼び出す為のサーブレットがある。[2008-09-21/2008-09-22]
同様の動作をするサーブレットを作ってみた。
JspClassServlet.java:
package jp.hishidama.servlet;
import java.io.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.*;
import javax.servlet.http.*;
public class JspClassServlet extends HttpServlet {
private static final long serialVersionUID = -8615861206799158465L;
/** JSPクラス名をキーにしてそのインスタンスを保持する */
protected ConcurrentHashMap<Class, HttpServlet> map = new ConcurrentHashMap<Class, HttpServlet>();
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
//(includeを考慮して)URIを取得
String uri;
try {
uri = getURI(req);
} catch (FileNotFoundException e) {
res.sendError(404, e.getMessage());
return;
}
// URIをクラス名に変換
String name = uri.substring(0, uri.length() - 4); //拡張子を取り除く
name = name.replace('/', '.');
name = name.replace("-", "_002d"); //WEB-INF対策
name = "org.apache.jsp" + name + "_jsp";
Class<HttpServlet> c;
try {
c = classForName(name);
} catch (ClassNotFoundException e) {
res.sendError(404, e.toString());
return;
}
HttpServlet s = map.get(c);
if (s == null) {
try {
s = c.newInstance();
s.init(super.getServletConfig());
} catch (Exception e) {
res.sendError(404, e.toString());
return;
}
map.put(c, s);
}
s.service(req, res); //JSPクラス呼び出し
}
/** 他サーブレットからincludeされた場合を考慮してURIを取得する。 */
private String getURI(HttpServletRequest req) throws FileNotFoundException {
String uri;
Object servletPath = req.getAttribute("javax.servlet.include.servlet_path");
if (servletPath != null) { //includeされている場合
uri = (String) servletPath;
Object pathInfo = req.getAttribute("javax.servlet.include.path_info");
if (pathInfo != null) {
uri += pathInfo;
}
} else { //includeされていない場合
uri = req.getServletPath();
String pathInfo = req.getPathInfo();
if (pathInfo != null) {
uri += pathInfo;
}
if (uri.startsWith("/WEB_002dINF/")) {
// クライアントから指定された場合は WEB_002dINFをエラーにしないと、
// WEB-INF/*.jspをコンパイルしたクラスにアクセスできてしまう
throw new FileNotFoundException(req.getRequestURI());
}
}
if (!uri.endsWith(".jsp")) {
// 拡張子がjspでない場合は当サーブレットではエラー
throw new FileNotFoundException(req.getRequestURI());
}
return uri;
}
/** 未チェック警告を回避する為だけのメソッド */
@SuppressWarnings("unchecked")
private Class<HttpServlet> classForName(String name) throws ClassNotFoundException {
return (Class<HttpServlet>) Class.forName(name);
}
}
このクラスをコンパイルしてWEB-INF/classesの中に置く。
そして、実行時にこのクラスを呼び出すようweb.xmlに定義する。
web.xml:
<?xml version="1.0" encoding="Shift_JIS"?> <web-app> <servlet> <servlet-name>JspClassServlet</servlet-name> <servlet-class>jp.hishidama.servlet.JspClassServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>JspClassServlet</servlet-name> <url-pattern>*.jsp</url-pattern> </servlet-mapping> </web-app>