서블릿에서 고려할 수 있는 오류 처리방법으로 다음의 방법들이 있다.
- 실행코드를 try-catch 블록으로 구성한다.
- 메소드 선언부에 throws 절을 선언한다.
- 강제로 예외를 발생시키기
- 사용자 예외를 생성하여 처리
- web.xml에 오류 처리 설정을 한다.
우선 다음의 코드를 작성해 보자. 이 코드는 몇가지 문제가 존재한다.
ErrorTestServlet.java
package job.study.web; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class ErrorTestServlet */ @WebServlet("/errorTest") public class ErrorTestServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public ErrorTestServlet() { super(); // TODO Auto-generated constructor stub } /** * @throws IOException * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO Auto-generated method stub response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); String getquery = request.getQueryString(); try { out.println("Query : " + getquery + "<br>"); out.println("Query길이 : " + getquery.length() + "<br>"); out.println("Done!"); } catch(NullPointerException e) { out.println("요청을 처리하는 동안 오류가 발생하였습니다 : "+e); } out.close(); } }
첫번째 문제는 이클립스에서 이 코드를 보면 다음과 같은 getWriter( ) 메소드에 대해서 "Unhandled exception type IOException"이라는 메시지가 나타난다. 그 이유는 getWriter( ) 메소드 호출시 getWriter( ) 메소드 내부에서 발생 가능성이 이는 IOException 예외처리를 메소드 내부에서 처리하지 않고 getWriter( ) 메소드를 호출하는 곳에서 처리하도록 자체에서 throws 절을 정의하고 있기 때문이다.
이에 대해 이클립스에서 두가지 방법을 권고하고 있다.
- doGet( ) 메소드에 throws 절을 선언하는 것,
- 또는 해당하는 코드를 try-catch 블록으로 감싸는 것이다.
doGet( )에 throws 절을 선언하자
IOException에 대한 예외처리를 아래처럼 doGet( ) 메소드에 IOException을 throws하는 정의를 추가해보자 그럼 권고와 관련된 메시지가 사라진 것을 알 수 있다. 편집기에서는 더이상 문제가 발생할 부분은 보이지 않는다. doPost( ) 메소드를 사용한다면 이 경우에도 마찬가지로 throws 절 선언이 가능하다.
주의할 것은! 재정의한 메소드라면 선언부를 변경할 수 없어서 원래 메소드에서 선언하고 있는 Exception 외에는 추가로 선언할 수 없습니다. 이럴 때는 반드시 try-catch 문으로 오류 처리를 구현해 주어야 한다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO Auto-generated method stub response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); String getquery = request.getQueryString(); out.println("Query : " + getquery + "<br>"); out.println("Query길이 : " + getquery.length() + "<br>"); out.println("Done!"); out.close(); }
그렇지만 아직 문제가 있다. 일단 실행 결과를 보자.
실행 중 NullPointerException 예외 오류가 발생하는 것을 확인할 수 있다.
그이유는 코드를 살펴보면 GET으로 전달된 질의 문자열(QueryString)의 문자열 길이를 구하는 메소드를 실행하고 있다. 여기에 두번째 문제가 있다. ErrorTestServlet에 접근할 때 전달된 질의 문자열이 없으므로 getQueryString( ) 메소드의 리턴값은 null이다.
그리고 이 null을 참조하여 문자열의 길이를 구하려 하는데, 이때 JVM에 의해 NullPointerException 이 생성되며, JVM은 오류에 대응하는 코드가 구현되어 있는지 검사합니다. 만일 오류 처리가 구현되어 있지 않다면 실행 중이던 프로그램을 강제로 중단합니다. 앞에서의 실행 결과에서 프로그램이 강제로 중단된 모습을 확인할 수 있습니다.
try-catch 를 이용해 오류를 처리해보자
try-catch는 오류가 발생했을 때 프로그램 실행이 강제로 중단되지 않도록, 프로그램 내에서 발생한 오류를 적적하게 처리할 수 있게 해준다.
가장 명심해야 할 것은 서블릿을 벗어나면 우리가 이 예외처리의 흐름을 제어하여 에러를 적절하게 처리 수 있는 곳이 없다. 또는 진입부를 작성하는 코드 역시나 이를 벗어나면 예외를 처리할 수 있는 곳이 없다. 이러한 곳을 최후의 보류라 볼 수 있다. 그렇기 떄문에 이런 경우는내부에서 처리할 수 밖에 없다.
그럼 위의 코드에서 try-catch를 이용해 doGet( ) 메소드 내부에서 NullPointerException 처리할 수 있도록 보완해보자.
ErrorTestServlet.java
다음의 코드에서는 NullPointerException이 발생하면 이를 try-catch 문으로 내부에서 처리하고 있다. 이때 catch 문 순서대로 발생한 오류 객체와 일치하는 타입의 변수를 살펴봅니다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO Auto-generated method stub response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); String getquery = request.getQueryString(); try { out.println("Query : " + getquery + "<br>"); out.println("Query길이 : " + getquery.length() + "<br>"); out.println("Done!"); }catch(NullPointerException e) { out.println("전달된 질의문자열이 없습니다. : "+e); }catch(Exception e) { //catch문은 처음 부터 검색하여 예외가 일치하는 catch문의 블록을 실행한다. }finally { //try-catch 문을 끝마치면 반드시 실행딘다. } out.close(); }
실행결과
catch문을 작성할 때 주의할 것은 Exception은 모든 예외 객체의 최상의 객체이므로 일치하는 타입의 catch문이 없을 경우 Exception 타입으로 선언된 catch문이 동작하게 끔 마지막에 정의해줍니다.
그렇지 않으면 다음의 그림처럼 Exception 객체 이후의 예외 catch문에 도달할 수 없다고 표시되면서 에러가 발생합니다.
사용자 예외 클래스를 작성하고 강제로 예외를 발생시켜보자.
이번에는 사용자 예외를 생성하고 전달된 질의문자열이 없을 때 getQueryString( )메소드의 값이 null인 경우 강제로 사용자 예외를 발생시키도록 해보자
사용자 예외 클래스 생성하기 - QueryStringNullException.java
package job.study.web; public class QueryStringNullException extends ServletException { public QueryStringNullException() { // TODO Auto-generated constructor stub super("전달된 질의 문자열이 없습니다."); } }
ErrorTestServlet.java
doGet( ) 메소드에서 질의문자열 값이 null이면 를 예외강제로 발생하도록 수정한다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO Auto-generated method stub response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); String getquery = request.getQueryString(); try { out.println("Query : " + getquery + "<br>"); //질의문자열 값이 null이면 사용자예외를 강제로 발생시킨다. if(getquery==null ) throw new QueryStringNullException(); out.println("Query길이 : " + getquery.length() + "<br>"); out.println("Done!"); }catch(QueryStringNullException e) { out.println("요청을 처리하는 동안 오류가 발생하였습니다 : <br/>"); out.println(e); }catch(Exception e) { //catch문은 처음 부터 검색하여 예외가 일치하는 catch문의 블록을 실행한다. }finally { //try-catch 문을 끝마치면 반드시 실행딘다. } out.close(); }
실행결과
마지막으로, web.xml을 이용해 웹 애플리케이션에서 발생하는 오류를 처리하는 페이지를 지정할 수도 있다.
web.xml
<error-page> <error-code>405</error-code> <location>/errorHandle</location> </error-page> <error-page> <exception-type>job.study.web.QueryStringNullException</exception-type> <location>/errorHandle</location> </error-page>
ErrorHandleServlet.java
package job.study.web; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class ErrorHandleServlet */ @WebServlet("/errorHandle") public class ErrorHandleServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public ErrorHandleServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); //발생된 오류의 코드정보 Integer code = (Integer) request.getAttribute("javax.servlet.error.status_code"); //발생된 오류 객체의 타입 정보를 가지고 있는 Class형 객체 String message = (String) request.getAttribute("javax.servlet.error.message"); //발생된 오류의 메시지 정보 Object type = request.getAttribute("javax.servlet.error.exception_type"); //발생된 오류 객체 Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception");; //오류가 발생된 페이지의 URI정보 String uri = (String) request.getAttribute("javax.servlet.error.request_uri"); out.println("<h2>Error Code : " + code + "</h2>"); out.println("<h2>Error Message : " + message + "</h2>"); out.println("<h2>Error Type : " + type + "</h2>"); out.println("<h2>Error Object : " + exception + "</h2>"); out.println("<h2>Error URI : " + uri + "</h2>"); out.close(); } }
ErrorTestServlet.java의 doGet() 을 다음와 같이 수정하자.
참고할 것은 doGet( ) 메소드의 throws 정의에 ServletException이 추가되었고 코드를 보면 ServletException을 상속 받아 구현한 QueryStringNullException 예외 객체를 throw하고 있는 것을 볼 수 있다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException { // TODO Auto-generated method stub response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); String getquery = request.getQueryString(); out.println("Query : " + getquery + "<br>"); //질의문자열 값이 null이면 사용자예외를 강제로 발생시킨다. if(getquery==null ) throw new QueryStringNullException(); out.println("Query길이 : " + getquery.length() + "<br>"); out.println("Done!"); out.close(); }
실행결과