JSFは簡単??
JSFの採用を決めたのはいいのですが、まだまとまった情報がないような気がしました。いろいろ探しても、2年前くらいの「はじめての○○」系の古い記事ばかり。「JSFで簡単に開発できる」とは良く聞くけど、簡単なことしか説明していないのでは??との疑惑を持ちはじめています。
※こちらの記事はとても分かりやすくて参考になりました。
知りたかったことは、一般的にWebアプリに求められるであろう、単純な内容です。
- レイアウト(デザイン)テンプレート
- サーバエラー時の画面遷移
- HTTPヘッダー制御(ファイルダウンロードやリダイレクト)
JSFの問題領域ではナイのかもしれないけど、どうサバくのがJSFらしいやりかたなのか。
ググってもイマイチだし、これといった決定版な書籍も見あたらず。結局、JavaEEチュートリアルと、MyFaces Tomahawkのソースを読むことになってしまいました。
・・というか、JSF本、山形に売ってない・・orz。
通りすがりの詳しいみなさま、教えていただければ幸いです。
今のところ感じているJSFの印象は、
簡単なものを作るにはめっぽう簡単。多少こみいってくると、ごっつい仕様書との格闘が。。。少なくとも、カスタムコンポーネントの作り方は、異様に煩雑だ。でもさすがにStrutsに戻りたいとは思わない。
です。
ぐぅ~、これで「簡単だ」と言い切るから、Javaギークな人達はすごい。
・・・疲れた。
以下、メモです。
レイアウトテンプレート
Struts Tilesのようなレイアウトテンプレートの機能が欲しいわけですが、JSFにはそんなTagはナイ。だからといって、他のフレームワークをごちゃごちゃといれるのはイヤ。
まあでも、CSSでレイアウトするのが今っぽいのでしょうから、せめて、ヘッダー情報を一括で入れ替えられるようにしたいです。
ということで、JSP2.0の機能、jsp-config を使うことにしました。
以下のように設定をしてやると、各JSPのアタマとオシリにフラグメントを挿入してくれます。各JSPでは、<body>タグの中身だけを記述することになります。
■web.xmlの抜粋
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<include-prelude>/jspf/header.jspf</include-prelude>
<include-coda>/jspf/footer.jspf</include-coda>
</jsp-property-group>
</jsp-config>
■/jspf/header.jspfの例:pageディレクティブとHTML-HEADを定義。
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@taglib prefix="my" uri="/WEB-INF/tlds/custom_validator"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<link href="/stylesheets/site.css" media="all"
rel="Stylesheet" type="text/css" />
</head>
<body>
■/jspf/footer.jspfの例:header.jspfのタグを閉じる
</body>
</html>
エラー制御
JSFを触りはじめると、まず悩むのが、MangedBeanがPOJOであることです。
一見キレイに思えるのですが、いざHTTPやSevletを意識しなければならない場面になると、唸ってしまいます。例えば、リダイレクトやファイルダウンロード、内部サーバエラーの捕獲など。
システムエラーの捕獲は、試してみればすぐにわかるのですが、ManagedBeanのメソッドからテキトーなExceptionをスローすると、コンテナがServletExceptionにラップしてくれるようでした。
こんな感じのフツーの対処で十分。
■ManagedBeanの抜粋:
public String managedBeanMethod() throws AppException{
if( true ) throw new AppException();
return "from-outcome"
}
■web.xmlの抜粋:
<error-page>
<exception-type>javax.servlet.ServletException</exception-type>
<location>/error.jsp</location>
</error-page>
■error.jspの抜粋:
<%@ page isErrorPage="true" %>
<html>
<body>
<h1>エラー!</h1>
${pageContext.errorData.throwable.cause}
</body>
</html>
業務エラーの遷移は、faces-config.xmlで設定。
<navigation-rule>
<navigation-case>
<from-outcome>application_error</from-outcome>
<to-view-id>/application_error.jsp</to-view-id>
<redirect/> <!--←これがナイ場合は、forwardになる-->
</navigation-case>
</navigation-rule>
強制的にnavigation-ruleを操作することもできます。もし、何かしらのエラーハンドリングフレームワークを作るのであれば、この方法も悪くはないのかもしれません。
//FacesContextを取得
FacesContext context = FacesContext.getCurrentInstance();
//アプリケーションコンテキストからNavigationHandlerを取得
ApplicationFactory factory
= (ApplicationFactory)FactoryFinder
.getFactory(FactoryFinder.APPLICATION_FACTORY);
NavigationHandler navi
= factory.getApplication().getNavigationHandler();
//強制的に遷移
navi.handleNavigation(context, fromAction ,"application_error");
ファイルダウンロード
HTTPヘッダーを弄らなければならない場合、結局は、HttpServletResponseオブジェクトが必要になります。
このあたりのコンテナを意識したコードは、カスタムコンポーネントに隠蔽してやるのがJSF流なのでしょうけど、ファイルダウンロード程度のちょっとした機能のためにたくさんのカスタムクラスを作るのはイヤです。作り方も煩雑だし・・。
だからといって、安易にServletで作ってしまうと、フレームワークの意味がないような気がしますし、web.xmlに業務機能が入り乱れることにもなります。なので、あくまでもJSFの枠組みで実現する方法がほしい。
とりあえず動くものは、以下のように作ることはできました。
■JSPの抜粋:
<h:form id="downloadForm">
<h:commandLink id="downloadLink"
actionListener="#{httpAction.download}">
<h:outputText value="ダウンロード"/>
</h:commandLink>
</h:form>
■ManagedBean:
public class HttpActionHandler {
public void download(ActionEvent event) throws IOException{
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext exContext = facesContext.getExternalContext();
HttpServletResponse response
= (HttpServletResponse)exContext.getResponse();
response.setContentType("text/plain");
response.setHeader("Content-disposition"
, "attachment; filename=\"test.txt\"");
PrintWriter writer = response.getWriter();
writer.print("test test test");
writer.close();
facesContext.responseComplete();//これ必要?
}
}
ExternalContextがミソ。
ここからRequest/Responseオブジェクトを取り出して、あとはServletコードを自由に書くことができるようです。RemoteUserやUserInRoleを取得したいときも、必要になるでしょう。
このManagedBeanは、POJOのようでPOJOにあらず。でも、それっぽい名前にして、Servletコードを集約すれば、許されるような気もしますw
先にも言及したように、こういうServlet的コードはカスタムRenderなどに隠蔽するのが、JSF流なのだと思います。