S-JIS[2008-09-12/2008-11-29] 変更履歴

Ant:jspc

jspcは、jspファイルのコンパイル(javaソースの生成)を行うオプションタスク。
(org.apache.tools.ant.taskdefs.optional.jsp.JspC extends MatchingTask
Antタスクを使って事前にコンパイルしておくことで、JSPのコンパイルエラーが実行前にチェックできる。

実体はorg.apacheのjasperなので、Tomcat系列(TomcatJBoss)向け。 WebLogicではwlappcwljspcを使う。


属性

属性 説明
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」というクラスが必要。
これはTomcatJBossをインストールすると入っているので、そのパスをjspcに指定してやる。 (ここではjasper.class.pathという名前にした)

Tomcatの場合

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>

JBossの場合

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を呼ぶのが良さそう。


jasper

Tomcat5.5JBoss4.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 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    

jasperの例

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>
〜

EmptyTag.java

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では大文字小文字が異なっていてもエラーにならずに呼び出せる)


javac

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>

JspClassServlet

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>

Ant目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま