#環境
##AP サーバー
GlassFish 3.1.2.2
#最初に実装した問題ありのパターン
package sample.jsf;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
@Named
@RequestScoped
public class HelloBean {
private String value;
@PostConstruct
public void init() {
this.log("init");
this.value = "Hello";
}
public void submit() {
this.log("submit");
}
private void log(String tag) {
System.out.println(tag + " hash:" + this.hashCode());
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<title>index</title>
</head>
<body>
<form jsfc="h:form">
<input type="text" jsfc="h:inputText" value="#{helloBean.value}" />
<input type="submit" jsfc="h:commandButton" action="#{helloBean.submit}" value="submit" />
</form>
</body>
</html>
画面
バッキング Bean は @Named
を付けることで CDI コンテナ管理にしている。
hello.xhtml
を表示すると、テキスト入力項目の値(#{helloBean.value}
)を参照するため、コンソールに以下のメッセージが出力される。
情報: init hash:396533266
次に、 submit ボタンをクリックすると次のようにコンソールに出力される。
情報: init hash:1209656366
情報: submit hash:1209656366
init()
メソッドが、計2回呼ばれてしまっている。
もし init()
メソッドで画面に表示するための情報をデータベースから取得していたりすると、 submit()
時も不要なデータベースアクセスが発生してしまう。
#スコープを @ConversationScoped にしてみる
前述の問題パターンは、バッキング Bean が @RequestScoped
になっていた。
なので、リクエストのたびに @PostConstract
が実行されるのは当たり前といえる。
ということで、スコープを @ConversationScoped
にしてみた。
※ @ConversationScoped
を使うと、 @RequestScoped
より長く @SessionScoped
よりも短いスコープを定義できる。このスコープの開始と終了は、コード中にハードに実装する。
package sample.jsf;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Inject;
import javax.inject.Named;
@Named
@ConversationScoped
public class HelloBean implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
private Conversation conversation;
private String value;
@PostConstruct
public void init() {
this.log("[@ConversationScoped] init");
this.value = "Hello";
this.conversation.begin();
}
public void submit() {
this.log("[@ConversationScoped] submit");
this.conversation.end();
}
private void log(String tag) {
System.out.println(tag + " hash:" + this.hashCode());
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
init()
の最後で begin()
して、 submit()
の最後で end()
を呼ぶようにした。
すると、コンソール出力は以下のようになった。
情報: [@ConversationScoped] init hash:1738863096
情報: [@ConversationScoped] init hash:1275168439
情報: [@ConversationScoped] submit hash:1275168439
ダメでした。
#FacesContext#isPostback() を使って init() メソッド内で条件分岐させる
↓検索したら、同じことで困っている人がいました。
jsf 2 - How to prevent @PostConstruct from being called on postback - Stack Overflow
ここで紹介されていた方法の1つが、 FacesContext#isPostback()
を使う方法。
package sample.jsf;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.FacesContext;
import javax.inject.Named;
@Named
@RequestScoped
public class HelloBean {
private String value;
@PostConstruct
public void init() {
if (FacesContext.getCurrentInstance().isPostback()) {
this.log("is postback");
} else {
this.log("init");
this.value = "Hello";
}
}
public void submit() {
this.log("submit");
}
private void log(String tag) {
System.out.println(tag + " hash:" + this.hashCode());
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
init()
メソッド内で、 FacesContext.getCurrentInstance().isPostback()
の値を取得し、 false のときだけ初期化を実行するようにしている。
画面にアクセスして submit を実行すると、以下のようにコンソールに出力される。
情報: init hash:201077391
情報: is postback hash:592087746
情報: submit hash:592087746
submit したときは、初期化処理が実行されなくなり、一応問題は解決しました。
#そもそも @ViewScoped にすべき?
前述のStackOverflow では、もう1つの解決策が紹介されている。
それは、バッキング Bean を JSF コンテナ管理にして、スコープを @ViewScoped
にする、という方法。
package sample.jsf;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class HelloBean implements Serializable {
private static final long serialVersionUID = 1L;
private String value;
@PostConstruct
public void init() {
this.log("[@ViewScoped] init");
this.value = "Hello";
}
public void submit() {
this.log("[@ViewScoped] submit");
}
private void log(String tag) {
System.out.println(tag + " hash:" + this.hashCode());
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
実際に動かした結果は以下。
情報: [@ViewScoped] init hash:684562545
情報: [@ViewScoped] submit hash:684562545
実装がシンプルでいい感じです。
どうしてもバッキング Bean を CDI コンテナ管理にしないといけない、という理由がない限りは(特に思いつかないけど)、こっちの方がよさげです。
#参考