ELとCDI管理Beanがうまく結びつかないとき

Struts1.xユーザがJSF2.0に移行すると、javax.el.PropertyNotFoundExceptionが500エラーの原因として表示されたり、@ModelでアノテートしたCDI管理Beanのパラメータが#{bean.property}でうまく出力されないことがあると思います。

この原因は主に2つあります。

1. CDI管理Beanにgetter/setterメソッドを忘れている

Strutsのように操作(Action)と状態(ActionForm)が分かれたプログラミングモデルに慣れていると、どうしても見落としがちなのがアクセッサです。例えば、以下のような簡単なBeanを想定します。

CDI管理Bean(StrutsのAction相当)のソース

@Model
public class BookController {
    /* 画面から入力したデータをインジェクション */
    @Inject private Book book;

    /* ビジネスロジックEJBで */
    @EJB public BookManager bookManager;

    /* アクションメソッド */
    public String entryBook() {
        bookManager.createBook(book);
        return "nextpage.xhtml";
    }

    /* 忘れがちなgetterとsetter */
    public void getBook { return book;}
    public Book setBook(Book book) { this.book = book; }
}

この例ではgetBookとsetBookを忘れた状態で、JSFページから#{book.author}のようにEL式でアクセスすると、PropertyNotFoundExceptionが発生してブラウザに500エラーが表示されます。通常のCDI管理Beanは、JSF管理Beanと異なり、getter/setterがなくてもインジェクションできるのですが、インジェクションしたオブジェクトをEL式から参照させる場合は必須になります。

参考 : JavaEE6 Tutorial Adding / Setter and Getter Methods
http://docs.oracle.com/javaee/6/tutorial/doc/gjbbp.html

2. 名前が重複している

私自身はまったのは、CDI管理Beanのプロパティ名と、JSFページのデータテーブルが持つのvar属性の名前が重複してうまく動かなかったことです。前述したサンプルソースに加えて、図書のリストをCDl管理Beanに持たせたとします。

@Model
public class BookController {
    @Inject private Book book;

    /* 図書リストを追加 */
    private List<Book> bookList = new ArrayList<Book>();

    @EJB public BookManager bookManager;

    public String entryBook() {
        // createBook()はListを返してくれる
        bookList = bookManager.createBook(book);   
        return "nextpage.xhtml";
    }

    /* 忘れがちなgetterとsetter */
    // 長くなるので省略。bookList分のアクセッサも忘れずに。
}

この状態で、JSFページに以下のようにdataTableを定義すると、BookControllerクラスが持つbookと、dataTableが持つbookで名前が重複してしまいます。

<h:dataTable value="#{bookController.bookList}" var="book">

手元のJBossAS7.1では、重複したときの動作はCDI管理Beanクラスの名前が優先されました。これは実装により異なるかもしれません。名前重複の場合は例外がthrowされないので注意が必要です。

今回は『はまりどころ』を書きましたが、JSF2.0自体はStrutsと比べて複雑なXMLの書き方を覚えなくていいので、便利な代物です。
これからJavaWebアプリケーションプログラミングを覚える方にはおすすめです。