Refactor large Controllers
Implementations of HttpServlet are sometimes informally called Controller classes. They form the entry point of client HTTP requests into a web app. Controller classes should be fairly compact, and their code should in general be at a high level of abstraction. Controller classes do a lot of work, but most of their work should be performed by delegating to other classes. A well designed application will have a Controller which is not excessively lengthy. If you use a framework, the Controller class is often defined as part of the framework, and not by your application.
Examples of tasks which can be delegated by the Controller to another class include:
- translating raw user requests into problem domain objects
- logging user request statistics
- validating user input
- interacting with the database
- ensuring necessary objects are in scope for subsequent display
- forwarding to a JSP for display to the user
- handling any exceptions thrown by the application
Example
Here is part of the Controller from the WEB4J framework. It delegates most of its work to:- a RequestParser, a higher level wrapper around the underlying request
- an implementation of an Action interface
- a ResponsePage, to represent the final page served to the user
- an ApplicationFirewall, to verify basic sanity of the request
- a TroubleTicket, to gather diagnostic information, and email it to the webmaster
/** * Controller that delegates to various framework classes. */ public class Controller extends HttpServlet { //..elided /** Call {@link #processRequest}. */ @Override public final void doGet( HttpServletRequest aRequest, HttpServletResponse aResponse ) throws ServletException, IOException { processRequest(aRequest, aResponse); } /** Call {@link #processRequest}. */ @Override public final void doPost( HttpServletRequest aRequest, HttpServletResponse aResponse ) throws ServletException, IOException { processRequest(aRequest, aResponse); } /** * Handle all HTTP <tt>GET</tt> and <tt>POST</tt> requests. * * <P>This method can be overridden, if desired. Most applications will not need * to override this method. * * <P>Operations include : * <ul> * <li>set the request character encoding to the value configured in <tt>web.xml</tt> * <li>get an instance of {@link RequestParser} * <li>get its {@link Action}, and execute it * <li>perform either a forward or a redirect to its * {@link hirondelle.web4j.action.ResponsePage} * <li>if an unexpected problem occurs, create a {@link TroubleTicket}, log it, and * email it to the webmaster email address configured in <tt>web.xml</tt> * <li>if the response time exceeds a configured threshold, build a * {@link TroubleTicket}, log it, and email it to the webmaster address configured * in <tt>web.xml</tt> * </ul> */ protected void processRequest( HttpServletRequest aRequest, HttpServletResponse aResponse ) throws ServletException, IOException { Stopwatch stopwatch = new Stopwatch(); stopwatch.start(); RequestParser requestParser = RequestParser.getInstance(aRequest, aResponse); try { Action action = requestParser.getWebAction(); ApplicationFirewall appFirewall = BuildImpl.forApplicationFirewall(); appFirewall.doHardValidation(action, requestParser); ResponsePage responsePage = action.execute(); if ( responsePage.getIsRedirect() ) { redirect(responsePage, aResponse); } else { forward(responsePage, aRequest, aResponse); } } catch (BadRequestException ex){ if( Util.textHasContent(ex.getErrorMessage()) ){ aResponse.sendError(ex.getStatusCode(), ex.getErrorMessage()); } else { aResponse.sendError(ex.getStatusCode()); } } catch (Throwable ex) { //Bugs OR rare conditions, for example datastore failure logAndEmailSeriousProblem(ex, aRequest); aResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); } stopwatch.stop(); if ( stopwatch.toValue() >= fPOOR_PERFORMANCE_THRESHOLD ) { logAndEmailPerformanceProblem(stopwatch.toValue(), aRequest); } } /** * Inform the webmaster of an unexpected problem with the deployed application. * * <P>Typically called when an unexpected <tt>Exception</tt> occurs in * {@link #processRequest}. Uses {@link TroubleTicket#mailToWebmaster}. * * <P>Also, stores the trouble ticket in application scope, for possible * later examination. */ protected final void logAndEmailSeriousProblem ( Throwable ex, HttpServletRequest aRequest ) throws AppException { TroubleTicket troubleTicket = new TroubleTicket(ex, aRequest); fLogger.severe("TOP LEVEL CATCHING Throwable"); fLogger.severe( troubleTicket.toString() ); log("SERIOUS PROBLEM OCCURRED."); log( troubleTicket.toString() ); aRequest.getSession().getServletContext().setAttribute( MOST_RECENT_TROUBLE_TICKET, troubleTicket ); troubleTicket.mailToWebmaster(); } /** * Inform the webmaster of a performance problem. * * <P>Called only when the response time of a request is above the threshold * value configured in <tt>web.xml</tt>. * * <P>Builds a <tt>Throwable</tt> with a description of the problem, then creates and * emails a {@link TroubleTicket} to the webmaster. * * @param aMilliseconds response time of a request in milliseconds */ protected final void logAndEmailPerformanceProblem( long aMilliseconds, HttpServletRequest aRequest ) throws AppException { String message = "Response time of web application exceeds configured performance threshold." + NEW_LINE + "Time : " + aMilliseconds + " milliseconds." ; Throwable ex = new Throwable(message); TroubleTicket troubleTicket = new TroubleTicket(ex, aRequest); fLogger.severe("Poor response time : " + aMilliseconds + " milliseconds"); fLogger.severe(troubleTicket.toString()); log("Poor response time : " + aMilliseconds + " milliseconds"); log(troubleTicket.toString()); troubleTicket.mailToWebmaster(); } //..elided }
See Also :
Use Model-View-Controller framework
Parse parameters into domain objects
Command objects
Send trouble-ticket emails
A Web App Framework - WEB4J
Parse parameters into domain objects
Command objects
Send trouble-ticket emails
A Web App Framework - WEB4J
Would you use this technique?