I`m kind of noob to JSF and I'm trying to figure out which would be the most elegant solution for the following scenario:
Let's say that I have a user managed bean called UserMB:
#ManagedBean
public class UserMB {
private User user;
private List<User> users;
// getters and setters here
public void addUser(User user){
// do add user logic here
}
public List<User> listAllUsers(){
// do list All users logic here
}
#PostConstruct
private void init(){
// populate List<user> users - for the listAllUsers scenario
}
}
Let`s assume that i do not have a form to submit directly to listAllUsers() method, but instead i want to see all users when I open the page list-all-users.xhtml.
When I hit the managed bean from addUser.xhtml a query will be performed to DB to load all users because the bean will not know if i want to use listAllUsers() method or addUser() method.
Should i split this functionality in 2 managed beans ?
Because if so I would have to create several managed beans to deal with "User" business (ie. in Struts2 i would have only one Action that would take care of all user interactions).
P.S. I know that there is the solution to populate List in getter method but I read one article of BalusC that advise us not to do this...
Should i split this functionality in 2 managed beans ?
Yes. Use one bean per view/form. Keep the backing bean class as slick as possible. Don't give it too much responsibilities.
Related
This question already has answers here:
How to choose the right bean scope?
(2 answers)
Closed 2 years ago.
I'm trying to implement JSF backing beans using CDI beans as suggested by the depreciation of #ManagedBean and it's scope annotations, but I'm struggling with the right use examples, I'm trying to implement view backing bean with #Model (javax.enterprise.inject.Model) which is #Named #RequestScoped.
I found this question but it's using a ViewScope bean, how would I implement the same functionality with RequestScoped (Preferably #Model), What is best practice use of #Model in general?
Edit 1:
I tried creating a new Product in the EditProduct PostConstruct:
#Model
public class EditProduct {
private Product product; // +getter +setter
#Inject
private ProductService productService;
#PostConstruct
public void init(){
product = new Product();
}
public String save() {
productService.save(product);
return "/products?faces-redirect=true";
}
// ...
}
and then setting the product via
<f:viewParameter name="product-id" target="#{editProduct.product}"
converter="#{productConverter}" />
it's working but I'm looking for a best practice.
A request scoped backing bean is meant to keep the application memory footprint as low as possible hence using them for supporting views with #Model annotation makes a lot of sense, the draw back is having to reach for the persistence data storage on every request that deals with data so a best use case for #Model bean is:
Basically every thing.
things like:
Events handling for JSF pages
Lazy loading of data
Validation and converting and other code execution
ETC.... yes every thing else
Those things are easily done best in request scoped beans, but then what are the roles of other beans?
In simplistic terms we can assume:
#ViewScoped to support data heavy pages where user edits data with many interactions and each interaction is a request but hitting the database for each one will be costly.
#SessionScoped for session data, authentication, credentials and configuration for the user.
#ApplicationScoped the state-full singleton of CDI.
.... each other scope has it's uses, but for a good web application #Model should be the default and the others has specific uses cases.
You should be able to also add the #Named annotation and it will be exposed as editProduct.
https://memorynotfound.com/cdi-managed-bean-example-with-named/
EDIT: See comment
If I have page, lets say includes two different customers informations, how can I use two different managed beans (which is same java class) in the same page?
As a summary, in the same page I want to hold information of one customer in one bean, another in another bean.
I want to hold information of one customer in one bean, another in
another bean.
Another bean for same purpose is duplication, and If you are thinking it logical. Every page have its page's state (life). when you try #{bean.customer} it will return same value. Because its object is same.
I would suggest to improve your code use another class for the view, layer your application. like
//Base class
public class Customer {
private String id;
/*
*Other fields
*/
//getter Setters
}
#ManagedBean
#RequestScoped
public class PageBackingBean implements Serializable{
List<Customer> customer = new ArrayList<>(); //can Hold more than one customer
public PageBackingBean(){
Customer cus1 = DataBase.loadByCustomerId(id);
customer.add(cus1);
Customer cus2 = DataBase.loadByCustomerId(id);
customer.add(cus2);
}
}
#SessionScoped public class User {
... //settings, attributes, etc
}
#ViewScoped public class FooController {
#ManagedProperty(value="#{user}")
private User user;
...
}
#RequestScoped public class LoginController {
#ManagedProperty(value="#{user}")
private User user;
public String login() {
//handle Servlet 3.0 based authenticate()... if success, make new User object and slap it into context
request.getSession().setAttribute("user", user);
return "?faces-redirect=true";
}
...
}
xhtml pages include the login controls on pretty much every page. The ideal would be that they are able to login, and the page will refresh and the existing FooController would have the reference to the currently logged in user, which conditionally renders buttons / elements. The behavior is that the login occurs, but the FooController view is still "valid", so the managed bean never is attempted to be injected again. If I navigate off the page, and back onto it [reconstructing the view scoped bean] the user bean is reinjected nicely... but I'd prefer not to have that interim step. Any ideas?
I've tried various forms of FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove("user"); in hopes that it will re-pull it from session but no avail. I do not want to tightly couple code in my LoginController to reference specifically invalidating a FooController or BarController or any other that refs the user bean.
Why are you trying to remove user (a #SessionBean), from the viewMap (a map of #ViewScoped objects in the current session)?
You should be removing FooController, the #ViewScoped, bean from the viewMap, i.e.
FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove("fooController");.
This is what will get rid of the viewscoped bean and force a new one to be created. Then of course, you'll need to refresh the page anyway.
If you're intent on removing the session bean however, you should be accessing the session directly:
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove("user");.
That gets rid of the user object
Ok, I figured this out on the drive home, it was a lifecycle problem. I was trying to get JSF to do something it shouldn't be doing to managed beans.
Instead of new'ing up a User object and reassigning it to the managed instance of user in LoginController, i changed the login method to look like this:
public String login() {
//handle Servlet 3.0 based authenticate()... if success...
User loadedFromDB = someDao.load(principal.getName());
user.setDefaultPage(loadedFromDB.getDefaultPage()); // NOTE: The user object IS THE MANAGED BEAN
user.setDefaultScheme(loadedFromDB.getDefaultScheme()); // This is the same object the view scoped bean has a ref on, so directly setting that object's fields proliferates that to any other bean that has the user in scope.
... //etc... not calling NEW, not reassigning object ref to user
loadedFromDB = null;
return "?faces-redirect=true";
}
This accomplishes what was needed. Who knew, if you stop fighting the framework for a minute and just use it, it helps you out :)
After I commit some data into the database I want my session beans to automatically refresh themselves to reflect the recently committed data. How do I achieve this when using managed session beans in JSF 2.0?
Currently I have to restart the web server in order for the sessions to clear and load anew again.
2 ways:
Put them in the view scope instead. Storing view-specific data sessionwide is a waste. If you have a performance concern, you should concentrate on implementing connection pooling, DB-level pagination and/or caching in the persistence layer (JPA2 for example supports second level caching).
#ManagedBean
#ViewScoped
public class FooBean {
// ...
}
Add a public load() method so that it can be invoked from the action method (if necessary, from another bean).
#ManagedBean
#SessionScoped
public class FooBean {
private List<Foo> foos;
#EJB
private FooService fooService;
#PostConstruct
public void load() {
foos = fooService.list();
}
// ...
}
which can be invoked in action method inside the same bean (if you submit the form to the very same managed bean of course):
public void submit() {
fooService.save(foos);
load();
}
or from an action method in another bean (for the case that your managed bean design is a bit off from usual):
#ManagedProperty("#{fooBean}")
private FooBean fooBean;
public void submit() {
fooService.save(foos);
fooBean.load();
}
This of course only affects the current session. If you'd like to affect other sessions as well, you should really consider putting them in the view scope instead, as suggested in the 1st way.
See also:
How to choose the right bean scope?
I need to preload some data to be displayed when the page loads. The initialization steps are performed on a #PostConstruct-annotated method but now i need to use a parameter in order to get the data.
What i'm trying to do:
#PostConstruct
public void init()
{
List data = getDataFromDB(parameter) /*Need to read a parameter created somewhere else*/
}
Is there a way to achieve this?
Thanks in advance
It's kind of hard to say what you mean by "a parameter set somewhere else". I will assume that "somewhere else" means "sent from browser by HTTP". In such case you should create a standard property in your managed bean and:
in JSF 2.0 you could annotate it with #ManagedProperty("#{param.nameOfParameterToRead}")
in JSF 1.2 and less - use managed-property element in your bean description (faces-config.xml).
Like this:
#ManagedBean
#RequestScoped
class MyManagedBean {
#ManagedProperty("#{param.id}")
public Integer id;
#PostConstruct
public void init(){
data = getDataFromDB(id)
}
// setters and getters (mandatory, even though annotation is on an attribute!!!)
}
Careful: injecting properties does not use JSF converters, so it is best to inject strings and take care of conversion in your own code.
how about reading from Properties file, or fetching List from DB ??