|
Recipe 9.1. Simplifying Exception Processing in an ActionProblemYou want to reduce the number of TRy . . . catch blocks within your Action classes. SolutionRemove the exception-handling code from your Action, and define global and local exception handlers in your struts-config.xml file, as shown in Example 9-1. Example 9-1. Global and local exception handling (partial)... <global-exceptions> <exception key="error.unknown.user" type="com.oreilly.strutsckbk.ch09.UnknownUserException" path="/securityError.jsp"/> </global-exceptions> ... <action-mappings> <action path="/Login" type="com.oreilly.strutsckbk.ch09.LoginAction" scope="request" name="LoginForm" validate="true" input="/login.jsp"> <exception key="error.password.match" type="com.oreilly.strutsckbk.ch09.PasswordMatchException"> <forward name="success" path="/login_success.jsp"/> </action> ... DiscussionPrior to Struts 1.1, the handling of exceptions was left to the devloper's devices. Exceptions returned from calls to the business layer from an Action had to be handled individually in your code. Because the perform( ) method of Struts 1.0 only allowed you to throw IOException and ServletException, you didn't have much choice in the matter: public ActionForward perform( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ... Any checked exception thrown within the body of perform() had to be caught and handled. In some cases, it was appropriate to catch the exception, generate an ActionError, and forward to the input page, much like a validation failure. But more often, the exception couldn't be handled by the application, so the developer returned a ServletException wrapped around the application exception. With Struts 1.1, the perform() method was deprecated and the execute( ) method was introduced. Unlike perform(), execute( ) can throw any exception, and once an exception is thrown, you can process using exception handlers configured in your struts-config.xml file. If you migrated an Action class from Struts 1.0 to Struts without using declarative exception handling, the class might look something like Example 9-2. Example 9-2. Action without declarative exception handlingpackage com.oreilly.strutsckbk.ch09; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.beanutils.PropertyUtils; import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class LoginAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String username = null; String password = null; ActionErrors errors = new ActionErrors( ); try { username = (String) PropertyUtils.getSimpleProperty(form, "username"); password = (String) PropertyUtils.getSimpleProperty(form, "password"); } catch (Exception e) { throw new IOException("Unable to retrieve username and password"); } SecurityService service = new SecurityService( ); try { service.authenticate( username, password); } catch (UnknownUserException e1) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.unknown.user")); saveErrors(request, errors); return mapping.findForward("securityError"); } catch (PasswordMatchException e) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.password.match")); } // Report any errors we have discovered back to the original form if (!errors.isEmpty( )) { saveErrors(request, errors); return (mapping.getInputForward( )); } User user = new User( ); user.setUsername(username); request.getSession( ).setAttribute("user", user); return mapping.findForward("success"); } } There's a lot of code here whose sole purpose is handling exceptions. The first try . . . catch block handles technical exceptions thrown in getting data from the form. The more interesting application exceptions are thrown by the SecurityService. The UnknownUserException is handled by generating an error, saving the errors in the request, and forwarding to a Struts forward. The PasswordMatchException is handled similarly, except that the page is forwarded to the input path for the action, where the error will probably be reported and the user can try again. All of this exception-handling code can be eliminated by using declarative exception handling. Example 9-3 shows the LoginAction with the exception handling removed. Example 9-3. LoginAction without exception handlingpackage com.oreilly.strutsckbk.ch09; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.beanutils.PropertyUtils; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class LoginAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String username = (String) PropertyUtils.getSimpleProperty(form, "username"); String password = (String) PropertyUtils.getSimpleProperty(form, "password"); SecurityService service = new SecurityService( ); service.authenticate( username, password); User user = new User( ); user.setUsername(username); request.getSession( ).setAttribute("user", user); return mapping.findForward("success"); } } The exceptions are now handled declaratively, as shown in the Solution. The UnknownUserException is declared as a global exception: <global-exceptions> <exception key="error.unknown.user" type="com.oreilly.strutsckbk.ch09.UnknownUserException" path="/securityError.jsp"/> ... </global-exceptions> The key specifies a MessageResources message that will be used to generate an ActionError. The path specifies the name of the resource to forward to. If path is omitted, the input page for an action will be used. The generated ActionError is stored in an ActionErrors object and can be displayed on the destination page using the <html:errors/> tag.
Because this exception is declared as a global-exception, any action that throws an UnknownUserException will be handled by this declaration unless overridden by a local exception. A local exception is defined by nesting the exception element within the action element to which it applies. It defines action-specific handling for specified types of exceptions. The PasswordMatchException is handled by a local exception: <action path="/Login" ...> <exception key="error.password.match" type="com.oreilly.strutsckbk.ch09.PasswordMatchException" path="/login.jsp"/> <forward .../> </action> So what happens when you throw an exception that doesn't have a declared handler? These exceptions will be wrapped in a ServletException and ultimately thrown by the ActionServlet.service( ) method to be handled by the application server. See AlsoFor more information on custom display of error messages, see Recipe 9-6. You can provide custom exception handling by extending the Struts exception handler. This approach is discussed in Recipe 9.2. If you want to provide a fallback exception-handling approach instead of relying on the container, take a look at Recipe 9.4. |
|