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.
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
, hayDELETE
, 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,…
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).
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
HttpServletRequest
vàHttpServletResponse
- 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.
Có 2 cách phổ biến để khai báo tham số khởi tạo cho servlet:
- 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>
- 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 đúngContent-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()
vàgetAttribute()
. - Bạn có thể truy cập nó bất cứ lúc nào trong servlet thông qua:
getServletContext()
.
11. ServletConfig
và ServletContext
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: ServletConfig
và ServletContext
. 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ượngServletConfig
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 chungServletContext
.
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 fileweb.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>
trongweb.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ứcsetAttribute()
và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:
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.
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 PrintWriter
và ServletOutputStream
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: PrintWriter
và ServletOutputStream
. 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ượngServletResponse
.
- Dùng để ghi các thông tin dạng văn bản như
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ủaServletResponse
.
- 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
ServletOutputStream out = response.getOutputStream();
out.write(byteArray);
14. Có thể sử dụng cả PrintWriter
và ServletOutputStream
trong cùng một servlet không?
Chúng ta không thể lấy cả PrintWriter
và ServletOutputStream
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()
và 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()
và 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: HttpServletRequestWrapper
và HttpServletResponseWrapper
. 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
static
vàsession
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.
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 GenericServlet
và HttpServlet
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à GenericServlet
và HttpServlet
. 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 interfaceServlet
. Điều này có nghĩa là bạn có thể sử dụngGenericServlet
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()
và 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:
- 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
. - 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.
- 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 scopeHttpSession
cho session scopeServletContext
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()
và 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:
- 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ó.
- 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. - 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. - 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:
- 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.
- 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ứcinit()
được thực thi. - 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:
- 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.
- 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.
- 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
. - 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
- Session Management APIServlet cung cấp
HttpSession
API để quản lý phiên dễ dàng hơn. Thông quarequest.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 Serializable
và Cloneable
. Để 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()
và 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 encodeRedirectURL
và encodeURL
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ắnjsessionid
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ụngresponse.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:
- Tạo file cấu hình Log4j (XML hoặc
.properties
) - 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:
- 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
- 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.
- 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()
- 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:
- 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.
- 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.
- 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.
- 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:
- 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.
- Với môi trường phát triển: sử dụng công cụ
- 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.
- Mở file
- (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.