Chào mừng bạn đến với Hướng dẫn lập trình Java JSP cho người mới bắt đầu. Trong một số bài viết gần đây, tôi đã chia sẻ khá nhiều về Java Servlet và nhận được phản hồi rất tích cực từ bạn đọc. Vì vậy, tôi bắt đầu một chuỗi bài mới về JSP, và đây là bài viết đầu tiên trong chuỗi đó.

Giới thiệu JSP
Trong hướng dẫn JSP có ví dụ minh họa này, chúng ta sẽ tìm hiểu các kiến thức cơ bản về JSP, những ưu điểm của JSP so với Servlet, vòng đời của JSP, các interface và lớp trong JSP API, cũng như vị trí đặt các file JSP trong ứng dụng web.
Chúng ta cũng sẽ điểm qua các thành phần như chú thích trong JSP (JSP Comments), Scriptlets, Directives, Expression, Declaration và các thuộc tính trong JSP. Một số chủ đề trong số này rất quan trọng và sẽ được trình bày chi tiết hơn trong các bài viết tiếp theo.
Hướng Dẫn JSP
SP là gì và tại sao cần dùng JSP?
JSP (JavaServer Pages) là một công nghệ phía máy chủ (server-side) dùng để tạo ra các ứng dụng web động bằng Java. Có thể xem JSP như một phần mở rộng của công nghệ Servlet, vì nó cung cấp các tính năng hỗ trợ việc xây dựng giao diện người dùng một cách dễ dàng hơn.
Một trang JSP gồm mã HTML, đồng thời cho phép chèn mã Java để tạo nội dung động. Vì các ứng dụng web thường chứa rất nhiều màn hình giao diện, JSP được sử dụng rộng rãi trong các dự án web. Để thu hẹp khoảng cách giữa mã Java và HTML trong JSP, công nghệ này cung cấp các tính năng bổ sung như JSP Tags (thẻ JSP), Expression Language (ngôn ngữ biểu thức) và Custom Tags (thẻ tùy chỉnh). Những tính năng này giúp mã dễ hiểu hơn và hỗ trợ lập trình viên web phát triển các trang JSP một cách nhanh chóng.
Lợi ích của JSP so với Servlet?
- Chúng ta cũng có thể tạo phản hồi HTML từ servlet, nhưng quá trình này vừa rườm rà vừa dễ xảy ra lỗi; khi phải viết một phản hồi HTML phức tạp, việc viết trong servlet sẽ là một cơn ác mộng. JSP giúp giải quyết tình huống này và cung cấp cho chúng ta sự linh hoạt để viết các trang HTML thông thường và chỉ chèn mã Java ở những nơi cần thiết.
- JSP cung cấp các tính năng bổ sung như thư viện thẻ, ngôn ngữ biểu thức và thẻ tùy chỉnh, giúp tăng tốc quá trình phát triển giao diện người dùng.
- Các trang JSP rất dễ triển khai, chúng ta chỉ cần thay thế trang đã chỉnh sửa trên máy chủ và container sẽ tự động xử lý việc triển khai. Còn với servlet, chúng ta phải biên dịch lại và triển khai toàn bộ dự án. Thực tế, Servlet và JSP bổ trợ lẫn nhau: Servlet nên được dùng làm controller phía server và để xử lý logic và giao tiếp với các lớp model, trong khi JSP nên được sử dụng cho tầng hiển thị (presentation layer).
Vòng đời của một trang JSP
Vòng đời của JSP cũng được quản lý bởi container. Thông thường, mỗi web container có chứa servlet container thì cũng sẽ có JSP container để quản lý các trang JSP. Các giai đoạn trong vòng đời của một trang JSP bao gồm:
- Translation (Chuyển đổi) – Các trang JSP không giống như những lớp Java thông thường. JSP container sẽ phân tích cú pháp (parse) các trang JSP và chuyển đổi chúng thành mã nguồn servlet tương ứng. Ví dụ, nếu file JSP có tên là home.jsp, thì file được tạo ra thường có tên là home_jsp.java.
- Compilation (Biên dịch) – Nếu quá trình chuyển đổi thành công, container sẽ tiếp tục biên dịch mã nguồn servlet vừa tạo để sinh ra file .class.
- Class Loading (Nạp lớp) – Khi JSP đã được biên dịch thành lớp servlet, vòng đời của nó sẽ giống như servlet, và lớp đó sẽ được nạp vào bộ nhớ.
- Instance Creation (Tạo đối tượng) – Sau khi lớp JSP được nạp vào bộ nhớ, container sẽ tạo ra một đối tượng từ lớp đó.
- Initialization (Khởi tạo) – Sau khi được khởi tạo, lớp JSP trở thành một servlet thực sự. Tại thời điểm này, các đối tượng ServletConfig và ServletContext sẽ có thể được truy cập từ bên trong lớp JSP.
- Request Processing (Xử lý yêu cầu) – Với mỗi yêu cầu từ phía client, một luồng (thread) mới được tạo ra cùng với các đối tượng ServletRequest và ServletResponse để xử lý và tạo ra phản hồi HTML.
- Destroy (Hủy) – Giai đoạn cuối cùng trong vòng đời JSP, khi nó được gỡ ra khỏi bộ nhớ.
Các phương thức trong vòng đời của JSP
Các phương thức vòng đời của JSP bao gồm:
- jspInit() – Được khai báo trong giao diện JspPage. Phương thức này được gọi một lần duy nhất trong suốt vòng đời của JSP để khởi tạo các tham số cấu hình.
- _jspService(HttpServletRequest request, HttpServletResponse response) – Được khai báo trong giao diện HttpJspPage. Đây là phương thức xử lý các yêu cầu từ khách hàng, được gọi mỗi khi có yêu cầu gửi đến trang JSP.
- jspDestroy() được khai báo trong giao diện JspPage để dỡ bỏ JSP khỏi bộ nhớ.
Ví dụ JSP đơn giản với Eclipse và Tomcat
Chúng ta có thể sử dụng Eclipse IDE để xây dựng dự án web động với các JSP và sử dụng Tomcat để chạy nó. Vui lòng đọc hướng dẫn [Java Web Applications](/community/tutorials/java-web-application-tutorial-for-beginners#first-web-app-servlet) để tìm hiểu cách tạo JSP trong Eclipse một cách dễ dàng và chạy nó trên Tomcat. Một ví dụ trang JSP đơn giản là: `home.jsp`
<%@ page language=”java” contentType=”text/html; charset=US-ASCII” pageEncoding=”US-ASCII”%> <!DOCTYPE html PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN” “https://www.w3.org/TR/html4/loose.dtd“> <html> <head> <meta http-equiv=”Content-Type” content=”text/html; charset=US-ASCII”> <title>First JSP</title> </head> <%@ page import=”java.util.Date” %> <body> <h3>Hi Pankaj</h3><br> <strong>Current Time is</strong>: <%=new Date() %>
</body> </html>
Nếu bạn có một JSP đơn giản chỉ sử dụng các lớp của JRE, thì không cần phải đóng gói nó thành file WAR. Chỉ cần tạo một thư mục trong thư mục `webapps` của Tomcat và đặt file JSP của bạn vào thư mục vừa tạo. Ví dụ, nếu file JSP của bạn nằm tại `apache-tomcat/webapps/test/home.jsp`, thì bạn có thể truy cập nó trên trình duyệt với URL `https://localhost:8080/test/home.jsp`. Nếu host và port của bạn khác, thì bạn cần điều chỉnh URL cho phù hợp.
Vị trí file JSP trong WAR của ứng dụng web
Chúng ta có thể đặt các file JSP ở bất kỳ vị trí nào trong file WAR, tuy nhiên nếu đặt nó bên trong thư mục `WEB-INF`, chúng ta sẽ không thể truy cập trực tiếp từ phía client. Chúng ta có thể cấu hình JSP giống như servlet trong file `web.xml`, ví dụ nếu tôi có một trang JSP ví dụ như sau nằm trong thư mục `WEB-INF`: `test.jsp`.
<%@ page language=”java” contentType=”text/html; charset=US-ASCII” pageEncoding=”US-ASCII”%> <!DOCTYPE html PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN” “https://www.w3.org/TR/html4/loose.dtd“> <html> <head> <meta http-equiv=”Content-Type” content=”text/html; charset=US-ASCII”> <title>Test JSP</title> </head> <body> Test JSP Page inside WEB-INF folder.<br> Init Param “test” value =<%=config.getInitParameter(“test”) %><br> HashCode of this object=<%=this.hashCode() %> </body> </html>
Và tôi cấu hình nó trong file web.xml như sau:
<?xml version=”1.0″ encoding=”UTF-8″?> <web-app xmlns:xsi=”https://www.w3.org/2001/XMLSchema-instance” xmlns=”https://java.sun.com/xml/ns/javaee” xsi:schemaLocation=”https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd” version=”3.0″> <display-name>FirstJSP</display-name>
<servlet> <servlet-name>Test</servlet-name> <jsp-file>/WEB-INF/test.jsp</jsp-file> <init-param> <param-name>test</param-name> <param-value>Test Value</param-value> </init-param> </servlet>
<servlet-mapping> <servlet-name>Test</servlet-name> <url-pattern>/Test.do</url-pattern> </servlet-mapping>
<servlet> <servlet-name>Test1</servlet-name> <jsp-file>/WEB-INF/test.jsp</jsp-file> </servlet>
<servlet-mapping> <servlet-name>Test1</servlet-name> <url-pattern>/Test1.do</url-pattern> </servlet-mapping> </web-app>
Sau đó, tôi có thể truy cập nó bằng cả hai URL: `https://localhost:8080/FirstJSP/Test.do` và `https://localhost:8080/FirstJSP/Test1.do`. Lưu ý rằng container sẽ tạo hai instance trong trường hợp này và cả hai sẽ có đối tượng servlet config riêng, bạn có thể xác nhận điều này bằng cách truy cập các URL đó trên trình duyệt. Với URI `Test.do`, bạn sẽ nhận được phản hồi như bên dưới.
Test JSP Page inside WEB-INF folder. Init Param “test” value =Test Value HashCode of this object=1839060256
For Test1.do URI, you will get response like below.
Test JSP Page inside WEB-INF folder. Init Param “test” value =null HashCode of this object=38139054
Lưu ý rằng giá trị của init param trong trường hợp thứ hai là null vì nó không được định nghĩa cho servlet thứ hai, đồng thời cũng lưu ý rằng mã hashcode là khác nhau. Nếu bạn thực hiện các yêu cầu tiếp theo, giá trị hashcode sẽ không thay đổi vì các yêu cầu được xử lý bằng cách tạo một luồng mới bởi container. Bạn có để ý việc sử dụng biến **config** trong ví dụ JSP ở trên nhưng không có biến nào được khai báo không? Đó là vì nó là một trong 9 đối tượng ngầm định có sẵn trong trang JSP, tìm hiểu thêm về chúng tại [**JSP Implicit Objects**](/community/tutorials/jsp-implicit-objects "JSP Implicit Objects with Examples").
Các giao diện và lớp trong JSP API
Tất cả các interface và class cốt lõi của JSP được định nghĩa trong gói `javax.servlet.jsp`. Các interface và class của Expression Language API thuộc gói `javax.servlet.jsp.el`. Các interface và class của JSP Tag Libraries được định nghĩa trong gói `javax.servlet.jsp.tagext`. Tại đây, chúng ta sẽ tìm hiểu về các interface và class của Core JSP API.
- Giao diện JspPage
Giao diện JspPage mở rộng từ giao diện Servlet và khai báo hai phương thức vòng đời của trang JSP là jspInit() và jspDestroy().
- Giao diện HttpJspPage
Giao diện HttpJspPage ****mô tả cách thức tương tác mà một lớp triển khai trang JSP phải tuân theo khi sử dụng giao thức HTTP. Giao diện này khai báo phương thức xử lý của trang JSP cho giao thức HTTP dưới dạng: public void _jspService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException.
- Lớp trừu tượng JspWriter
Tương tự như PrintWriter trong servlet nhưng có thêm khả năng hỗ trợ bộ đệm (buffering). Đây là một trong những biến ngầm định (implicit variables) trong trang JSP với tên là “out”. Lớp này mở rộng từ java.io.Writer và container sẽ cung cấp phần hiện thực cụ thể cho lớp trừu tượng này khi chuyển trang JSP thành servlet. Chúng ta có thể lấy đối tượng của nó thông qua phương thức PageContext.getOut(). Lớp hiện thực cụ thể của JspWriter trong Apache Tomcat là org.apache.jasper.runtime.JspWriterImpl.
- Lớp trừu tượng JspContext
JspContext đóng vai trò là lớp cơ sở cho lớp PageContext và trừu tượng hóa tất cả thông tin không đặc thù cho servlet. JspContext cung cấp cơ chế để lấy đối tượng JspWriter dùng để xuất dữ liệu, cơ chế làm việc với các thuộc tính và API để quản lý các không gian tên theo từng phạm vi khác nhau.
- Lớp trừu tượng PageContext
PageContext mở rộng từ JspContext để cung cấp thông tin ngữ cảnh hữu ích khi JSP được sử dụng cho các ứng dụng web. Một thể hiện của PageContext cung cấp quyền truy cập vào tất cả các không gian tên liên quan đến một trang JSP, truy cập vào nhiều thuộc tính của trang, cũng như là một lớp trừu tượng bên trên các chi tiết hiện thực. Các đối tượng ngầm định được tự động thêm vào pageContext.
- Lớp trừu tượng JspFactory
JspFactory ****là một lớp định nghĩa một số phương thức nhà máy có sẵn cho trang JSP trong thời gian chạy, nhằm mục đích tạo ra các thể hiện của nhiều giao diện và lớp khác nhau được sử dụng để hỗ trợ JSP.
- Lớp trừu tượng JspEngineInfo JspEngineInfo là một lớp cung cấp thông tin về công cụ JSP hiện tại.
- Lớp cuối cùng của ErrorData
Chứa thông tin về một lỗi, được sử dụng cho các trang xử lý lỗi.
- Lớp JspException là một ngoại lệ tổng quát được JSP container nhận biết, tương tự như ServletException. Nếu các trang JSP ném ra JspException, thì cơ chế errorPage sẽ được sử dụng để hiển thị thông tin lỗi cho người dùng.
- Lớp JspTagException
Một ngoại lệ được sử dụng bởi Tag Handler để chỉ ra rằng đã xảy ra một lỗi nghiêm trọng không thể khắc phục được.
- Lớp SkipPageException
Ngoại lệ được dùng để chỉ ra rằng trang đang gọi phải dừng việc xử lý tiếp theo. Ngoại lệ này thường được ném ra bởi một simple tag handler để yêu cầu phần còn lại của trang không được thực thi nữa. Ngoại lệ này không nên được ném thủ công trong một trang JSP.
JPS Comments
Vì JSP được xây dựng dựa trên HTML, chúng ta có thể viết chú thích trong file JSP giống như chú thích HTML như `<-- This is HTML Comment -->`. Những chú thích này được gửi đến client và chúng ta có thể xem chúng bằng tùy chọn xem mã nguồn của trình duyệt. Chúng ta có thể đặt chú thích trong file JSP như: `<%-- This is JSP Comment--%>`. Chú thích này phù hợp cho các nhà phát triển để ghi chú ở cấp độ mã vì chúng không được gửi trong phản hồi đến client.
JSP Scriptlets
Scriptlet tags là cách dễ nhất để chèn mã Java vào một trang JSP. Một scriptlet tag bắt đầu với `<%` và kết thúc với `%>`. Bất kỳ mã nào được viết bên trong scriptlet tags sẽ được đưa vào phương thức `_jspService()`. Ví dụ: `<% Date d = new Date(); System.out.println("Current Date=" + d); %>`
JSP Expression
Vì hầu hết thời gian chúng ta in dữ liệu động trong trang JSP bằng phương thức *out.print()*, nên có một cách viết tắt để thực hiện điều này thông qua JSP Expressions. JSP Expression bắt đầu với `<%=` và kết thúc với `%>`. `<% out.print("Pankaj"); %>` có thể được viết bằng JSP Expression là `<%= "Pankaj" %>`. Lưu ý rằng bất kỳ nội dung nào nằm giữa `<%= %>` sẽ được truyền làm tham số cho phương thức `out.print()`. Cũng lưu ý rằng scriptlet có thể chứa nhiều câu lệnh Java và luôn kết thúc bằng dấu chấm phẩy (;) nhưng expression thì không kết thúc bằng dấu chấm phẩy.
JSP Directives
JSP Directives được sử dụng để đưa ra các chỉ dẫn đặc biệt cho container trong quá trình trang JSP được biên dịch thành mã nguồn servlet. JSP directives bắt đầu với `<%@` và kết thúc với `%>`. Ví dụ, trong ví dụ JSP ở trên, tôi đang sử dụng *page* directive để hướng dẫn trình biên dịch JSP của container import lớp Date.
JSP Declaration
JSP Declarations được sử dụng để khai báo các phương thức và biến thành viên của lớp servlet. JSP Declarations bắt đầu với `<%!` và kết thúc với `%>`. Ví dụ, chúng ta có thể tạo một biến int trong JSP ở cấp độ lớp như sau: `<%! public static int count=0; %>`
Vị trí file Servlet được sinh ra từ JSP trong Tomcat
Once JSP files are translated to Servlet source code, the source code (.java) and compiled classes both are place in **Tomcat/work/Catalina/localhost/FirstJSP/org/apache/jsp** directory. If the JSP files are inside other directories of application, the directory structure is maintained. For JSPs inside WEB-INF directory, its source and class files are inside **Tomcat/work/Catalina/localhost/FirstJSP/org/apache/jsp/WEB\\_002dINF** directory. Here is the source code generated for above test.jsp page. `test_jsp.java`
/*
- Generated by the Jasper component of Apache Tomcat
- Version: Apache Tomcat/7.0.32
- Generated at: 2013-08-21 03:40:59 UTC
- Note: The last modified time of this file was set to
-
the last modified time of the source file after -
generation to assist with modification tracking.
*/ package org.apache.jsp.WEB_002dINF;
import javax.servlet.; import javax.servlet.http.; import javax.servlet.jsp.*;
public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent {
private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory();
private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.tomcat.InstanceManager _jsp_instancemanager;
public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; }
public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); }
public void _jspDestroy() { }
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException {
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
try {
response.setContentType("text/html; charset=US-ASCII");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\\n");
out.write("<!DOCTYPE html PUBLIC \\"-//W3C//DTD HTML 4.01 Transitional//EN\\" \\"<https://www.w3.org/TR/html4/loose.dtd\\">\\n">);
out.write("<html>\\n");
out.write("<head>\\n");
out.write("<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=US-ASCII\\">\\n");
out.write("<title>Test JSP</title>\\n");
out.write("</head>\\n");
out.write("<body>\\n");
out.write("Test JSP Page inside WEB-INF folder.<br>\\n");
out.write("Init Param \\"test\\" value =");
out.print(config.getInitParameter("test") );
out.write("<br>\\n");
out.write("HashCode of this object=");
out.print(this.hashCode() );
out.write("\\n");
out.write("</body>\\n");
out.write("</html>");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try { out.clearBuffer(); } catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
} }
Notice following points in above servlet code;
- The package of class starts with org.apache.jsp and if JSPs are inside other folders, it includes directory hierarchy too. Usually we dont care about it.
- The generates servlet class is final and can't be extended.
- It extends `org.apache.jasper.runtime.HttpJspBase` that is similar to HttpServlet except that it's internal to Tomcat JSP Translator implementation. HttpJspBase extends HttpServlet and implements HttpJspPage interface.
- Notice the local variables at the start of \\_jspService() method implementation, they are automatically added by JSP translator and available for use in service methods, i.e in scriptlets.As a java programmer, sometimes it helps to look into the generated source for debugging purposes.
Tham số khởi tạo JSP
Chúng ta có thể định nghĩa các tham số khởi tạo cho trang JSP như trong ví dụ ở trên và có thể truy xuất chúng trong JSP bằng đối tượng ngầm định **config**, chúng ta sẽ tìm hiểu chi tiết hơn về các đối tượng ngầm định trong JSP trong các bài viết sau.
Ghi đè phương thức init() trong JSP
Chúng ta có thể ghi đè phương thức khởi tạo của JSP để tạo các tài nguyên được sử dụng bởi phương thức `service()` của JSP bằng cách sử dụng JSP Declaration tags, chúng ta có thể ghi đè `jspInit()`, `jspDestroy()` hoặc bất kỳ phương thức nào khác. Tuy nhiên, chúng ta không nên ghi đè phương thức `_jspService()` vì bất kỳ nội dung nào được viết trong JSP đều sẽ được đưa vào phương thức này.
Các thuộc tính trong JSP
Ngoài các thuộc tính chuẩn của servlet với phạm vi request, session và context, trong JSP chúng ta còn có một phạm vi khác cho các thuộc tính, đó là Page Scope, có thể truy xuất thông qua đối tượng `pageContext`. Chúng ta sẽ tìm hiểu tầm quan trọng của nó trong bài hướng dẫn về custom tags. Đối với lập trình JSP thông thường, chúng ta không cần quan tâm đến page scope.
Đó là tất cả cho hướng dẫn ví dụ JSP dành cho người mới bắt đầu. Tôi hy vọng nó sẽ giúp bạn hiểu được các khái niệm cơ bản về JSP và hỗ trợ bạn trong việc bắt đầu. Chúng ta sẽ tìm hiểu thêm các tính năng khác của JSP trong các bài viết sau.