<< Step into the J2EE architecture and process | 首页 | Java 多线程的Thread类和Runnable接口 >>

Exception Handling in Web Applications

Exception Handling in Web Applications

Posted by crazybob on February 06, 2004 at 01:45 PM | Comments (8)

If you've ever diagnosed a bug in a web application, you've undoubtedly experienced annoyance digging through a list of fifteen exception stack traces trying to identify the one you're interested in (if it's even present), or a sinking feeling when you tailed the web server log only to find:

  java.lang.NullPointerException

I sure have. The output to the browser client helps even less, typically churning out the de facto, "Cannot display page," message. Avoid these symptoms of exception handling pitfalls and troubleshoot web applications effortlessly with this simple recipe.

AntiPattern: T.M.I. (Too Much Information)

The first exception throwing antipattern occurs when we repeatedly log or wrap and rethrow an exception every time we catch it:

  catch (Exception e) {
    e.printStackTrace();
    throw new WrappingException(e);
  }

The application prints the stack trace and rewraps the exception fifteen times before it finally propagates to the top where we're often subjected to the stack trace for each wrapper exception. Our visual noise filters go into overload as we sift through a proverbial log hay stack in search of the one stack trace we actually care about.

AntiPattern: Lie of Omission

The second antipattern surfaces when we trash the original stack trace:

  catch (Exception e) {
    // print message only.
    System.err.println(e);
    throw new WrapperException(e.getMessage()); 
  }

If we look at the web server log, we'll see our WrapperException instance's stack trace, which will point to where we caught the original exception as opposed to where it was thrown. Where did the original error occur? Your guess is as good as mine.

AntiPattern: Head in the Sand

This brings us to our final, most evil antipattern, ignoring exceptions:

  catch (Exception e) {
    e.printStackTrace();
  }

Printing the stack trace and going on about your business is on par with trying to drive a car after the wheel has fallen off. Doing so leaves the system in an unpredictable state, often leading to security holes and code that's insanely difficult to debug. It's the modern day incarnation of a segmentation fault.

The moral of this story: don't be afraid to throw your hands in the air and refuse to go on. People entrusting you with their credit card numbers may not thank you, but they'll hold on to their identities a little longer.

Solution: NestedException

Failing early helps avoid these pitfalls. The original stack trace in addition to other pertinent state information (user IDs, primary keys, method arguments, etc.) is a troubleshooter's best friend. I've found that when faced with a checked exception I can't possibly handle, it's best to wrap the exception in a runtime exception (once) and throw it to the top where it can ultimately be thrown to the web container. The exception propagates to the top sans explicit handling until the web container catches and logs it once and only once.

We can accomplish this with a class called NestedException which I originally inherited from my friend and mentor Tim Williams and mentioned in my book Bitter EJB. NestedException wraps exceptions only when necessary (so we don't end up with exceptions nested fifteen deep) and keeps the original stack trace intact. The catch block becomes:

  catch (CheckedException e) {
    throw NestedException.wrap(e);
  }

NestedException consists of a simple wrapper class and static factory method, wrap(Throwable). NestedException overrides all methods to delegate to the wrapped exception (you don't even need to unwrap it to get the message or stack trace you're really interested in):

  public class NestedException extends RuntimeException {

    Throwable throwable;

    private NestedException(Throwable t) {
      this.throwable = t;
    }

    /** Wraps another exeception in a RuntimeException. */
    public static RuntimeException wrap(Throwable t) {
      if (t instanceof RuntimeException) 
        return (RuntimeException) t;
      return new NestedException(t);
    }

    public Throwable getCause() {
      return this.throwable;
    }

    public void printStackTrace() {
      this.throwable.printStackTrace();
    }

    ...
  }

Create a Default Error Page

Now that we've scrubbed our logs, we're left with the matter of output to the web browser. From my experience, only the finest of quality assurance testers take time to look at server logs. I often see bugs filed with the following description:

Did so and so... Browser said "Error 500: Cannot display page."

Not a lot of help. On the other hand, most testers are willing to cut and paste browser output into an issue tracking system. A simple solution is to configure your web application's default error page in the WEB-INF/web.xml file:

  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error.jsp</location>
  </error-page>

Now, when we throw an exception to the container, it forwards the request to /error.jsp providing the exception instance as an implicit variable called "exception" (go figure). The error page is a simple JSP with the isErrorPage page directive set to "true":

  <%@ page isErrorPage="true" import="java.io.PrintWriter" %>

  <html><body>

  <h1 style="color: red">Error</h1>

  <pre>
  <%
  // unwrap ServletExceptions.
  while (exception instanceof ServletException)
    exception = ((ServletException) exception).getRootCause();

  // print stack trace.
  exception.printStackTrace(new PrintWriter(out));
  %>
  </pre>

  </body></html>

The resulting page looks something like this:

Error

com.mycompany.ApplicationException
  at _TestError._jspService(_TestError.java:65)
  at com.vendor.SomeClass.service(SomeClass.java:89)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
  ...

标签 :



发表评论 发送引用通报