Trang chủHướng dẫnTop 50 câu hỏi phỏng vấn Servlet phổ biến (kèm đáp án chi tiết)
Java

Top 50 câu hỏi phỏng vấn Servlet phổ biến (kèm đáp án chi tiết)

CyStack blog 34 phút để đọc
CyStack blog16/06/2025
Locker Avatar

Bao Tran

Web Developer

Locker logo social
Reading Time: 34 minutes

Bạn đang chuẩn bị cho một buổi phỏng vấn Java Web và muốn làm chủ kiến thức về Servlet? Bài viết này sẽ giúp bạn có cái nhìn rõ ràng và hệ thống về Servlet nền tảng cốt lõi của Java Web Development.

câu hỏi phỏng vấn Servlet

Từ những khái niệm cơ bản đến các câu hỏi nâng cao, tôi đã tổng hợp 50 câu hỏi phỏng vấn Servlet phổ biến nhất kèm theo câu trả lời chi tiết, giúp bạn tự tin hơn khi bước vào bất kỳ vòng phỏng vấn nào. Dù bạn là người mới học hay đã có kinh nghiệm, danh sách này sẽ là tài liệu ôn tập hữu ích giúp bạn củng cố kiến thức, hiểu sâu bản chất Servlet, và ứng dụng hiệu quả trong các dự án thực tế.

1. Sự khác biệt giữa web server và application server là gì?

Khi bắt đầu làm việc với các ứng dụng Java Web, bạn sẽ thường nghe đến hai khái niệm quen thuộc: Web Server và Application Server. Tuy nhiên, không phải ai cũng phân biệt rõ ràng được vai trò và chức năng của từng loại server này. Hãy cùng tìm hiểu!

1.1. Web Server là gì?

Web Server chịu trách nhiệm xử lý các HTTP request đến từ trình duyệt và phản hồi lại bằng nội dung HTML hoặc các tài nguyên tĩnh khác như CSS, JavaScript, hình ảnh,… Nó hiểu và giao tiếp thông qua giao thức HTTP.

Một ví dụ phổ biến là Apache Web Server chuyên dùng để phục vụ các nội dung tĩnh. Còn nếu bạn cần xử lý các thành phần động như Servlet hay JSP, bạn sẽ cần một Servlet Container, điển hình như Apache Tomcat.

1.2. Application Server là gì?

Application Server thì toàn diện hơn. Nó bao gồm tất cả chức năng của Web Server, kèm theo các dịch vụ bổ sung dành cho phát triển ứng dụng doanh nghiệp như:

  • Hỗ trợ Enterprise JavaBeans (EJB)
  • JMS Messaging (truyền thông điệp giữa các thành phần)
  • Transaction Management (quản lý giao dịch)
  • Tích hợp bảo mật, logging, clustering,…

Các ví dụ phổ biến của Application Server bao gồm WildFly (JBoss), GlassFish, WebLogic, và WebSphere.

Web Server xử lý tài nguyên tĩnh và giao tiếp HTTP,

Application Server là Web Server “cộng thêm” các tính năng mạnh mẽ để xây dựng ứng dụng enterprise.

2. Phương thức HTTP nào không idempotent (không đảm bảo kết quả giống nhau khi gọi lặp lại)?

Trong giao thức HTTP, một phương thức được gọi là idempotent nếu việc gọi nó nhiều lần không làm thay đổi trạng thái tài nguyên trên server. Vậy thì ngược lại, phương thức nào không idempotent tức là mỗi lần gọi đều có thể tạo ra tác động khác nhau?

Câu trả lời là: POST

Phương thức POST được dùng để gửi dữ liệu từ client lên server, ví dụ như khi người dùng đăng ký tài khoản, tạo đơn hàng, hoặc gửi biểu mẫu. Mỗi lần gọi POST, server có thể thực hiện một hành động mới như tạo một bản ghi mới trong cơ sở dữ liệu.

Điều đó có nghĩa là:

  • Nếu bạn gửi một POST hai lần, bạn có thể tạo ra hai đối tượng khác nhau.
  • Hành vi này khác biệt với các phương thức idempotent như GET, PUT, hay DELETE, vốn được thiết kế để dù có gọi nhiều lần thì trạng thái server vẫn không thay đổi (hoặc thay đổi theo cùng một cách).

3. Sự khác nhau giữa phương thức GET và POST là gì?

GET và POST là hai phương thức HTTP được sử dụng phổ biến nhất trong các ứng dụng web, nhưng mỗi phương thức lại có mục đích sử dụng và đặc điểm riêng biệt. Dưới đây là những điểm khác biệt chính:

3.1. Về bản chất và tính bất biến

  • GET là phương thức an toàn và có tính bất biến (idempotent) nghĩa là dù bạn gửi đi một hay nhiều lần thì trạng thái server vẫn không thay đổi. Ví dụ như việc tải một trang tin tức.
  • POST thì ngược lại nó không bất biến. Mỗi lần gửi POST có thể khiến dữ liệu trên server thay đổi, ví dụ: thêm đơn hàng, gửi form liên hệ,…

3.2. Cách gửi dữ liệu:

  • Với GET, dữ liệu được gửi trực tiếp qua URL dưới dạng chuỗi truy vấn (?key=value). Điều này làm cho dung lượng bị giới hạn (thường khoảng vài KB tùy trình duyệt).
  • Ngược lại, POST gửi dữ liệu trong phần body của request, cho phép truyền lượng thông tin lớn và phức tạp hơn như JSON, file, dữ liệu biểu mẫu,…

3.3. Tính bảo mật:

  • GET không an toàn khi truyền các thông tin nhạy cảm, vì dữ liệu hiện rõ trên URL và có thể được bookmark hoặc lưu trong lịch sử trình duyệt.
  • POST an toàn hơn vì dữ liệu được gửi trong request body, không hiển thị trên URL và không thể bookmark.

3.4. Khi nào dùng GET, khi nào dùng POST?

  • Dùng GET khi bạn chỉ cần truy vấn hoặc lấy dữ liệu, ví dụ như tìm kiếm sản phẩm, lọc danh sách,…
  • Dùng POST khi bạn cần gửi dữ liệu để tạo hoặc thay đổi thông tin trên server, như đăng ký người dùng, đặt hàng, upload file,…

câu hỏi phỏng vấn Servlet

4. MIME Type là gì?

Khi một server gửi dữ liệu về cho trình duyệt hoặc bất kỳ client nào, làm sao để client biết đó là HTML, file PDF hay ảnh PNG? Câu trả lời chính là thông qua MIME Type hay còn được gọi là Content-Type trong phần header của HTTP response.

MIME (Multipurpose Internet Mail Extensions) Type là một định danh được gửi kèm theo phản hồi từ server, giúp client hiểu rõ loại dữ liệu mà nó sắp xử lý. Nhờ đó, trình duyệt hoặc ứng dụng có thể render hoặc xử lý nội dung một cách chính xác.

Ví dụ:

  • text/html → Dữ liệu HTML, hiển thị như trang web.
  • application/json → Dữ liệu JSON, thường dùng trong API.
  • image/png → Ảnh định dạng PNG.

Trong Java Servlet, bạn có thể sử dụng phương thức ServletContext.getMimeType() để lấy ra MIME type tương ứng với tên file, từ đó gán vào response như sau:

String mimeType = getServletContext().getMimeType(filePath);
response.setContentType(mimeType);

Cách làm này đặc biệt hữu ích khi bạn muốn cho phép người dùng tải file từ server. Việc set đúng MIME type sẽ giúp trình duyệt xử lý file đúng cách (hiển thị, mở bằng phần mềm phù hợp, hoặc tải về).

5. Ứng dụng web là gì và cấu trúc thư mục của nó như thế nào?

Nói một cách đơn giản, Web Application là các mô-đun chạy trên server, chịu trách nhiệm xử lý các request từ trình duyệt người dùng và trả lại kết quả có thể là nội dung tĩnh như HTML, CSS, hoạt động như dữ liệu được sinh ra từ xử lý logic server.

Tuỳ vào công nghệ, cách tạo ứng dụng web sẽ khác nhau:

  • Với Apache Web Server, bạn có thể viết web app bằng PHP.
  • Với Java, chúng ta sử dụng Servlets và JSPs, chạy trong môi trường như Apache Tomcat để xây dựng nội dung động.

Ứng dụng web Java được đóng gói như thế nào?

Trong Java, một web application thường được đóng gói dưới dạng WAR file (viết tắt của Web Application Archive). Đây là định dạng chuẩn để triển khai ứng dụng vào các servlet container như Tomcat hoặc Jetty.

Cấu trúc thư mục chuẩn của WAR file

Dưới đây là cấu trúc thư mục cơ bản của một Java Web Application:

Giải thích nhanh:

  • /WEB-INF/ Thư mục đặc biệt, nơi chứa các tài nguyên nội bộ của server. Trình duyệt không thể truy cập trực tiếp vào đây.
  • /WEB-INF/web.xml File cấu hình chính, định nghĩa servlet, filter, security, v.v.
  • /WEB-INF/classes/ Chứa các file.class đã được biên dịch từ source code Java.
  • /WEB-INF/lib/ Nơi đặt các file.jar mà ứng dụng cần.
  • Ngoài ra, bạn có thể có các thư mục khác như css/, js/, images/ để phục vụ tài nguyên tĩnh.

6. Servlet là gì?

Nếu HTML, CSS, và JavaScript là những phần không thể thiếu ở phía người dùng (frontend), thì ở phía server (backend), Servlet chính là nền tảng cốt lõi giúp Java xử lý các yêu cầu một cách động và thông minh. Vậy cụ thể Servlet là gì và vai trò của nó trong hệ thống web như thế nào?

Java Servlet là một công nghệ phía server (server-side) trong nền tảng Java EE, cho phép mở rộng khả năng của web server bằng cách xử lý các yêu cầu động (dynamic request) và duy trì dữ liệu giữa các request (data persistence).

Servlet hoạt động bên trong một Servlet Container (như Apache Tomcat), nơi tiếp nhận các request HTTP, xử lý và trả lại phản hồi phù hợp có thể là HTML, JSON, file download hoặc bất kỳ định dạng nào khác.

Servlet được định nghĩa trong hai gói chính:

  • javax.servlet.*
  • javax.servlet.http.*

Chúng cung cấp các giao diện và lớp cần thiết để tạo ra servlet tuỳ chỉnh theo nhu cầu của ứng dụng.

Tất cả các servlet đều phải triển khai thành giao diện ( implement interface ) javax.servlet.Servlet, trong đó định nghĩa các vòng đời cơ bản như init(), service(), và destroy().

Tuy nhiên, để lập trình dễ dàng hơn:

  • Nếu bạn viết một servlet không phụ thuộc HTTP, bạn có thể kế thừa từ lớp GenericServlet.
  • Trong phần lớn trường hợp sử dụng HTTP (GET, POST,…), chúng ta thường kế thừa lớp HttpServlet. Lớp này cung cấp sẵn các phương thức như:
    • doGet() Xử lý yêu cầu GET từ trình duyệt.
    • doPost() Xử lý yêu cầu POST (thường dùng trong form).

Câu hỏi phỏng vấn servlet

7. Những ưu điểm của Servlet so với CGI là gì?

Trước khi Servlet trở thành “xương sống” của các ứng dụng web Java, công nghệ phổ biến để xử lý yêu cầu từ trình duyệt là CGI (Common Gateway Interface). Tuy nhiên, CGI tồn tại nhiều hạn chế khiến nó dần bị thay thế. Và đó chính là lý do Servlet ra đời như một giải pháp mạnh mẽ và hiệu quả hơn.

Dưới đây là những lý do rõ ràng cho thấy Servlet vượt trội hơn CGI:

7.1. Hiệu suất vượt trội nhờ đa luồng

CGI tạo một process mới cho mỗi request điều này rất tốn thời gian và bộ nhớ. Ngược lại, Servlet sử dụng multi-threading: chỉ tạo một đối tượng servlet duy nhất và tạo một thread mới cho mỗi request. Điều này giúp:

  • Xử lý nhanh hơn
  • Tối ưu sử dụng CPU và bộ nhớ
  • Giảm tải cho server trong môi trường có nhiều request đồng thời

7.2. Tính độc lập nền tảng

Ứng dụng viết bằng Servlet có thể chạy trên mọi hệ điều hành hỗ trợ Java như: Windows, Linux, macOS, Solaris,…

Chỉ cần có Servlet Container (như Apache Tomcat, JBoss, Glassfish…), ứng dụng của bạn có thể hoạt động ổn định mà không cần viết lại code cho từng nền tảng.

7.3. Ổn định và mạnh mẽ

Servlet Container (ví dụ: Tomcat) chịu trách nhiệm quản lý toàn bộ vòng đời của servlet từ khởi tạo, xử lý request, cho đến huỷ bỏ. Điều này giúp:

  • Giảm nguy cơ rò rỉ bộ nhớ
  • Tăng tính bảo mật
  • Hạn chế lỗi do xử lý tài nguyên không đúng cách

7.4. Dễ bảo trì và dễ học

Không cần lo lắng về quá nhiều thứ kỹ thuật phức tạp như quản lý tài nguyên hay vòng đời xử lý. Khi làm việc với Servlet, bạn chỉ cần tập trung vào business logic logic nghiệp vụ thực sự của ứng dụng.

Thêm vào đó, vì Servlet được viết bằng Java, nên nếu bạn đã biết Java cơ bản, việc học Servlet là rất tự nhiên và nhẹ nhàng.

8. Những tác vụ phổ biến mà Servlet Container thực hiện là gì?

Dưới đây là những công việc quan trọng mà Servlet Container âm thầm đảm nhiệm để giúp ứng dụng web của bạn hoạt động trơn tru:

8.1. Hỗ trợ giao tiếp với client

Servlet Container giúp bạn kết nối với trình duyệt web một cách đơn giản. Thay vì phải viết code tạo socket server, lắng nghe request, phân tích cú pháp request HTTP, và sinh response thủ công — Container lo hết! Bạn chỉ cần tập trung vào xử lý logic nghiệp vụ (doGet(), doPost(), v.v.).

8.2. Quản lý vòng đời servlet và tài nguyên hệ thống

Container chịu trách nhiệm:

  • Load servlet vào bộ nhớ khi cần
  • Gọi các phương thức init(), service(), destroy()
  • Quản lý tài nguyên như kết nối CSDL, pool thread thông qua JNDI…

Điều này giúp giảm công sức lập trình thủ công và đảm bảo hệ thống luôn chạy ổn định.

8.3. Hỗ trợ đa luồng (Multithreading)

Mỗi khi có một request từ client, container sẽ:

  • Tạo một thread mới
  • Gán thread đó với các đối tượng HttpServletRequestHttpServletResponse
  • Gọi phương thức thích hợp trong servlet để xử lý

Điều này giúp tăng tốc xử lý và giảm sử dụng tài nguyên so với cách khởi tạo servlet mới cho mỗi request như CGI truyền thống.

8.4. Hỗ trợ JSP

JSP (JavaServer Pages) ban đầu không giống như một class Java, nhưng container sẽ biên dịch JSP thành Servlet ở phía sau. Sau đó, nó xử lý JSP như bất kỳ servlet nào khác — cực kỳ tiện lợi cho lập trình viên muốn viết giao diện HTML động nhanh chóng.

8.5. Những tác vụ khác phía sau hậu trường

Ngoài các chức năng kể trên, servlet container còn thực hiện hàng loạt nhiệm vụ khác như:

  • Tối ưu bộ nhớ, kích hoạt Garbage Collector
  • Hỗ trợ bảo mật: cấu hình SSL, session management,…
  • Hỗ trợ chạy nhiều ứng dụng cùng lúc (multi-app hosting)

9. Đối tượng ServletConfig là gì?

Khi làm việc với các servlet trong ứng dụng Java web, đôi lúc bạn sẽ cần truyền một vài cấu hình riêng biệt cho từng servlet ví dụ như đường dẫn file, thông tin kết nối, hay các tham số tuỳ chỉnh. Và đó chính là lúc ServletConfig xuất hiện như một công cụ cực kỳ hữu ích.ServletConfig là một đối tượng do Servlet Container cung cấp, dùng để truyền thông tin cấu hình khởi tạo vào servlet khi nó được khởi chạy lần đầu. Mỗi servlet sẽ có một ServletConfig riêng biệt, và bạn hoàn toàn có thể sử dụng nó để lấy ra các giá trị cấu hình đã được khai báo sẵn.

phỏng vấn servlet

Có 2 cách phổ biến để khai báo tham số khởi tạo cho servlet:

  1. Khai báo trong web.xml:
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.example.MyServlet</servlet-class>
        <init-param>
            <param-name>configFile</param-name>
            <param-value>/WEB-INF/config.properties</param-value>
        </init-param>
    </servlet>
    
    
  2. Sử dụng annotation @WebInitParam:
    @WebServlet(
        name = "MyServlet",
        urlPatterns = {"/hello"},
        initParams = {
            @WebInitParam(name = "configFile", value = "/WEB-INF/config.properties")
        }
    )
    
    

ServletConfig là cách đơn giản và hiệu quả để truyền tham số khởi tạo riêng biệt cho từng servlet trong ứng dụng. Nó giúp bạn tách riêng phần cấu hình và logic xử lý, giúp code dễ bảo trì và mở rộng hơn trong thực tế.

10. Đối tượng ServletContext là gì?

Trong khi ServletConfig cung cấp tham số cấu hình riêng cho từng servlet, thì ServletContext là chiếc cầu nối giúp các servlet trong cùng một ứng dụng web “nói chuyện” với nhau và chia sẻ thông tin một cách hiệu quả. ServletContext là một đối tượng duy nhất (singleton) đại diện cho toàn bộ ứng dụng web đang chạy trong container (như Tomcat, Jetty…). Nó được tạo ra bởi servlet container khi ứng dụng được deploy và tồn tại suốt vòng đời của ứng dụng. Nói đơn giản, ServletContext là nơi lưu trữ các thông tin cấu hình hoặc tài nguyên mà nhiều servlet cần truy cập chung.

Khai báo cấu hình dùng chung với <context-param>

Nếu bạn muốn thiết lập tham số cấu hình dùng chung cho tất cả servlet, bạn có thể làm điều đó trong file web.xml:

<context-param>
    <param-name>appName</param-name>
    <param-value>My Web App</param-value>
</context-param>

Sau đó, trong bất kỳ servlet nào, bạn đều có thể lấy giá trị đó bằng:

String appName = getServletContext().getInitParameter("appName");

Ngoài việc lưu trữ tham số, ServletContext còn cung cấp nhiều tính năng cực kỳ hữu ích như:

  • Tải tài nguyên tĩnh: getResourceAsStream() để đọc file cấu hình, hình ảnh, hoặc file data nằm trong ứng dụng.
  • Lấy MIME Type: getMimeType("file.pdf") để thiết lập đúng Content-Type khi trả về response.
  • Đăng ký Servlet, Filter, Listener bằng code (từ Servlet 3.x trở đi): cho phép lập trình cấu hình động mà không cần chỉnh web.xml.

Điều quan trọng cần nhớ

  • ServletContext là dùng chung giữa tất cả các servlet trong một ứng dụng web.
  • Bạn có thể lưu trữ đối tượng trong nó như một kiểu cache tạm thời cho toàn ứng dụng bằng setAttribute()getAttribute().
  • Bạn có thể truy cập nó bất cứ lúc nào trong servlet thông qua: getServletContext().

11. ServletConfigServletContext khác nhau như thế nào?

Trong quá trình phát triển ứng dụng Java Web với Servlet, bạn sẽ thường xuyên bắt gặp hai đối tượng cấu hình quan trọng: ServletConfigServletContext. Mặc dù thoạt nhìn chúng có vẻ giống nhau vì đều liên quan đến việc truyền tham số cấu hình cho servlet, nhưng trên thực tế, mỗi đối tượng phục vụ một mục đích rất khác biệt.

Dưới đây là những điểm khác biệt cốt lõi giữa chúng:

11.1 Phạm vi tồn tại (Scope)

  • ServletConfig: Mỗi servlet sẽ có một đối tượng ServletConfig riêng biệt. Nó được servlet container khởi tạo khi servlet đó được load lần đầu.
  • ServletContext: Là một đối tượng duy nhất cho toàn bộ ứng dụng web. Tất cả các servlet trong ứng dụng có thể truy cập chung ServletContext.

11.2. Mục đích sử dụng

  • ServletConfig: Dùng để cung cấp các tham số khởi tạo riêng cho từng servlet, thường được khai báo trong file web.xml dưới thẻ <init-param>.
  • ServletContext: Dùng để cung cấp các tham số cấp ứng dụng, có thể truy cập được bởi tất cả servlet thường được khai báo bằng <context-param> trong web.xml.

11.3. Quản lý thuộc tính (Attributes)

  • ServletConfig: Không hỗ trợ việc lưu trữ và chia sẻ attribute.
  • ServletContext: Cho phép lưu trữ attribute dùng chung giữa các servlet thông qua các phương thức setAttribute()getAttribute(). Đây là cách tiện lợi để chia sẻ dữ liệu tạm thời trên phạm vi toàn ứng dụng.

12. RequestDispatcher là gì?

Trong Java Servlet, khi bạn muốn chuyển tiếp một request đến một tài nguyên khác (như một servlet khác, một JSP, hay một HTML), hoặc chèn nội dung từ tài nguyên đó vào response hiện tại, thì RequestDispatcher chính là công cụ bạn cần.

RequestDispatcher là một interface trong Servlet API, cung cấp cơ chế để:

  • Chuyển tiếp (forward) request từ một servlet đến một tài nguyên khác trong cùng ứng dụng web.
  • Nhúng (include) nội dung của một tài nguyên vào trong response hiện tại.

Interface RequestDispatcher định nghĩa hai phương thức chính:

  1. forward(ServletRequest request, ServletResponse response)→ Dùng để chuyển tiếp request hiện tại đến một tài nguyên khác để xử lý tiếp.

    → Lưu ý: trình duyệt sẽ không biết được sự chuyển tiếp này vì nó diễn ra hoàn toàn ở phía server.

  2. include(ServletRequest request, ServletResponse response)→ Dùng để chèn nội dung của một tài nguyên khác vào response hiện tại.

    → Hữu ích khi bạn muốn dùng chung phần header, footer hoặc các phần tử giao diện khác.

13. Sự khác biệt giữa PrintWriterServletOutputStream là gì?

Trong Java Servlet, việc ghi dữ liệu về phía client thông qua HttpServletResponse là thao tác rất phổ biến. Tùy vào loại dữ liệu mà bạn muốn phản hồi, bạn có thể lựa chọn giữa hai cách chính: PrintWriterServletOutputStream. Tuy nhiên, hiểu rõ sự khác biệt giữa hai loại này sẽ giúp bạn tránh được lỗi không mong muốn trong quá trình phát triển.

  • PrintWriter là lớp xử lý theo luồng ký tự.
    • Dùng để ghi các thông tin dạng văn bản như String, mảng ký tự (char[]), HTML hoặc JSON.
    • Được lấy thông qua phương thức getWriter() của đối tượng ServletResponse.
PrintWriter out = response.getWriter();
out.println("Xin chào");
  • ServletOutputStream là lớp xử lý theo luồng byte.
    • Thích hợp cho việc ghi dữ liệu nhị phân như ảnh, video, file PDF, hoặc bất kỳ dữ liệu dạng byte[] nào.
    • Được lấy thông qua phương thức getOutputStream() của ServletResponse.
ServletOutputStream out = response.getOutputStream();
out.write(byteArray);

14. Có thể sử dụng cả PrintWriterServletOutputStream trong cùng một servlet không?

Chúng ta không thể lấy cả PrintWriterServletOutputStream trong cùng một phương thức servlet. Nếu bạn cố gắng gọi cả hai phương thức getWriter()getOutputStream() trên cùng một đối tượng HttpServletResponse, ứng dụng sẽ ném ra lỗi java.lang.IllegalStateException tại thời điểm chạy, kèm theo thông báo rằng phương thức còn lại đã được gọi trước đó cho response này.

15. Tình huống deadlock (bế tắc) trong Servlet xảy ra khi nào?

Một trong những cách đơn giản nhất để tạo ra deadlock trong servlet là gây vòng lặp vô hạn giữa hai phương thức doGet()doPost().

Cụ thể, nếu bạn gọi doPost() từ bên trong doGet(), và sau đó lại gọi doGet() từ bên trong doPost(), servlet sẽ rơi vào vòng lặp đệ quy vô tận, dẫn đến stack overflow hoặc treo toàn bộ thread đang xử lý request đây chính là một ví dụ điển hình của tình huống deadlock trong servlet theo hướng logic luồng xử lý:

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req, resp); // Gọi ngược lại doPost()
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doGet(req, resp); // Gọi lại doGet() → vòng lặp vô hạn
}

Đây là kiểu deadlock không xuất phát từ việc tranh chấp tài nguyên đa luồng (multithreading), mà là do đệ quy không có điều kiện dừng, cũng nguy hiểm không kém trong môi trường web server.

16. Lớp wrapper trong Servlet được sử dụng để làm gì?

Trong Servlet API, chúng ta có hai lớp wrapper quan trọng: HttpServletRequestWrapperHttpServletResponseWrapper. Những lớp này được thiết kế để giúp lập trình viên tuỳ biến và mở rộng cách xử lý request/response mà không cần phải viết lại toàn bộ giao diện HttpServletRequest hay HttpServletResponse.

Các lớp wrapper hoạt động như một lớp trung gian chúng gói (wrap) đối tượng gốc và cho phép bạn ghi đè (override) các phương thức cần thiết để thay đổi hành vi theo ý muốn. Đây là cách tiếp cận rất hữu ích khi bạn cần:

  • Can thiệp hoặc thay đổi dữ liệu trong request/response trước khi servlet xử lý.
  • Ghi log thông tin request hoặc response.
  • Thực hiện kiểm tra bảo mật hoặc lọc nội dung đầu vào/đầu ra.

Ví dụ, nếu bạn muốn mã hóa tất cả các tham số request đầu vào hoặc ghi lại toàn bộ nội dung phản hồi trả về client, bạn có thể mở rộng các lớp wrapper này.

Tuy nhiên, các lớp wrapper này không thường được sử dụng trong các ứng dụng servlet cơ bản, mà chủ yếu xuất hiện trong các tình huống nâng cao hoặc khi viết các Servlet Filter tùy chỉnh.

17. Giao diện SingleThreadModel trong Servlet là gì và dùng để làm gì?

Trong những phiên bản đầu tiên của Servlet API, giao diện SingleThreadModel được đưa vào nhằm mục đích đảm bảo tính an toàn luồng (thread safety). Khi một servlet sử dụng giao diện này, nó đảm bảo rằng không có hai luồng (thread) nào được phép thực thi đồng thời phương thức service() của servlet đó. Điều này nghe có vẻ lý tưởng khi bạn muốn tránh các vấn đề liên quan đến dữ liệu bị truy cập đồng thời trong môi trường đa luồng.

Tuy nhiên, trên thực tế, SingleThreadModel không giải quyết được triệt để các vấn đề về thread-safety. Ví dụ như:

  • Các biến staticsession vẫn có thể bị truy cập đồng thời từ nhiều request khác nhau.
  • Nó làm giảm hiệu năng vì loại bỏ hoàn toàn lợi thế đa luồng vốn là một điểm mạnh của Servlet.

Chính vì những hạn chế này, giao diện SingleThreadModel đã bị khai tử (deprecated) kể từ Servlet 2.4. Thay vào đó, việc đảm bảo thread-safety nên được lập trình viên tự xử lý thông qua các chiến lược đồng bộ hóa (synchronization), sử dụng các đối tượng bất biến (immutable), hoặc tránh dùng biến dùng chung giữa các luồng.

câu hỏi phỏng vấn servlet phổ biến

18. Có cần override phương thức service() trong Servlet không?

Khi servlet container nhận được một HTTP request từ client, nó sẽ gọi phương thức service(). Chính phương thức này sẽ tự động định tuyến yêu cầu đến các phương thức thích hợp như doGet(), doPost(), doPut(), v.v. tùy vào loại HTTP method được sử dụng. Vì vậy, trong hầu hết các tình huống, chúng ta không cần override service(). Việc can thiệp vào nó có thể phá vỡ luồng xử lý tiêu chuẩn của Servlet API, khiến code khó bảo trì hơn.

Nếu bạn muốn xử lý logic chung cho mọi request (như logging, authentication, hay request preprocessing), cách làm chuẩn hơn là sử dụng Servlet Filters hoặc Servlet Listeners, vừa tách biệt logic, vừa giữ được cấu trúc rõ ràng cho ứng dụng. Tóm lại, hãy để service() làm đúng vai trò của nó, và chỉ can thiệp khi thực sự có yêu cầu đặc biệt điều vốn rất hiếm khi xảy ra.

19. Có nên tạo constructor cho servlet không?

Chúng ta có thể định nghĩa constructor cho servlet, nhưng thực tế thì constructor này không thực sự hữu ích trong hầu hết các trường hợp. Lý do là khi servlet được khởi tạo, chúng ta không thể truy cập vào đối tượng ServletConfig cho đến khi servlet được container khởi tạo.

Thay vì sử dụng constructor, nên sử dụng phương thức init() để khởi tạo các tài nguyên cho servlet. Phương thức này cho phép bạn truy cập các tham số khởi tạo của servlet thông qua đối tượng ServletConfig.

Điều này sẽ giúp quản lý tài nguyên và cấu hình của servlet một cách hợp lý hơn. Vì vậy, việc tạo constructor cho servlet không phải là một ý tưởng tốt nếu bạn muốn servlet hoạt động đúng cách trong môi trường servlet container.

20. Sự khác biệt giữa GenericServletHttpServlet là gì?

Khi làm việc với Servlet trong Java, bạn sẽ thường bắt gặp hai lớp quan trọng là GenericServletHttpServlet. Vậy chúng khác nhau như thế nào?

  • GenericServlet là một lớp trừu tượng cung cấp một cài đặt độc lập với giao thức cho interface Servlet. Điều này có nghĩa là bạn có thể sử dụng GenericServlet cho bất kỳ loại giao thức nào (không chỉ HTTP).
  • HttpServlet là lớp mở rộng từ GenericServlet và được thiết kế riêng cho giao thức HTTP. Lớp này cung cấp các phương thức tiện ích như doGet(), doPost(), doPut(), doDelete()… để xử lý các yêu cầu HTTP một cách dễ dàng.

Trên thực tế, khi xây dựng ứng dụng web với Java, chúng ta gần như luôn làm việc với giao thức HTTP, vì vậy việc kế thừa từ HttpServlet là lựa chọn phổ biến nhất. Nó giúp đơn giản hóa việc xử lý các yêu cầu từ client và tận dụng được những đặc trưng của HTTP một cách hiệu quả.

21. Giao tiếp giữa các Servlet (Inter-Servlet Communication) là gì?

Trong một số tình huống, bạn có thể muốn một servlet chuyển tiếp yêu cầu hoặc phối hợp với một servlet khác để xử lý yêu cầu từ phía client. Đây chính là lúc cơ chế giao tiếp giữa các servlet (inter-servlet communication) phát huy tác dụng.

Servlet có thể gọi một servlet khác từ bên trong phương thức service() (hoặc doGet(), doPost()) bằng cách sử dụng hai phương thức của RequestDispatcher:

  • forward() chuyển tiếp toàn bộ yêu cầu đến servlet khác.
  • include() bao gồm nội dung của servlet khác vào trong phản hồi.

Bạn cũng có thể truyền dữ liệu giữa các servlet bằng cách đính kèm các thuộc tính vào đối tượng HttpServletRequest, từ đó servlet được gọi có thể truy cập và xử lý dữ liệu đó một cách linh hoạt.

22. Servlets có Thread-safe không? Làm thế nào để đảm bảo thread-safety trong servlet?

Về mặt bản chất, Servlet không đảm bảo an toàn luồng (thread-safe). Mặc định, một thể hiện (instance) servlet được tạo ra duy nhất bởi container và dùng chung cho tất cả các request. Điều này có nghĩa là nhiều luồng (threads) có thể truy cập đồng thời vào cùng một đối tượng servlet — đây chính là nguyên nhân tiềm ẩn gây ra xung đột dữ liệu nếu chúng ta không xử lý đúng cách.

Các phương thức như init()destroy() chỉ được gọi một lần duy nhất trong vòng đời servlet, nên không cần lo lắng về đồng bộ hóa trong hai phương thức này. Tuy nhiên, với các phương thức doGet(), doPost() hoặc bất kỳ phương thức xử lý request nào — những phương thức này được gọi mỗi khi có một client gửi yêu cầu đến, và sẽ được xử lý bởi các luồng riêng biệt.

Nếu bên trong các phương thức này bạn chỉ sử dụng biến cục bộ (local variables), thì không cần lo lắng, bởi vì mỗi luồng có một stack riêng. Tuy nhiên, nếu bạn làm việc với tài nguyên dùng chung (shared resources) — ví dụ như biến instance, collection tĩnh, connection pool dùng chung,… thì bạn cần sử dụng các cơ chế đồng bộ như synchronized, ReentrantLock, hoặc các cấu trúc dữ liệu thread-safe như ConcurrentHashMap. Tóm lại: Luôn đảm bảo các thao tác trên tài nguyên chia sẻ phải được xử lý đồng bộ hoặc thông qua các cơ chế thread-safe.

23. Servlet Attributes là gì? Phạm vi (Scope) của chúng ra sao?

Servlet Attributes là một cách phổ biến để chia sẻ dữ liệu giữa các thành phần trong ứng dụng web Java — đặc biệt là giữa các servlet. Chúng cho phép chúng ta lưu trữ, truy xuất và xóa thông tin ở nhiều phạm vi khác nhau, tuỳ vào nhu cầu sử dụng.

Có 3 phạm vi chính (scope) của servlet attributes:

  1. Request ScopeDữ liệu tồn tại trong suốt vòng đời của một HTTP request. Sử dụng khi bạn cần truyền dữ liệu từ một servlet sang servlet khác thông qua RequestDispatcher.
  2. Session ScopeAttribute được lưu trong phiên làm việc (session) của người dùng và tồn tại cho đến khi session hết hạn hoặc bị xoá thủ công. Rất hữu ích cho việc lưu trạng thái đăng nhập hoặc giỏ hàng.
  3. Application ScopeDữ liệu được chia sẻ trên toàn bộ ứng dụng, sử dụng ServletContext. Tất cả các servlet đều có thể truy cập vào attribute này.

Để thao tác với attributes, ta sử dụng các interface tương ứng:

  • ServletRequest cho request scope
  • HttpSession cho session scope
  • ServletContext cho application scope

Lưu ý: Servlet Attributes khác với init parameters được định nghĩa trong web.xml cho ServletConfig hoặc ServletContext. Init parameters là cấu hình cố định, còn attributes có thể thay đổi trong thời gian thực khi ứng dụng chạy.

24. Làm thế nào để gọi một Servlet từ một Servlet khác?

Trong Java Servlet, để gọi một servlet từ servlet khác, chúng ta có thể sử dụng RequestDispatcher. Đây là một công cụ mạnh mẽ giúp xử lý yêu cầu và chuyển tiếp (forward) đến một servlet khác hoặc bao gồm (include) đầu ra của servlet khác vào phản hồi hiện tại.

  • RequestDispatcher forward(): Phương thức này được sử dụng để chuyển tiếp yêu cầu từ servlet này sang servlet khác. Sau khi chuyển tiếp, servlet gốc không thực hiện thêm bất kỳ xử lý nào.
  • RequestDispatcher include(): Nếu bạn muốn bao gồm đầu ra của servlet khác vào trong phản hồi của servlet hiện tại, bạn có thể sử dụng phương thức include(). Điều này cho phép bạn kết hợp kết quả từ nhiều servlet vào một trang duy nhất.

25. Làm thế nào để gọi một Servlet trong một ứng dụng khác?

Trong Java Servlet, RequestDispatcher không thể được sử dụng để gọi một servlet trong một ứng dụng khác vì nó chỉ áp dụng trong phạm vi của cùng một ứng dụng.

Tuy nhiên, nếu bạn cần chuyển tiếp yêu cầu đến một servlet trong một ứng dụng khác, bạn có thể sử dụng phương thức sendRedirect() của ServletResponse và cung cấp URL đầy đủ của servlet đó. Khi sử dụng sendRedirect(), một mã phản hồi HTTP 302 (Found) sẽ được gửi đến trình duyệt, yêu cầu trình duyệt chuyển hướng đến URL mới.

response.sendRedirect("<http://other-application.com/anotherServlet>");

Ngoài ra, nếu bạn cần gửi thêm dữ liệu cùng với yêu cầu, bạn có thể sử dụng cookies, là một phần của phản hồi servlet và sẽ được gửi trong yêu cầu tới servlet khác.

26. Sự khác biệt giữa phương thức ServletResponse sendRedirect() và RequestDispatcher forward() là gì?

  • RequestDispatcher forward() được sử dụng để chuyển tiếp cùng một yêu cầu đến một tài nguyên khác, trong khi ServletResponse sendRedirect() là một quá trình hai bước. Với sendRedirect(), ứng dụng web trả về phản hồi cho client với mã trạng thái 302 (chuyển hướng) và URL để gửi yêu cầu. Yêu cầu gửi đi là một yêu cầu hoàn toàn mới.
  • forward() được xử lý bên trong container, trong khi sendRedirect() được xử lý bởi trình duyệt.
  • Chúng ta nên sử dụng forward() khi truy cập các tài nguyên trong cùng một ứng dụng vì nó nhanh hơn so với phương thức sendRedirect(), vốn yêu cầu một lần gọi mạng bổ sung.
  • Trong forward(), trình duyệt không biết về tài nguyên thực tế đang xử lý và URL trên thanh địa chỉ vẫn giữ nguyên, trong khi trong sendRedirect(), URL trên thanh địa chỉ thay đổi thành tài nguyên được chuyển hướng.
  • forward() không thể được sử dụng để gọi một servlet trong một context khác, trong trường hợp này, chúng ta chỉ có thể sử dụng sendRedirect().

27. Tại sao lớp HttpServlet lại được khai báo là abstract?

Lớp HttpServlet cung cấp triển khai giao thức HTTP của servlet, nhưng nó được khai báo là abstract vì không có logic triển khai trong các phương thức service như doGet()doPost(). Chúng ta cần phải ghi đè ít nhất một trong các phương thức service này.

Chính vì vậy, không có lý do gì để tạo một thể hiện (instance) của lớp HttpServlet, và nó được khai báo là lớp abstract.

28. Các giai đoạn trong vòng đời của servlet là gì?

Vòng đời của servlet được quản lý bởi Servlet Container và có bốn giai đoạn chính:

  1. Tải lớp Servlet – Khi container nhận được yêu cầu cho một servlet, nó sẽ tải lớp servlet vào bộ nhớ và gọi constructor mặc định không tham số của nó.
  2. Khởi tạo lớp Servlet – Sau khi lớp servlet được tải, container sẽ khởi tạo đối tượng ServletContext cho servlet và sau đó gọi phương thức init() của nó, truyền vào đối tượng ServletConfig. Đây là giai đoạn servlet chuyển từ một lớp thông thường thành servlet.
  3. Xử lý yêu cầu – Sau khi servlet được khởi tạo, nó sẵn sàng để xử lý các yêu cầu của khách hàng. Với mỗi yêu cầu từ khách hàng, servlet container sẽ tạo một luồng mới và gọi phương thức service() với tham chiếu đến đối tượng yêu cầu và phản hồi.
  4. Loại bỏ khỏi dịch vụ – Khi container dừng hoặc khi chúng ta dừng ứng dụng, servlet container sẽ hủy servlet bằng cách gọi phương thức destroy().

29. Các phương thức trong vòng đời của một servlet là gì?

Vòng đời của servlet bao gồm ba phương thức sau:

  1. public void init(ServletConfig config) – Phương thức này được container sử dụng để khởi tạo servlet, và nó chỉ được gọi một lần trong suốt vòng đời của servlet.
  2. public void service(ServletRequest request, ServletResponse response) – Phương thức này được gọi mỗi khi có yêu cầu mới, container không thể gọi phương thức service() cho đến khi phương thức init() được thực thi.
  3. public void destroy() – Phương thức này được gọi một lần khi servlet bị hủy khỏi bộ nhớ.

30. Tại sao chúng ta chỉ nên ghi đè phương thức init() không có tham số?

Nếu chúng ta cần khởi tạo một số tài nguyên trước khi servlet xử lý yêu cầu của khách hàng, chúng ta nên ghi đè phương thức init() của servlet. Nếu ghi đè phương thức init(ServletConfig config), thì câu lệnh đầu tiên trong phương thức đó nên là super(config) để đảm bảo phương thức init(ServletConfig config) của lớp cha được gọi trước.

Chính vì vậy, GenericServlet cung cấp một phương thức trợ giúp init() không có tham số, và phương thức này được gọi sau khi phương thức init(ServletConfig config) được thực thi. Chúng ta nên sử dụng phương thức này khi ghi đè phương thức init() để tránh các vấn đề, vì chúng ta có thể quên gọi super() trong phương thức init khi có tham số ServletConfig.

31. URL Encoding là gì?

URL Encoding là quá trình chuyển đổi dữ liệu thành định dạng CGI (Common Gateway Interface) để đảm bảo dữ liệu có thể truyền tải qua mạng một cách an toàn và không gặp lỗi. Trong quá trình này, các ký tự đặc biệt như dấu cách, dấu nháy đơn, dấu &, v.v… sẽ được thay thế bằng các ký tự thoát (escape characters). Ví dụ, chuỗi "Pankaj's Data" sẽ được mã hóa thành "Pankaj%27s+Data".

Trong Java, ta có thể sử dụng phương thức java.net.URLEncoder.encode(String str, String unicode) để mã hóa chuỗi trước khi gửi qua URL. Ngược lại, để giải mã dữ liệu đã được mã hóa, chúng ta sử dụng java.net.URLDecoder.decode(String str, String unicode). URL Encoding đặc biệt hữu ích khi truyền dữ liệu qua query string hoặc biểu mẫu HTML, đảm bảo rằng dữ liệu không bị lỗi định dạng trong quá trình truyền tải.

32. Các phương pháp quản lý phiên (session) trong Servlets là gì?

Trong môi trường web, HTTP là giao thức stateless, nghĩa là mỗi yêu cầu (request) từ client đều được xử lý như một sự kiện độc lập, không có ngữ cảnh gì về các request trước đó. Do đó, để duy trì trạng thái phiên làm việc (session) giữa client và server ví dụ như việc người dùng đã đăng nhập hay chưa, giỏ hàng đang có gì chúng ta cần sử dụng cơ chế quản lý session.

Dưới đây là một số phương pháp phổ biến để quản lý session trong Servlet:

  1. Xác thực người dùng (User Authentication)Đây là cách phổ biến trong các hệ thống bảo mật. Sau khi người dùng đăng nhập thành công, server sẽ ghi nhận thông tin phiên và duy trì nó thông qua các kỹ thuật khác như cookies hoặc session object.
  2. Trường ẩn trong HTML (Hidden Fields)Dữ liệu phiên được lưu trong các input ẩn (hidden input) trong form HTML và gửi kèm theo mỗi lần submit. Phù hợp với các ứng dụng chỉ sử dụng biểu mẫu.
  3. CookiesThông tin phiên được lưu trong cookies và gửi tự động kèm theo mỗi request từ client đến server. Servlet API hỗ trợ thao tác với cookies thông qua lớp javax.servlet.http.Cookie.
  4. URL RewritingDữ liệu phiên (thường là session-id) được nhúng trực tiếp vào URL như một query parameter. Ví dụ:

    http://example.com/myapp/page?jsessionid=XYZ123

  5. Session Management APIServlet cung cấp HttpSession API để quản lý phiên dễ dàng hơn. Thông qua request.getSession(), chúng ta có thể lưu trữ và truy xuất dữ liệu liên quan đến từng người dùng trong suốt phiên làm việc của họ.

33. URL Rewriting là gì?

URL Rewriting là một kỹ thuật quản lý phiên (session) trong servlet được sử dụng khi cookie bị vô hiệu hóa trên trình duyệt người dùng. Mặc dù chúng ta thường dùng HttpSession để lưu trữ session giữa các request, nhưng HttpSession hoạt động dựa trên cookie – nếu cookie bị tắt, session sẽ không hoạt động như mong đợi.

Để xử lý trường hợp này, Servlet API hỗ trợ URL Rewriting như một cơ chế dự phòng. Kỹ thuật này hoạt động bằng cách mã hóa (encode) session-id vào trong URL. Về mặt lập trình, việc triển khai rất đơn giản chỉ cần gọi phương thức encodeURL() của HttpServletResponse.

Trong trường hợp redirect, chúng ta có thể sử dụng encodeRedirectURL() để đảm bảo session-id được giữ lại khi chuyển hướng đến một tài nguyên khác. Điều đặc biệt là Servlet Container sẽ tự động kiểm tra xem cookie có bị tắt hay không và chỉ sử dụng URL Rewriting khi cần thiết, giúp tối ưu và an toàn hơn trong nhiều tình huống.

34. Cookies hoạt động như thế nào trong Servlets?

Cookies được sử dụng rất phổ biến trong giao tiếp giữa client và server trong các ứng dụng web, chứ không phải là một khái niệm chỉ riêng trong Java. Cookie là dữ liệu dạng văn bản được gửi từ server đến client và được lưu trữ trên máy cục bộ của client.

Trong Java Servlet API, cookie được hỗ trợ thông qua lớp javax.servlet.http.Cookie, lớp này triển khai các interface SerializableCloneable. Để lấy cookie từ request, ta sử dụng phương thức getCookies() của HttpServletRequest, phương thức này trả về một mảng các đối tượng Cookie. Do cookie không được thêm vào request từ phía server, nên API không cung cấp phương thức nào để set hay add cookie vào request.

Ngược lại, để gửi cookie từ server về client, chúng ta dùng phương thức addCookie(Cookie c) của HttpServletResponse, phương thức này sẽ đính kèm cookie vào header của phản hồi. Tương tự, do server không đọc lại cookie từ response nên API cũng không cung cấp phương thức getter nào cho cookie trong HttpServletResponse.

35. Làm thế nào để một đối tượng trong session nhận được thông báo khi session bị vô hiệu hóa hoặc hết hạn?

Nếu bạn cần đảm bảo một đối tượng được thông báo khi session bị hủy, đối tượng đó nên triển khai interface javax.servlet.http.HttpSessionBindingListener. Interface này định nghĩa hai phương thức callback là valueBound()valueUnbound(), cho phép bạn xử lý logic khi đối tượng được thêm vào session và khi session bị hủy (hoặc đối tượng bị xóa khỏi session).

36. Sự khác biệt giữa encodeRedirectURLencodeURL là gì?

HttpServletResponse cung cấp hai phương thức để mã hóa URL nhằm đảm bảo các ký tự đặc biệt và khoảng trắng được xử lý đúng, đồng thời tự động thêm jsessionid vào URL nếu cần (khi trình duyệt không hỗ trợ cookie).

  • encodeURL() được sử dụng để mã hóa các liên kết trong nội dung HTML, chẳng hạn như các thẻ <a href="">. Phương thức này giống như URLEncoder nhưng bổ sung thêm việc gắn jsessionid nếu cần thiết.
  • encodeRedirectURL() được dùng chuyên biệt cho các URL chuyển hướng (redirect). Nếu bạn sử dụng response.sendRedirect(), hãy dùng phương thức này để đảm bảo URL chuyển hướng được mã hóa đúng cách và hỗ trợ session tracking.

37. Servlet Filter dùng để làm gì và tại sao lại quan trọng?

Trong kiến trúc web sử dụng Java Servlet, Servlet Filter đóng vai trò như một lớp trung gian mạnh mẽ và linh hoạt, cho phép bạn can thiệp vào quá trình xử lý request và response một cách có tổ chức và có thể mở rộng.

Cụ thể, Filter giúp bạn:

  • Ghi log các tham số request để phục vụ theo dõi và kiểm tra hệ thống.
  • Thực hiện xác thực và phân quyền truy cập tài nguyên.
  • Xử lý, định dạng lại phần thân hoặc header của request trước khi đến servlet.
  • Nén dữ liệu phản hồi gửi về client để tối ưu tốc độ tải.
  • Thêm cookie, header hoặc tùy chỉnh nội dung response.

Điểm mạnh của Servlet Filter là cấu hình dễ dàng mà không cần sửa đổi mã nguồn của servlet chính. Chính vì vậy, nó là lựa chọn tối ưu để xử lý các tác vụ chung mà nhiều servlet cùng cần.

38. Làm thế nào để đảm bảo tất cả servlet chỉ cho phép truy cập khi người dùng đã đăng nhập?

Cách hiệu quả nhất để đảm bảo các servlet chỉ được truy cập khi người dùng có phiên làm việc hợp lệ (valid session) là sử dụng Servlet Filter.

Servlet Filter là lớp trung gian có thể chặn và xử lý request trước khi request đó đến servlet đích. Nhờ vào khả năng này, bạn có thể xây dựng một Authentication Filter để:

  • Kiểm tra xem request có chứa session hợp lệ hay không.
  • Nếu có session: cho phép tiếp tục xử lý.
  • Nếu không: chuyển hướng người dùng đến trang đăng nhập.

Điều này giúp bạn dễ dàng kiểm soát quyền truy cập vào toàn bộ ứng dụng web mà không cần thêm logic kiểm tra session vào từng servlet riêng lẻ.

39. Tại sao chúng ta cần sử dụng servlet listener?

Trong ứng dụng web Java, đôi khi chúng ta cần thực hiện một số thao tác quan trọng ngay khi ứng dụng khởi động — ví dụ như khởi tạo kết nối cơ sở dữ liệu, nạp cấu hình, hoặc theo dõi sự kiện session của người dùng. Đây là lúc Servlet Listener phát huy tác dụng.

Giả sử bạn muốn thiết lập một đối tượng kết nối cơ sở dữ liệu (Database Connection) và lưu trữ nó dưới dạng attribute trong ServletContext để tất cả các servlet có thể sử dụng chung. Nếu bạn thực hiện điều này trong các servlet, bạn sẽ phải lặp lại cùng một đoạn mã ở nhiều nơi, gây ra sự dư thừa và khó bảo trì.

Thay vì vậy, Servlet API cung cấp các Listener interface, cho phép bạn lắng nghe và xử lý các sự kiện như:

  • Ứng dụng web khởi động hoặc dừng lại
  • Session người dùng được tạo hoặc hủy
  • Attribute được thêm hoặc gỡ bỏ khỏi session hay context

Nhờ đó, bạn có thể thực hiện các thao tác khởi tạo hoặc dọn dẹp một cách tự động, tập trung và rõ ràng.

40. Xử lý exception trong ứng dụng Java bằng servlet riêng như thế nào?

Các phương thức như doGet() hay doPost() thường ném ra các exception như ServletException hoặc IOException. Tuy nhiên, trình duyệt chỉ hiểu HTML, nên khi một exception xảy ra, servlet container sẽ xử lý và trả về một trang lỗi HTML mặc định — thường không thân thiện hoặc hữu ích với người dùng.

Để cải thiện trải nghiệm người dùng, Servlet API cho phép bạn cấu hình một servlet xử lý lỗi hoặc exception riêng biệt, được định nghĩa trong file web.xml. Servlet này sẽ được gọi tự động khi xảy ra lỗi hoặc exception, giúp bạn:

  • Hiển thị thông báo lỗi tùy chỉnh thân thiện hơn
  • Cung cấp liên kết trở về trang chủ
  • Gửi thêm thông tin giải thích về sự cố cho người dùng

Ví dụ cấu hình trong web.xml:

<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>

Với cấu hình này, bất kỳ lỗi 404 hoặc exception kiểu ServletException nào cũng sẽ được chuyển tiếp đến servlet /AppExceptionHandler.

41. Deployment Descriptor trong Java Servlet là gì và dùng để làm gì?

Deployment Descriptor là tập tin cấu hình chính của ứng dụng web Java, có tên là web.xml và nằm trong thư mục WEB-INF. Đây là nơi servlet container (như Tomcat) đọc để biết cách khởi tạo và quản lý ứng dụng web của bạn.

Thông qua file web.xml, bạn có thể cấu hình:

  • Các servlet và tham số khởi tạo của servlet
  • Các tham số khởi tạo ở mức ứng dụng (context)
  • Các filter, listener
  • Trang chào mừng (welcome page)
  • Cách xử lý lỗi và exception

Từ Servlet 3.0 trở đi, bạn có thể sử dụng annotations để cấu hình trực tiếp trong mã Java thay vì viết dài dòng trong web.xml, giúp mã ngắn gọn và dễ quản lý hơn. Tuy nhiên, với những cấu hình tổng thể và nâng cao, web.xml vẫn là một thành phần quan trọng không thể thiếu.

42. Làm thế nào để đảm bảo một servlet được tải khi ứng dụng khởi động?

Thông thường, servlet chỉ được khởi tạo (load) khi có request đầu tiên từ client gửi đến. Tuy nhiên, với những servlet “nặng”, mất thời gian khởi tạo, hoặc cần thiết lập trước khi ứng dụng chạy (ví dụ: kết nối CSDL, nạp cấu hình…), chúng ta nên yêu cầu servlet container tải servlet ngay khi ứng dụng khởi động.

Để làm điều đó, bạn có thể sử dụng cấu hình <load-on-startup> trong file web.xml như sau:

<servlet>
	<servlet-name>foo</servlet-name>
	<servlet-class>com.foo.servlets.Foo</servlet-class>
	<load-on-startup>5</load-on-startup>
</servlet> 

Giải thích:

  • Nếu giá trị load-on-startup là 0 hoặc số nguyên dương, servlet sẽ được nạp khi ứng dụng khởi động.
  • Nếu là số âm hoặc không khai báo, servlet chỉ được nạp khi có request đầu tiên.
  • Khi có nhiều servlet cùng cấu hình load-on-startup, servlet có giá trị nhỏ hơn sẽ được nạp trước.

Ngoài ra, nếu bạn dùng annotation @WebServlet từ Servlet 3.0, bạn cũng có thể cấu hình trực tiếp trong Java bằng thuộc tính loadOnStartup.

43. Làm thế nào để lấy đường dẫn thực tế của servlet trên server?

Đôi khi bạn cần biết đường dẫn thực tế (physical path) của một servlet hoặc tài nguyên trên hệ thống file của server, ví dụ để đọc/ghi file hoặc kiểm tra cấu trúc thư mục. Trong Java Servlet, bạn có thể sử dụng đoạn mã sau để lấy đường dẫn đó:

String realPath = getServletContext().getRealPath(request.getServletPath());

Giải thích:

  • getServletContext() trả về context của ứng dụng web.
  • getServletPath() lấy đường dẫn của servlet trong URL.
  • getRealPath() chuyển đường dẫn tương đối này thành đường dẫn vật lý đầy đủ trên hệ thống file của server.

Ví dụ: nếu servlet của bạn có đường dẫn /user/home và ứng dụng được triển khai tại /usr/local/tomcat/webapps/MyApp, thì kết quả có thể là:

/usr/local/tomcat/webapps/MyApp/user/home

Lưu ý: Trong một số server hiện đại hoặc khi triển khai ứng dụng dạng.war không giải nén, getRealPath() có thể trả về null. Khi đó, nên cân nhắc cách tiếp cận khác (ví dụ: dùng getResourceAsStream() để đọc file trong classpath).

44. Làm thế nào để lấy thông tin server trong một servlet?

Nếu bạn muốn lấy thông tin về máy chủ (server) đang chạy ứng dụng servlet (ví dụ: tên và phiên bản của server như Apache Tomcat, Jetty…), bạn có thể sử dụng phương thức getServerInfo() từ đối tượng ServletContext.

String serverInfo = getServletContext().getServerInfo();

45. Viết một servlet để upload file lên server.

Việc upload file là một chức năng phổ biến trong các ứng dụng web Java. Tuy nhiên, Servlet API không hỗ trợ đầy đủ các phương thức để xử lý upload file một cách dễ dàng, đặc biệt trong các phiên bản trước Servlet 3.0.

Để đơn giản hóa quá trình này, bạn có thể sử dụng thư viện Apache Commons FileUpload, một công cụ mạnh mẽ và phổ biến hỗ trợ xử lý dữ liệu multipart/form-data định dạng được sử dụng khi gửi file qua biểu mẫu HTML.

Với sự trợ giúp của thư viện này, bạn có thể:

  • Nhận và xử lý các file người dùng tải lên
  • Lưu file vào thư mục cụ thể trên server
  • Thêm logic xác thực định dạng hoặc kích thước file nếu cần

46. Làm thế nào để kết nối cơ sở dữ liệu và tích hợp log4j trong servlet?

Khi làm việc với các ứng dụng web Java có sử dụng kết nối cơ sở dữ liệu thường xuyên, một trong những cách hiệu quả và dễ quản lý nhất là khởi tạo kết nối trong ServletContextListener. Sau đó, bạn có thể lưu kết nối này như một context attribute, để các servlet khác có thể tái sử dụng mà không cần tạo mới mỗi lần.

Tương tự, việc tích hợp Log4j thư viện logging phổ biến trong Java cũng rất đơn giản. Bạn chỉ cần:

  1. Tạo file cấu hình Log4j (XML hoặc .properties)
  2. Cấu hình Log4j trong ServletContextListener để được khởi tạo khi ứng dụng bắt đầu chạy

Cách làm này giúp bạn:

  • Tách biệt logic cấu hình khỏi servlet
  • Tránh việc tạo lại kết nối hoặc logger nhiều lần
  • Dễ dàng quản lý tài nguyên, đặc biệt khi ứng dụng mở rộng

47. Làm thế nào để lấy địa chỉ IP của client trong servlet?

Để lấy địa chỉ IP của người dùng truy cập ứng dụng web, bạn có thể sử dụng phương thức:

String clientIp = request.getRemoteAddr();

Phương thức getRemoteAddr() sẽ trả về địa chỉ IP của client gửi request đến servlet. Đây là cách đơn giản và phổ biến nhất để truy xuất IP trong ứng dụng Java web.

48. Những tính năng quan trọng của Servlet 3 là gì?

Servlet 3.0 là một bản phát hành lớn của Servlet API, mang lại nhiều cải tiến đáng kể giúp việc phát triển ứng dụng web bằng Java trở nên dễ dàng, linh hoạt và hiện đại hơn. Dưới đây là những tính năng quan trọng nhất:

  1. Hỗ trợ Annotation

Trước Servlet 3.0, toàn bộ cấu hình servlet, filter, listener và các tham số khởi tạo đều phải viết thủ công trong file web.xml dễ sai và khó quản lý khi dự án lớn. Servlet 3.0 cho phép dùng annotation trực tiếp trong mã Java, giúp đơn giản hóa cấu hình.

Một số annotation quan trọng:

  • @WebServlet
  • @WebInitParam
  • @WebFilter
  • @WebListener
  1. Web Fragment

Trước đây, toàn bộ cấu hình phải gom hết vào một file web.xml, rất dễ rối. Servlet 3.0 giới thiệu Web Fragment cho phép chia nhỏ cấu hình thành nhiều module thông qua các file web-fragment.xml (đặt trong thư mục META-INF của từng JAR). Điều này đặc biệt hữu ích trong các ứng dụng lớn hoặc có cấu trúc module.

  1. Thêm thành phần web động

Với ServletContext, bạn có thể thêm servlet, filter và listener một cách lập trình (runtime), giúp tạo ra các hệ thống linh hoạt và tối ưu hiệu suất.

Các phương thức sử dụng:

  • addServlet()
  • addFilter()
  • addListener()
  1. Xử lý bất đồng bộ (Asynchronous Processing)

Trước đây, mỗi request chiếm một luồng servlet cho đến khi xử lý xong. Servlet 3.0 cho phép xử lý bất đồng bộ, giúp chuyển công việc sang một luồng khác, giải phóng tài nguyên servlet và tăng khả năng chịu tải của ứng dụng.

49. Có những cách nào để thực hiện xác thực trong servlet?

Servlet container (như Tomcat, Jetty…) hỗ trợ nhiều cách khác nhau để thực hiện xác thực người dùng trong ứng dụng web Java. Dưới đây là các phương pháp phổ biến:

  1. HTTP Basic Authentication

Đây là hình thức xác thực cơ bản nhất. Trình duyệt sẽ hiện ra một hộp thoại yêu cầu người dùng nhập username và password. Dữ liệu được gửi qua mỗi request dưới dạng Base64.

  1. HTTP Digest Authentication

Giống như Basic Authentication nhưng bảo mật hơn, vì mật khẩu không được gửi trực tiếp mà được mã hóa. Tuy nhiên, phương pháp này ít được dùng phổ biến do sự phức tạp trong cấu hình và giới hạn hỗ trợ trình duyệt.

  1. HTTPS Authentication

Sử dụng chứng chỉ số (SSL/TLS) để xác thực người dùng thường áp dụng trong các hệ thống bảo mật cao. Cần cấu hình server sử dụng giao thức HTTPS và chứng chỉ số của client.

  1. Form-Based Authentication

Cách xác thực phổ biến nhất trong các ứng dụng web hiện đại. Người dùng đăng nhập qua một trang HTML tùy biến. Servlet container sẽ xử lý thông tin đăng nhập, nhưng bạn có thể tự thiết kế giao diện trang login theo yêu cầu

50. Làm thế nào để đảm bảo bảo mật lớp truyền tải (transport layer) cho ứng dụng web của chúng ta?

Để đảm bảo bảo mật lớp truyền tải (Transport Layer Security – TLS) cho ứng dụng web, bạn cần cấu hình servlet container (như Apache Tomcat) để sử dụng SSL khi truyền tải dữ liệu qua mạng. Việc này giúp mã hóa toàn bộ thông tin trao đổi giữa client và server, ngăn chặn rò rỉ dữ liệu và tấn công kiểu nghe lén (sniffing). Các bước chính để triển khai:

  1. Tạo chứng chỉ số (digital certificate)
    • Với môi trường phát triển: sử dụng công cụ keytool của Java để tạo chứng chỉ tự ký.
    • Với môi trường sản xuất: nên sử dụng chứng chỉ SSL từ các nhà cung cấp uy tín như Verisign, Entrust, v.v.
  2. Cấu hình Tomcat để dùng SSL
    • Mở file server.xml trong Tomcat, cấu hình connector cho cổng HTTPS (mặc định là 8443 hoặc 443).
    • Trỏ đến file keystore chứa chứng chỉ và khai báo password.
  3. (Tùy chọn) Thiết lập tự động chuyển hướng từ HTTP sang HTTPSĐảm bảo mọi request đều sử dụng kết nối bảo mật.

Tổng kết

Trên đây là tập hợp những câu hỏi phỏng vấn Servlet phổ biến kèm theo phần giải thích chi tiết, giúp bạn nắm chắc các khái niệm cốt lõi cũng như các tính năng nâng cao trong phát triển ứng dụng web với Java. Đây là tài liệu hữu ích không chỉ dành cho các ứng viên chuẩn bị phỏng vấn, mà còn cho các lập trình viên muốn củng cố nền tảng Servlet hoặc ôn tập lại trước khi làm việc với các framework cao hơn như Spring MVC.

Hy vọng những chia sẻ trong bài viết này đã phần nào giúp bạn hệ thống lại kiến thức, tự tin hơn trong các buổi phỏng vấn cũng như trong quá trình xây dựng các ứng dụng web chuyên nghiệp với Java Servlet.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.