The JSF/CDI request scope is bound to the HTTP
request-response life cycle.
The request scope is very
useful in any web application, and an object defined in the request scope
usually has a short lifespan; beans live as long as the HTTP request-response
lives. When the container accepts an HTTP request from the client, the
specified object is attached to the request scope and it is released when the
container has finished transmitting the response to that request. A new HTTP
request always comes in a new request scope object. In short, a request scope
represents a user's interaction with a web application in a single HTTP
request. Commonly, a request scope is useful for simple GET requests that
expose some data to the user without requiring to store the data.
The request scope is present in JSF and CDI and functions in the same
way. It can be used for non-rich AJAX and non-AJAX requests.
Request Scope Annotations
JSF: The JSF request scope
annotation is @RequestScoped
(javax.faces.bean.RequestScoped). A bean with
this scope should be annotated with @ManagedBean (javax.faces.bean.ManagedBean). For JSF managed beans (@ManagedBean), this is
the default scope, when none is specified.
CDI: The CDI request scope
annotation is @RequestScoped
(javax.enterprise.context.RequestScoped). A
bean with this scope should be annotated with @Named (javax.inject.Named). For CDI managed beans (@Named), the default
scope is the @Dependent
pseudo-scope.
Simple Example
// index.xhtml
<h:body>
<h:body>
<h4>Same view after submit (index.xhtml):</h4>
<h:form>
<h:commandButton value="Count"
action="#{countBean.countActionVoid()}"/>
</h:form>
Current value: #{countBean.count}
<h4>Forward to another view after submit
(count.xhtml):</h4>
<h:form>
<h:commandButton value="Count"
action="#{countBean.countActionAndForward()}"/>
</h:form>
Current value: #{countBean.count}
<h4>Redirect to another view after
submit (count.xhtml):</h4>
<h:form>
<h:commandButton value="Count"
action="#{countBean.countActionAndRedirect()}"/>
</h:form>
Current value: #{countBean.count}
<h4>AJAX :</h4>
<h:form>
<h:commandButton value="Count"
action="#{countBean.countActionVoid()}">
<f:ajax render="currentValueId"/>
</h:commandButton>
</h:form>
<h:outputText id="currentValueId"
value="Current value: #{countBean.count}"/>
</h:body>
// count.xhtml
<h:body>
Current value: #{countBean.count}
</h:body>
// count.xhtml
<h:body>
Current value: #{countBean.count}
</h:body>
// CountBean.java
import
java.util.logging.Logger;
// for JSF
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
// for CDI
import javax.inject.Named;
import
javax.enterprise.context.RequestScoped;
// JSF vs
CDI
@ManagedBean @Named
@RequestScoped @RequestScoped
public class
CountBean {
private static final Logger LOG =
Logger.getLogger(CountBean.class.getName());
private int count;
public CountBean() {
LOG.info("CountBean#Initializing counter
...");
count = 0;
}
public void countActionVoid() {
LOG.info("CountBean#countActionVoid() -
Increasing counter ...");
count++;
}
public String countActionAndForward() {
LOG.info("CountBean#countActionAndForward() - Increasing counter
...");
count++;
return "count";
}
public String countActionAndRedirect() {
LOG.info("CountBean#countActionAndRedirect()
- Increasing counter ...");
count++;
return
"count?faces-redirect=true;";
}
public int getCount() {
return
count;
}
public void setCount(int count) {
this.count = count;
}
}
The complete application is available here.
So, when navigating via AJAX or via forward mechanism back in the same
view or another view the count
will be 1. This
reveals two aspects:
- At each request the CountBean
constructor is called for creating a new instance. This means that count is reset to 0. This is happening for AJAX requests also.
- The request scope doesn't lose the object's state while forwarding, because
the source page and the destination page (the forwarded page) are part of the
same request-response cycle.
Well, in case of redirect actions the request scope lose the object's
state because redirect involves two requests, a POST and a GET (POST-REDIRECT-GET).
So, when redirecting in our case, the count will be 0 in the destination page (view).
Basically you have to pay attention when you submit data to a request
scoped bean. The submitted data will "live" as long as the current
HTTP request-response, so, if you don't save/use/process them then they will be
lost.
Implements Serializable
JSF and CDI managed beans shouldn't be declared Serializable (implements Serializable). But, you have
to keep in mind that there is a tiny chance that the system will give them the
same id, so make them Serializable
is not a bad practice.
Request Scope Programmatic Access
Programmatically you can interact with request scope like this:
- access the request scope map
// JSF 2.0-2.2
FacesContext
context = FacesContext.getCurrentInstance();
Map<String,
Object> requestMap = context.getExternalContext().getRequestMap();
// JSF 2.3
@Inject
@RequestMap
private Map<String,
Object> requestMap;
// OmniFaces
Map<String,
Object> requestmap = Faces.getRequestMap();
- set a request scoped attribute
// JSF 2.0 -
2.3
requestMap.put(name, value);
// OmniFaces
Faces.setRequestAttribute(name, value);
- get a request scoped attribute
// JSF 2.0-2.3
Object value = requestMap.get(name);
// OmniFaces
<T> value = Faces.getRequestAttribute(name);
- remove a request scoped attribute
// JSF 2.0-2.3
Object value = requestMap.remove(name);
// OmniFaces
<T> value = Faces.removeRequestAttribute(name);
! In JSF pages, you can use the implicit
object, #{requestScope}
(e.g. get CountBean
instance: #{requestScope.countBean}).
Among others, the request map will contain instances of managed beans
that are declared under the request scope (@RequestScoped (JSF/CDI)).
In case of JSF managed beans (not CDI managed beans - in this case, the
keys are pretty complex), you can easily identify such beans by their names
which becomes keys in the request map. Therefore you will be able to locate an
instance of this JSF managed bean in the request map under the key, countBean. If you
specify the bean name via @ManagedBean(name="some_name"), then some_name will be the key in the request map. So, via
the request map, you can access a property of a request scoped JSF managed
bean, like this:
String count =
((CountBean)(Faces.getRequestAttribute("countBean/some_name"))).getCount();
Is perfectly legal to do this also (this refers to the current bean):
@ManagedBean(name="some_name")
...
String
bean_name = getClass().getAnnotation(ManagedBean.class).name();
int count =
((CountBean)(Faces.getRequestAttribute(bean_name))).getCount();
Now, you can easily intuit how to work with managed beans stored in the
request map.
Using @PostConstruct
Typically, in a managed bean, we need to write a method annotated with @PostConstruct for
accomplishing initializations tasks based on injected artifacts. With other words, the @PostConstruct annotation is used on a
method that needs to be executed after dependency injection is done to perform
any initialization. When the initialization doesn't involve injected artifacts
the constructor can be used for initializations. For request scoped beans the method
annotated with @PostConstruct
will be called for each request, since each request requires a separate
instance of the request scoped bean.
JSF managed bean example:
import
javax.faces.bean.ManagedBean;
import
javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class
InitBean {
private int init;
public InitBean() {
init = 5;
}
public int getInit() {
return init;
}
public void setInit(int init) {
this.init = init;
}
}
import
javax.faces.bean.ManagedBean;
import
javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class
CountBean {
@ManagedProperty("#{initBean}")
private InitBean initBean;
@PostConstruct
public void init(){
LOG.info("CountBean#Initializing counter
with @PostConstruct ...");
count =
initBean.getInit();
}
public void setInitBean(InitBean initBean) {
this.initBean = initBean;
}
...
}
CDI managed bean example:
import
javax.enterprise.context.RequestScoped;
import
javax.inject.Named;
@Named
@RequestScoped
public class
InitBean {
private int init;
public InitBean() {
init = 5;
}
public int getInit() {
return init;
}
public void setInit(int init) {
this.init = init;
}
}
import
javax.inject.Inject;
import
javax.enterprise.context.RequestScoped;
import
javax.inject.Named;
@Named
@RequestScoped
public class
CountBean {
@Inject
private InitBean initBean;
@PostConstruct
public void init(){
LOG.info("CountBean#Initializing counter
with @PostConstruct ...");
count =
initBean.getInit();
}
...
}
Injection and request scoped
beans
JSF: For JSF managed beans,
injection is accomplished via @ManagedProperty.
For example:
CDI: For CDI managed beans,
injection is accomplished via @Inject.
For example:
JSF & CDI mixed: CDI can
be injected in JSF (vice versa is not true!)
! As a general rule in JSF, don't
use objects that have shorter lifespan than the objects you are calling it from. In other words, use objects whose lifespan is the same
as, or longer than, the object being injected into. Breaking this rule will end
up in a JSF exception. Base on this rule in a JSF request scoped managed bean
you can inject session, view and application managed beans.
JSF managed beans can be injected in other JSF managed beans.
CDI Managed Beans Restrictions:
! When you are using an object
that has a shorter lifespan than the object you are calling it from (for
example, injecting a request scoped bean into a session scoped bean), CDI
classifies the use case as a mismatched injection and fixes the issue via CDI
proxies. For each request, the CDI proxy re-establishes the connection to a
live instance of the request scoped bean.
CDI managed beans can be injected in JSF managed beans.
Configuring JSF request scoped managed
beans programmatically
Starting with JSF 2.2, we can programmatically
reproduce the content of faces-config.xml.
For request scoped managed beans, the
relevant snippet of code is:
@Override
public void
populateApplicationConfiguration (Document toPopulate) {
String ns =
toPopulate.getDocumentElement().getNamespaceURI();
Element managedbeanEl = toPopulate.createElementNS(ns,
"managed-bean");
Element managedbeannameEl = toPopulate.createElementNS(ns,
"managed-bean-name");
managedbeannameEl.appendChild(toPopulate.createTextNode("countBean"));
managedbeanEl.appendChild(managedbeannameEl);
Element managedbeanclassEl =
toPopulate.createElementNS(ns, "managed-bean-class");
managedbeanclassEl.appendChild(toPopulate.createTextNode("beans.CountBean"));
managedbeanEl.appendChild(managedbeanclassEl);
Element managedbeanscopeEl = toPopulate. createElementNS(ns,
"managed-bean-scope");
managedbeanscopeEl.appendChild(toPopulate. createTextNode("request"));
managedbeanEl.appendChild(managedbeanscopeEl);
...
// programmatic create managed-property
...
toPopulate.getDocumentElement().appendChild(managedbeanEl);
}
A complete application can be seen in Mastering
JSF 2.2 book .
Configuring JSF request scoped managed
beans in XML file
With XML configuration, you can use the old JSF 1.x mechanism to define
the managed bean in a normal faces-config.xml
file. For example:
...
<managed-bean>
<managed-bean-name>countBean</managed-bean-name>
<managed-bean-class>beans.CountBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
...
<!-- managed bean properties --> via <managed-property/>
...
</managed-bean>
...
Managed beans should be defined in a separate XML file because the faces-config.xml is
used to set the application level configurations. Basically, if you prefer this
approach, create a new XML file and put the managed beans detail inside.
Finally, declare the XML file via javax.faces.CONFIG_FILES context parameter in web.xml file.
...
<context-param>
<param-name>javax.faces.CONFIG_FILES</param-name>
<param-value>WEB-INF/my-manage-beans.xml</param-value>
</context-param>
...
See you in the next post about JSF/CDI session scope.
Niciun comentariu :
Trimiteți un comentariu