3.3 Struts Controller
|
Is the Action Part of the Controller or Model?
The various articles, tutorials, and other resources available on the Struts framework
Another reason to consider the Struts Action class part of the controller is that it has access to the ActionServlet , and therefore all of the controller resources, which the model domain shouldn't know about. Hypothetically, the Action class's behavior could have been left in the servlet, and the servlet would call the appropriate method on itself. If this were the case, there would be no doubt about whether this was controller or model functionality. With all of that said, the Action class may invoke operations on the business model, and many developers end up trying to insert too much of their business logic into the Action classes. Eventually, the line becomes blurry. Perhaps this is why some developers consider it part of the model. However, this book will take the approach that the Action class is part of the controller. |
The Struts
Action
class contains several
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception;
The
execute()
method is called by the controller when a request is received from a client. The controller creates an instance of the
Action
class if one doesn't already exist. The Struts framework will create only a single instance of each
Action
class in your application. Because there is only one instance for all users, you must ensure that all of your
Action
classes
Although the execute() method is not abstract, the default implementation returns null, so you will need to create your own Action class implementation and override this method.
There is some debate over how best to implement
Action
classes using Struts. Whether you create a different
Action
class for each operation or put several business operations in the same
Action
class is
|
For the banking application, we will create a unique Action class for each action that the user can perform:
Login
Logout
GetAccountInformation
GetAccountDetail
Each of the banking Action classes will extend the Struts Action class and will override the execute() method to carry out a specific operation. In Chapter 5, you'll learn that it's best to create an abstract base Action class, which all of your other Action classes extend. The application-specific base Action extends the Struts Action class and provides you with added flexibility and extensibility by allowing common Action behavior to be handled by a single parent class. For example, if you want to verify for each request that the user's session has not timed out, you can put this behavior in the abstract base Action before calling the subclass. For the banking application, however, things will be kept simple, and the actions will be direct descendants of the Struts Action class.
The com.oreilly.struts.banking.action.LoginAction class is shown in Example 3-3. It extends the Struts Action class and is invoked by the controller when a user attempts to log in to the banking application.
package com.oreilly.struts.banking.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
// Non Struts Imports
import com.oreilly.struts.banking.IConstants;
import com.oreilly.struts.banking.service.IAuthentication;
import com.oreilly.struts.banking.service.SecurityService;
import com.oreilly.struts.banking.service.InvalidLoginException;
import com.oreilly.struts.banking.view.UserView;
import com.oreilly.struts.banking.form.LoginForm;
/**
* This Action is called by the ActionServlet when a login attempt
* is made by the user. The ActionForm should be an instance of
* a LoginForm and contain the credentials needed by the SecurityService.
*/
public class LoginAction extends Action {
public ActionForward execute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response )
throws Exception {
// The ActionForward to return when completed
ActionForward forward = null;
UserView userView = null;
// Get the credentials from the LoginForm
String accessNbr = ((LoginForm)form).getAccessNumber( );
String pinNbr = ((LoginForm)form).getPinNumber( );
/*
* In a real application, you would typically get a reference
* to a security service through something like JNDI or a factory.
*/
IAuthentication service = new SecurityService( );
// Attempt to log in
userView = service.login(accessNbr, pinNbr);
// Since an exception wasn't thrown, login was successful
// Invalidate existing session if it exists
HttpSession session = request.getSession(false);
if(session != null) {
session.invalidate( );
}
// Create a new session for this user
session = request.getSession(true);
// Store the UserView into the session and return
session.setAttribute( IConstants.USER_VIEW_KEY, userView );
forward = mapping.findForward(IConstants.SUCCESS_KEY );
return forward;
}
}
The LoginAction in Example 3-3 gets the credentials from the ActionForm that was passed in as an argument in the execute() method.
|
A
SecurityService
is then created, and the security credentials are passed to the
login()
method. If the login succeeds, a new
HttpSession
is created for the user, and the
UserView
returned from the
login()
method is put into the session. If authentication fails, an
InvalidLoginException
is thrown. Note that there is no try/catch block for the
InvalidLoginException
in the
execute( )
method. This is because one of the new features of Struts 1.1 is its declarative exception-handling capabilities, which remove much of the
<global-exceptions>
<exception
key="global.error.invalidlogin"
path="/login.jsp"
scope="request"
type="com.oreilly.struts.banking.service.InvalidLoginException"/>
</global-exceptions>
This fragment from the banking configuration file
At this point, you might be asking yourself, "How does the controller know which Action instance to invoke when it receives a request?" It determines this by inspecting the request information and using a set of action mappings.
Action mappings are part of the Struts configuration information that is configured in a special XML file. This configuration information is loaded into memory at startup and made available to the framework at runtime. Each action element is represented in memory by an instance of the org.apache.struts.action. ActionMapping class. The ActionMapping object contains a path attribute that is matched against a portion of the URI of the incoming request. We'll talk more about action mappings and the Struts configuration file in Chapter 4.
The following XML fragment illustrates the login action mapping from the configuration file used by the banking application:
<action
path="/login
"
type="com.oreilly.struts.banking.action.LoginAction"
scope="request"
name="loginForm"
validate="true"
input="/login.jsp">
<forward name="Success" path="/action/getaccountinformation" redirect="true"/>
<forward name="Failure" path="/login.jsp" redirect="true"/>
</action>
The login action mapping shown here maps the path "/login" to the Action class com.oreilly.struts.banking.LoginAction . Whenever the controller receives a request where the path in the URI contains the string "/login", the execute() method of the LoginAction instance will be invoked. The Struts framework also uses the mappings to identify the resource to forward the user to once the action has completed. We'll talk more about configuring action mappings in Chapter 4.
So far, we've discussed how the controller receives the request and determines the correct Action instance to invoke. What hasn't been discussed is how or what determines the view to return to the client.
If you
The action forwards are specified in the configuration file, similar to action mappings. They can be specified at an Action level, as this forward is for the logout action mapping:
<action
path="/logout"
type="com.oreilly.struts.banking.action.LogoutAction"
scope="request">
<forward name="Success" path="/login.jsp" redirect="true"/>
</action>
The logout action declares a forward element named "Success", which forwards to the "/login.jsp" resource. The redirect attribute is specified and set to "true". Now, instead of performing a forward using a RequestDispatcher , the request will be redirected.
The action forward mappings also can be specified in a global section, independent of any specific action mapping. In the previous case, only the logout action mapping could reference the action forward named "Success", However, all action mappings can reference forwards declared in the global-forwards section. Here is an example global-forwards section from the banking configuration file:
<global-forwards> <forward name="SystemFailure" path="/systemerror.jsp" /> <forward name="SessionTimeOut" path="/sessiontimeout.jsp" /> </global-forwards>
The forwards defined in this section are more general and don't apply to specific action mappings. Notice that every forward must have a name and path , but the redirect attribute is optional. If you don't specify a redirect attribute, its default value of "false" is used, and thus the framework will perform a forward. The path attribute in an action forward also can specify another Struts Action . You'll see an example of how to do this in Chapter 5.
Now that you understand from a high level how the Struts controller components operate, it's time to look at the next piece of the MVC puzzle -the model.