This question already has an answer here:
#ViewScoped calls #PostConstruct on every postback request
(1 answer)
Closed 6 years ago.
I thought #ViewScoped was supposed to prevent the bean from being reconstructed while the user is on the same page... So why is my #ViewScoped JSf controller bean being created multiple times even before the action handler causes the browser to navigate away from that view?
Can anyone point me in the right direction here?
Here is my code:
The View (domain/edit.xhtml)
<h:form prependId="false">
<h:inputText id="descriptionField" value="#{domainEdit.domain.description}" />
<h:commandButton id="saveButton" value="save" action="#{domainEdit.save}" />
</h:form>
The ViewScoped controller (DomainEdit.java)
#Named("domainEdit")
#ViewScoped
public class DomainEdit implements Serializable {
private static final long serialVersionUID = 1L;
protected DomainEdit() {
}
#PostConstruct
protected void init() {
System.out.println("post construct called.");
}
#PreDestroy
public void destroy() {
System.out.println("pre destroy called.");
}
public DomainEntity getDomain() {
System.out.println("displaying domain...");
// some code to return the domain
return domain;
}
public String save() {
System.out.println("saving...");
// some saving code
return "view";
}
}
Output
I get the following output when I deploy this and perform the following:
Navigate to the edit view (edit.xhtml)
post construct called.
displaying domain...
pre destroy called.
Change the content of the domainDescriptionField input text
nothing logged
Click 'save'
post construct called.
displaying domain...
pre destroy called.
post construct called.
displaying domain...
pre destroy called.
post construct called.
displaying domain...
pre destroy called.
post construct called.
displaying domain...
pre destroy called.
post construct called.
displaying domain...
saving domain...
pre destroy called.
Unless you're using JSF 2.2 (which is still not out yet at this moment) or MyFaces CODI (which I'd have expected that you would explicitly mention that), the #ViewScoped doesn't work in CDI. This also pretty much matches your problem symptoms.
Manage the bean by JSF instead of CDI. Replace #Named("domainEdit") by #ManagedBean from javax.faces.bean package. Or, install MyFaces CODI to bridge JSF #ViewScoped to CDI.
Related
In a JSF managed bean constructor, I load a entity from database usint a request parameter. Some times, the entity is not in database and I want to show other JSF (.xhtml) page with 404 message.
This is a sample of managed bean:
#ManagedBean(name = "someBean")
#RequestScoped
public class SomeBean implements Serializable {
private static final long serialVersionUID = 1L;
private SomeData someData;
public SomeBean() throws IOException {
someData = ... loads from database using JPA features
if(someData == null){
HttpServletResponse response = (HttpServletResponse) FacesContext
.getCurrentInstance().getExternalContext().getResponse();
response.sendError(404);
}
}
public SomeData getSomeData(){
return someData;
}
}
I configured the web.xml file something like that:
<error-page>
<error-code>404</error-code>
<location>/404.xhtml</location>
</error-page>
I have a JSF page to handle the entity loaded by managed bean. When the entity exists, I will use it in the page. Like that:
<h1>#{someBean.someEntity.name}</h1>
<h2>#{someBean.someEntity.description}</h2>
<ui:repeat value="#{someBean.someEntity.books}" var="book">
// ..........
</ui:repeat>
The page above works when the managed loads the data successfully.
The Problem
When the entity not exists and I send a 404 ERROR CODE, the JSF still process methods defined in the expression language of the first page.
This behavior makes the managed bean throws a NullPointerException, and a HTTP 500 ERRO CODE.
My 404 error page is not called. I do not know why.
I try send the 404 error even when the entity is found in database and the 404 error page works.
Enyone can explain this JSF behavior to this happiness? Or offer some kind to show the 404 error page without URL change ?
You're basically trying to perform front controller logic while rendering the view. You should do it before rendering the view. Because, once you start rendering the view, it's already too late to change the view to a different destination, e.g. an error page as in your case. You namely cannot take the already sent response back from the client.
In JSF 2.2 you can use <f:viewAction> for this.
<f:metadata>
<f:viewAction action="#{bean.init}" />
</f:metadata>
public void init() {
// ...
if (someCondition) {
context.getExternalContext().responseSendError(404, "some message");
context.responseComplete();
}
}
(note that whenever you need to import javax.servlet.* classes into your JSF backing bean, you should absolutely stop and look if the functionality isn't already available in ExternalContext or otherwise think twice if you're doing things the right way, e.g. perhaps you needed a servlet filter; also note that you need to explicitly tell JSF that you've completed the response, otherwise it will still attempt to render the view)
In JSF 2.0/2.1 you can use <f:event type="preRenderView"> for this. See also among others What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
In case you're actually trying to validate a HTTP request parameter and you also happen to use OmniFaces, you may consider using <f:viewParam> with a true JSF validator and control the sendError with OmniFaces <o:viewParamValidationFailed>.
I am working on a page with a session-scoped managed bean.
On the page I have a preRenderComponent:
<f:metadata>
<f:event listener="#{pageBean.init}" type="preRenderComponent"></f:event>
</f:metadata>
This page is a template client, that shares a template with some other pages. The template contains a side navigation bar with links to each of the template client pages.
And the page bean:
#Named
#Default
#SessionScoped
public class pageBean implements Serializable {
#PostConstruct
public void init(){
System.out.println("Page Bean init.");
//call methods that populate data on the page
}
}
And the issue occurs as follows:
If I removed the line on the page with preRenderComponent, the pageBean init() method will still be called when the page is accessed.
If I kept the line said above, the init() method will be called when accessed, but it will also be called whenever I clicked on the side navigation bar and accessed another page which uses the same template.
I have referred to this question:CDI bean constructor and #PostConstruct called multiple times
and made sure I was not mixing JSF with CDI, yet this issue still occurs. Though it seems I could resolve this problem by just simply removing the preRenderComponent line, I really wish to understand what is going on here and figure out a way to avoid it in the future.
Any information is much appreciated.
I do not really understand how getter and setter work althougth it is a basic concept. I have the following code, how is the attribute id sent to Managed Bean? Is it captured by getter method?
My facelet
<p:inputText id="id" value="#{bean.id}">
My managed bean
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
The call of getter and setter methods by #{} expressions is not part of JSF but Expression Language (most known as EL). JSF takes advantage of EL to bind the data of the HTML components to the fields of a bean through proper getters and setters. This is:
If the bean exists, Expression Language will execute the proper getter of the registered bean in the proper scope.
If client performs a form submission or an ajax request, then the components that are sent to the server (usually all the components in the <h:form>, in case of ajax requests you can state which components to send to the server) will contain a new value, and this value will be set to the field with the proper setter method.
For example, you have a SayHelloBean which belongs to request scope:
#RequestScoped
#ManagedBean
public class LoginBean {
private String name;
//proper getter
public String getName() {
return this.name;
}
//proper setter
public void setName(String name) {
this.name = name;
}
}
And these 2 facelets pages (since it's an example I avoid declaring <html>, <h:head>, <h:body> and other elements, just focusing on the relevant code)
Page1.xhtml:
<h:form>
Please tell me your name
<h:inputText value="#{loginBean.name}" />
<h:commandButton action="page2" />
</h:form>
Page2.xhtml:
Hello #{loginBean.name}
This is what happens behind the scenes:
When Page1.xhtml is loaded, a new instance of LoginBean, which we may call loginBean, will be created by JSF and registered into JSP request scope. Since the value of <h:inputText /> is bound to LoginBean#name (which is read as the field name of LoginBean class), then EL will display the value of loginBean#name (which is read as the field name of instance loginBean), and since that is not initialized, EL will display null, as an empty string.
When you submit the form of Page1.xhtml, since LoginBean is #RequestScoped then JSF will create a new instance of LoginBean, which we may call it loginBean2 (adding 2 in the end because this instance is totally different from the loginBean previously created) and will register it in JSP request scope. Since the value of <h:inputText /> is bound to LoginBean#name, JSF will validate and set the data by calling the proper setter. This will make loginBean2#name have the value of the <input type="text"> that was rendered by <h:inputText/>.
At last, JSF will make sure to navigate to Page2.xhtml through forward, where when processing it, it will find #{loginBean.name} and EL will check for the value of loginBean2#name and replace it.
The steps explained here are a very small explanation (and with lot of elements not explained) of the JSF lifecycle and how JSF uses getters and setters.
More info:
How to pass parameter to jsp:include via c:set? What are the scopes of the variables in JSP?
How to choose the right bean scope?
The Lifecycle of a JavaServer Faces Application
Differences between Forward and Redirect
Additional note: since you're learning JSF, avoid putting any business logic code in getters/setters. This is greatly explained here: Why JSF calls getters multiple times
Whenever you use something like
#{someBean.someField}
the EL looks for a someBean.getSomeField() or someBean.setSomeField(...) method, depending on whether you're reading that field or writing in it (which can easily be inferred from the context). JSF never accesses a field directly (i.e without making use of its getter or setter). Try deleting the getter and setter of a given field and you'll see it won't work.
In a JSF managed bean constructor, I load a entity from database usint a request parameter. Some times, the entity is not in database and I want to show other JSF (.xhtml) page with 404 message.
This is a sample of managed bean:
#ManagedBean(name = "someBean")
#RequestScoped
public class SomeBean implements Serializable {
private static final long serialVersionUID = 1L;
private SomeData someData;
public SomeBean() throws IOException {
someData = ... loads from database using JPA features
if(someData == null){
HttpServletResponse response = (HttpServletResponse) FacesContext
.getCurrentInstance().getExternalContext().getResponse();
response.sendError(404);
}
}
public SomeData getSomeData(){
return someData;
}
}
I configured the web.xml file something like that:
<error-page>
<error-code>404</error-code>
<location>/404.xhtml</location>
</error-page>
I have a JSF page to handle the entity loaded by managed bean. When the entity exists, I will use it in the page. Like that:
<h1>#{someBean.someEntity.name}</h1>
<h2>#{someBean.someEntity.description}</h2>
<ui:repeat value="#{someBean.someEntity.books}" var="book">
// ..........
</ui:repeat>
The page above works when the managed loads the data successfully.
The Problem
When the entity not exists and I send a 404 ERROR CODE, the JSF still process methods defined in the expression language of the first page.
This behavior makes the managed bean throws a NullPointerException, and a HTTP 500 ERRO CODE.
My 404 error page is not called. I do not know why.
I try send the 404 error even when the entity is found in database and the 404 error page works.
Enyone can explain this JSF behavior to this happiness? Or offer some kind to show the 404 error page without URL change ?
You're basically trying to perform front controller logic while rendering the view. You should do it before rendering the view. Because, once you start rendering the view, it's already too late to change the view to a different destination, e.g. an error page as in your case. You namely cannot take the already sent response back from the client.
In JSF 2.2 you can use <f:viewAction> for this.
<f:metadata>
<f:viewAction action="#{bean.init}" />
</f:metadata>
public void init() {
// ...
if (someCondition) {
context.getExternalContext().responseSendError(404, "some message");
context.responseComplete();
}
}
(note that whenever you need to import javax.servlet.* classes into your JSF backing bean, you should absolutely stop and look if the functionality isn't already available in ExternalContext or otherwise think twice if you're doing things the right way, e.g. perhaps you needed a servlet filter; also note that you need to explicitly tell JSF that you've completed the response, otherwise it will still attempt to render the view)
In JSF 2.0/2.1 you can use <f:event type="preRenderView"> for this. See also among others What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
In case you're actually trying to validate a HTTP request parameter and you also happen to use OmniFaces, you may consider using <f:viewParam> with a true JSF validator and control the sendError with OmniFaces <o:viewParamValidationFailed>.
I want to reset by JSF backing bean when some method is invoked. Assume that there is a command button, someone press it and after succesfull transaction, my View or Session scope JSF bean should be reseted. Is there a way to do that?
Thank
I found the solution for View scope.
public static void removeViewScopedBean(String beanName)
{
FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(beanName);
}
A view scoped bean will be recreated when you return non-null or non-void from the action method, even if it would go back to the same view. So, just return a String from the action method, even if it's just an empty string:
public String submit() {
// ...
return "";
}
To make it complete, you could consider sending a redirect by appending the ?faces-redirect=true query string to the returned outcome.
public String submit() {
// ...
return "viewId?faces-redirect=true";
}
A session scoped bean is in first place the wrong scope for whatever you're currently trying to achieve. The bean in question should have been be a view scoped one. Ignoring that, you could just recreate the model in the action method, or very maybe invalidate the session altogether (which would only also destroy all other view and session scoped beans, not sure if that is what you're after though).
just clear all views:
FacesContext.getCurrentInstance().getViewRoot().getViewMap().clear();
and remember to implements Serializable in all views
You could also refresh the page from javascript, so the ViewScoped Bean will be reseted, for example in a primefaces commandButton:
<p:commandButton value="Button" action="#{bean.someAction()}" oncomplete="location.reload()"/>
I solve the problem with code like this:
((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getSession().removeAttribute("bean name");
By this way I enter to session scoped bean and reset it without the data that was there before