5.3. Managed BeansAs we've just seen, the JSF configuration file has two main sectionsone for navigation rules and one for bean definitions. The JSF framework relies on managed beans to glue the user interface (defined primarily in JSP using the JSF tag libraries) to the business logic of your application (which can be whatever sort of code you want). Managed beans are simply regular Java beans, configured within the faces-config.xml file. The bean class follows the standard JavaBeans rulesa zero-parameter constructor and a set of get/set methods for manipulating properties. In JSF, managed beans also contain action methods . Action methods are invoked by the JSF framework in response to a user action or an event and contain the code that actually manipulates the data model behind your application. In Struts, these methods would correspond to the Action classes that drive the application. A JSF application usually has two types of managed beansmodel beans and backing beans . The difference between the two is more architectural than technical. Model beans focus on integration with the data model of the application. In the Library application, we have model beans (in the com.oreilly.jent.jsf.library.model package) for tasks like managing the book collection itself and for keeping track of user accounts and privileges. Backing beans are linked more directly to the structure of the user interface. A backing bean contains action methods that manipulate the model beans on behalf of the UI and properties that relate directly to user interface elements embedded in HTML forms (we'll look at this in more detail in the next section). Remember that there's no technical difference between the two: you write and deploy them exactly the same way.[*]
Beans have four possible scopes: application, session, request, and scopeless. If a managed bean is defined as application scope, the entire application shares a single instance. For session-scoped beans, the JSF framework creates a new instance of the bean for each user when the bean is first accessed by the user. For request-scoped beans, the framework creates a new instance for each request, assuming that the request uses the bean at some point. Scopeless beans are accessed by other beans, but not directly by the application. They are instantiated when requested and will be garbage-collected like any other object once they pass out of scope. Note that the four scope options are not quite the same as the equivalent Servlet/JSP scopes, and to ensure compatibility, you'll want to access them through JSF rather than trying to pull values out of request attributes or HttpSession objects. Example 5-1 shows a simple backing bean, which we use for keeping track of information about the current user's session. It includes properties (currentUser and loggedIn) and an action method, logout( ). Example 5-1. UserSession.javaimport model.User; import logic.UserManager; public class UserSession { private User currentUser = null; public UserSession( ) { currentUser = UserManager.getGuestUser( ); } public User getCurrentUser( ) { return currentUser; } public void setCurrentUser(User currentUser) { this.currentUser = currentUser; } public boolean isLoggedIn( ) { if (currentUser == null) return false; if (currentUser == UserManager.getGuestUser( )) return false; return true; } public String logout( ) { currentUser = UserManager.getGuestUser( ); return "home"; } } Managed beans are set up via the JSF configuration file. The following example sets up three beans, a model bean stored in the application scope, and backing beans stored in the session and request scopes: <managed-bean> <managed-bean-name>library</managed-bean-name> <managed-bean-class> com.oreilly.jent.jsf.library.model.Library </managed-bean-class> <managed-bean-scope>application</managed-bean-scope> </managed-bean> <managed-bean> <managed-bean-name>usersession</managed-bean-name> <managed-bean-class> com.oreilly.jent.library.session.UserSession </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <managed-bean> <managed-bean-name>loginform</managed-bean-name> <managed-bean-class> com.oreilly.jent.library.backing.LoginForm </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean> Each bean definition has a name, a class, and a bean scope (application, session, request, or none). Sometimes we need to access managed beans directly within our code. We can do this using the FacesContext object, which provides access to a set of Map objects that are associated with the underlying JSF session. To retrieve the usersession managed bean from the session scope, we can use code like this at any time during request processing: UserSession userSession = (UserSession)FacesContext.getCurrentInstance( ) .getExternalContext( ).getSessionMap( ).get("usersession"); There's a major architectural problem with doing this too often, thoughyou become reliant on the usersession literal rather than having a truly type-safe way to access session data. You can improve this in two ways. The first is to establish a centralized directory that different classes in your application can use to look up objects. The Library application uses a Directory class (Example 5-2) to provide easy access to the Library object that we store in the application's scope. This example also shows how to retrieve managed beans by dynamically executing an expression written in the JSF EL. We'll look at the EL in the next section, so for now just be advised that #{library} means to retrieve a bean named library from the closest possible scope. There's no map maintained for application scope, so this technique is important. Example 5-2. Directory classpackage com.oreilly.jent.library; import com.oreilly.jent.library.model.Library; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.application.Application; public class Directory { public static Library getLibrary( ) { FacesContext context = FacesContext.getCurrentInstance( ); Application app = context.getApplication( ); ValueBinding binding = app.createValueBinding("#{library}"); Library library = (Library)binding.getValue(context); return library; } } The second way to reduce or eliminate dependencies on literal values in compiled code is to have the JSF engine populate the bean properties at runtime. If we want to give a managed bean access to another managed bean, we can also have the JSF framework provide the first bean with access to the second bean by passing the second bean to the first bean at runtime. In our application, we have a backing bean for the book display form that requires access to the library bean that we're managing via the JSF framework. The BookDisplayForm class just needs a simple setter method, like this: public void setLibrary(Library library) { this.library = library; } With the setter in place, we can use the <managed-property> tag in the Faces configuration file to execute an expression and populate the library property whenever an instance of BookDisplayForm is created: <managed-bean> <managed-bean-name>bookdisplayform</managed-bean-name> <managed-bean-class> com.oreilly.jent.jsf.library.backing.BookDisplayForm </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>library</property-name> <value>#{library}</value> </managed-property> </managed-bean> Remember to keep your scopes straight when setting up managed propertiesthe bean you're using as the property value must have an equal or broader scope. You can populate a request scope bean with session-scoped beans, but not vice versa. This is also where those scopeless beans (that is, beans with a scope of none) are useful: you can use them as injected property values for other managed beans without exposing them directly to the application. |