Hôm nay, chúng ta sẽ tìm hiểu về cách xử lý Exception và Error trong Servlet. Trước đây tôi đã viết một bài về xử lý Exception trong Java, tuy nhiên khi phát triển ứng dụng web, cơ chế xử lý exception thông thường của Java là chưa đủ, vậy nên chúng ta cần các phương pháp phù hợp hơn với ngữ cảnh web.

Xử lý Exception trong Servlet
Nếu bạn để ý, các phương thức doGet() và doPost() ném ra javax.servlet.ServletException và IOException. Giờ thì hãy cùng xem điều gì xảy ra khi chúng ta ném các exception này ra từ ứng dụng. Tôi sẽ viết một servlet đơn giản để ném ra ServletException.
package com.journaldev.servlet.exception;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/MyExceptionServlet")
public class MyExceptionServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
throw new ServletException("GET method is not supported.");
}
}
Khi chúng ta truy cập servlet này qua trình duyệt bằng phương thức GET, phản hồi nhận được sẽ giống như hình minh họa bên dưới.

Vì trình duyệt chỉ hiểu nội dung HTML, nên khi ứng dụng của chúng ta ném ra exception, servlet container sẽ xử lý exception đó và tạo ra phản hồi dưới dạng HTML. Cách xử lý này phụ thuộc vào từng servlet container. Trong trường hợp này, tôi đang sử dụng Tomcat nên nhận được trang lỗi như vậy. Nếu bạn sử dụng các máy chủ khác như JBoss hoặc GlassFish, phản hồi HTML lỗi có thể sẽ khác.
Vấn đề là phản hồi này không mang lại giá trị gì cho người dùng. Ngoài ra, nó còn hiển thị các lớp trong ứng dụng và thông tin về máy chủ cho người dùng, điều này không chỉ vô ích mà còn không an toàn về mặt bảo mật.
Lỗi Servlet
Chắc hẳn bạn đã từng gặp lỗi 404 khi truy cập vào một URL không tồn tại. Hãy xem servlet container phản hồi như thế nào khi xảy ra lỗi 404. Nếu chúng ta gửi yêu cầu đến một URL không hợp lệ, sẽ nhận được phản hồi HTML giống như hình minh họa bên dưới.

Một lần nữa, đây là HTML chung do máy chủ tạo thay mặt cho ứng dụng của chúng ta và hầu như không có giá trị gì đối với người dùng.
Xử lý Exception và Error trong Servlet
Servlet API hỗ trợ thiết lập các servlet xử lý Exception và Error tùy chỉnh, có thể cấu hình thông qua deployment descriptor. Mục đích của các servlet này là xử lý các Exception hoặc Error phát sinh trong ứng dụng và trả về phản hồi HTML có ích cho người dùng. Chúng ta có thể cung cấp liên kết về trang chủ của ứng dụng hoặc hiển thị một số thông tin giúp người dùng hiểu được điều gì đã xảy ra.
Trước tiên, chúng ta cần tạo một servlet để xử lý Exception và Error tùy chỉnh. Có thể khai báo nhiều servlet khác nhau để xử lý các loại exception và error riêng biệt, nhưng để đơn giản, tôi sẽ tạo một servlet duy nhất và sử dụng cho cả hai trường hợp Exception và Error. Dưới đây là mã nguồn của AppExceptionHandler.java:
package com.journaldev.servlet.exception;
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;
@WebServlet("/AppExceptionHandler")
public class AppExceptionHandler extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processError(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processError(request, response);
}
private void processError(HttpServletRequest request,
HttpServletResponse response) throws IOException {
// Phân tích Exception trong servlet
Throwable throwable = (Throwable) request
.getAttribute("javax.servlet.error.exception");
Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
String servletName = (String) request
.getAttribute("javax.servlet.error.servlet_name");
if (servletName == null) {
servletName = "Unknown";
}
String requestUri = (String) request
.getAttribute("javax.servlet.error.request_uri");
if (requestUri == null) {
requestUri = "Unknown";
}
// Thiết lập kiểu nội dung phản hồi
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.write("<html><head><title>Exception/Error Details</title></head><body>");
if(statusCode != 500){
out.write("<h3>Error Details</h3>");
out.write("<strong>Status Code</strong>:"+statusCode+"<br>");
out.write("<strong>Requested URI</strong>:"+requestUri);
}else{
out.write("<h3>Exception Details</h3>");
out.write("<ul><li>Servlet Name:"+servletName+"</li>");
out.write("<li>Exception Name:"+throwable.getClass().getName()+"</li>");
out.write("<li>Requested URI:"+requestUri+"</li>");
out.write("<li>Exception Message:"+throwable.getMessage()+"</li>");
out.write("</ul>");
}
out.write("<br><br>");
out.write("<a href=\\"index.html\\">Home Page</a>");
out.write("</body></html>");
}
}
Hãy cùng xem cách cấu hình servlet này trong tập tin mô tả triển khai (deployment descriptor), sau đó chúng ta sẽ phân tích cách triển khai và hoạt động của nó.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>" xmlns="<https://java.sun.com/xml/ns/javaee>" xsi:schemaLocation="<https://java.sun.com/xml/ns/javaee> <https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd>" version="3.0">
<display-name>ServletExceptionHandling</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<error-page>
<error-code>404</error-code>
<location>/AppExceptionHandler</location>
</error-page>
<error-page>
<exception-type>javax.servlet.ServletException</exception-type>
<location>/AppExceptionHandler</location>
</error-page>
</web-app>
Như bạn thấy, việc khai báo servlet xử lý Exception cho ứng dụng được thực hiện rất đơn giản thông qua phần tử error-page. Mỗi phần tử error-page phải có một trong hai phần tử con là error-code hoặc exception-type. Chúng ta chỉ định servlet xử lý lỗi thông qua phần tử location. Với cấu hình ở trên, nếu ứng dụng phát sinh lỗi 404 hoặc ServletException, thì servlet AppExceptionHandler sẽ được sử dụng để xử lý.
Khi xảy ra lỗi hoặc exception, servlet container sẽ gọi phương thức HTTP tương ứng của servlet xử lý Exception và truyền vào các đối tượng request và response. Lưu ý rằng tôi đã triển khai cả hai phương thức doGet() và doPost() để có thể xử lý cả yêu cầu GET lẫn POST, đồng thời sử dụng một phương thức chung processError() để thực hiện xử lý lỗi.
Trước khi gọi servlet để xử lý exception, servlet container sẽ thiết lập một số thuộc tính trong đối tượng request để cung cấp thông tin liên quan đến lỗi. Một số thuộc tính đó bao gồm javax.servlet.error.exception, javax.servlet.error.status_code, javax.servlet.error.servlet_name và javax.servlet.error.request_uri.
Đối với exception, mã trạng thái luôn là 500, tương ứng với lỗi “Internal Server Error”. Đối với các lỗi khác, chúng ta sẽ nhận được mã lỗi tương ứng như 404, 403… Dựa vào mã trạng thái, phần triển khai sẽ hiển thị các loại phản hồi HTML khác nhau cho người dùng. Ngoài ra, còn có liên kết quay về trang chủ của ứng dụng.
Khi truy cập vào servlet đang ném ServletException, chúng ta sẽ nhận được phản hồi như hình minh họa bên dưới.
Tương tự, nếu cố gắng truy cập vào một URL không hợp lệ dẫn đến phản hồi 404, chúng ta cũng sẽ nhận được phản hồi như hình minh họa bên dưới.
Giao diện này trông rõ ràng hơn và giúp người dùng dễ dàng hiểu điều gì đã xảy ra, đồng thời cung cấp cách để họ quay lại đúng vị trí. Nó cũng tránh việc lộ thông tin nhạy cảm của ứng dụng tới người dùng.
Chúng ta nên luôn cấu hình các trình xử lý exception cho ứng dụng web của mình. Nếu bạn muốn xử lý tất cả các runtime exception cũng như mọi exception khác chỉ trong một trình xử lý duy nhất, có thể khai báo exception-type là Throwable.
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/AppExceptionHandler</location>
</error-page>
Nếu có nhiều phần tử error-page, ví dụ một cho Throwable và một cho IOException, và ứng dụng ném ra FileNotFoundException thì lỗi này sẽ được xử lý bởi trình xử lý lỗi khai báo cho IOException.
Bạn cũng có thể sử dụng một trang JSP làm trình xử lý exception, chỉ cần chỉ định vị trí của tệp JSP thay vì mapping tới servlet.
Trên đây là toàn bộ nội dung về cách xử lý exception trong servlet cho ứng dụng web. Hy vọng bài viết này sẽ giúp bạn hiểu rõ hơn và áp dụng hiệu quả vào dự án của mình.
Tải về Dự án Ví dụ về Servlet Exception Handling
Tham khảo thêm các bài viết khác trong chuỗi này:
- Java Web Application
- Java Servlet Tutorial
- Session Management in Java
- Servlet Filter
- Servlet Listeners
- Cookies in Servlets
- Servlet File Upload and Download Example