Servlet与JSP指南
# 介绍
# 1 什么是Servlet
Servlet是一种Java编写的服务器端程序,用于处理客户端的请求并生成响应。
Servlet主要用于构建基于Web的应用程序。它运行在Servlet容器(如Tomcat、Jetty等)中,并依赖于容器提供的运行时环境。Servlet能够接收来自客户端的各种类型的请求(如HTTP请求),并生成动态的响应(如HTML、XML等)。 Servlet在处理请求时可以执行各种操作,包括但不限于以下内容:
解析请求参数:Servlet能够获取来自客户端请求的参数,包括查询字符串中的参数、表单提交的参数等。
访问请求头信息:Servlet可以获取客户端发送的请求头部信息,如User-Agent、Cookie等。
处理业务逻辑:Servlet可以执行业务逻辑,例如查询数据库、调用其他服务、计算等。
生成动态内容:Servlet能够生成动态的响应内容,如使用模板引擎生成HTML页面、生成XML数据等。
与其他组件交互:Servlet可以与其他Java组件(如数据库、消息队列、外部API等)进行交互,以完成特定的任务。
处理会话管理:Servlet能够管理用户会话,包括创建会话、读取和写入会话属性、使会话失效等。
通过Java Servlet规范,Servlet提供了一套标准的API,用于处理请求和生成响应。开发人员可以根据这些API编写Servlet类,并将其部署到Servlet容器中运行。
# 2 什么是JSP
JSP(JavaServer Pages)是一种基于HTML的动态网页技术,允许在网页中嵌入Java代码。它是一种用于构建Web应用程序的服务器端技术。
JSP的工作原理是将包含Java代码的JSP文件转换为对应的Servlet,并由Servlet容器(如Tomcat)在服务器端进行解析和执行。最终,动态生成的HTML、XML或其他格式的内容将发送给客户端浏览器进行显示。
JSP允许开发人员在网页中直接嵌入Java代码,从而可以实现以下功能:
动态内容生成:通过在JSP文件中插入Java代码,可以根据特定条件或数据生成动态内容。这使得开发人员能够根据用户请求或其他因素动态生成页面内容。
数据交互和处理:JSP可以与Java组件(如JavaBean、数据库连接等)进行交互,从而实现数据的读取、处理和展示。它可以使用Java代码访问数据库、调用业务逻辑等。
分离业务逻辑和表示层:JSP使用模板技术,将业务逻辑和表示层分离开来。开发人员可以将Java代码放在JSP中,而将HTML和页面布局放在模板中,使得业务逻辑和页面设计更清晰、易于维护。
支持标签库和自定义标签:JSP提供了标签库(如JSTL)和自定义标签的机制,以便开发人员可以更方便地进行页面设计和开发。标签库提供了一些常用的标签,用于简化页面的逻辑和展示。
与Servlet紧密集成:JSP本质上是Servlet的一种简化形式,它使用Servlet容器来解析和执行JSP文件。因此,JSP可以与Servlet紧密集成,共同构建Web应用程序。
# 3 Servlet和JSP的关系
Servlet和JSP是紧密相关的技术,它们都用于构建Java Web应用程序。Servlet主要用于处理请求和生成响应,而JSP用于创建动态的网页内容。在许多情况下,Servlet和JSP会同时使用。
# 4 开发环境准备
你必须先有一个JDK环境 (opens new window)。
本文以Tomcat为例,你需要对Tomcat基本认知,可以参考:Tomcat体系介绍及应用
# Servlet基础
# 1 发展历史概述
Servlet的历史发展:
Servlet 1.0: Servlet的最初版本,于1997年发布。它是Java平台的一部分,提供了基本的Servlet API,允许开发人员创建Servlet来处理HTTP请求和生成动态内容。
Servlet 2.2: 于1999年发布,引入了JSP(JavaServer Pages)技术,并将Servlet API与JSP技术整合在一起。Servlet 2.2增加了一些新功能,如过滤器(Filter)、监听器(Listener)和会话管理。
Servlet 2.3: 于2001年发布,作为Java的一部分。Servlet 2.3引入了注解和文件上传等新特性,提供了更便捷和灵活的方式来开发Servlet。
Servlet 2.4: 于2003年发布,作为Java的一部分。Servlet 2.4主要引入了对Java 5的支持,并提供了一些新的特性和改进,如更好的错误处理和会话管理。
Servlet 2.5: 于2006年发布,作为Java的一部分。Servlet 2.5主要提供了对Java 6的支持,并引入了一些新的功能,如注解的支持、更好的会话管理和异步处理。
Servlet 3.0: 于2009年发布,作为Java EE 6的一部分。Servlet 3.0引入了一系列新特性,包括基于注解的配置、异步处理、非阻塞I/O和安全性增强等。此外,Servlet 3.0还提供了更简化的部署描述符,使得部署更加灵活和便捷。
Servlet 3.1: 于2013年发布,作为Java EE 7的一部分。Servlet 3.1引入了对Java 7的支持,并提供了一些新的功能,如非阻塞I/O的改进、HTTP协议升级和WebSockets的支持。
Servlet 3.2: 于2013年发布,作为Java EE 7的一部分。Servlet 3.2主要提供了一些增强和改进,以提高性能、安全性和可靠性。
Servlet 4.0: 于2017年发布,作为Java EE 8的一部分。Servlet 4.0引入了一些新特性,如HTTP/2支持、响应式编程模型和服务工作者(Service Worker)等。
Servlet 5.0: 于2020年发布,作为Jakarta EE 9的一部分。Servlet 5.0仅改变了包名,从
javax.*
改为了jakarta.*
。Servlet 6.0: 于2022年发布,作为Jakarta EE 10的一部分。Servlet 6.0最低支持Java 11,模块化了Servlet API。
从Java EE 9开始,Java EE规范改名为Jakarta EE。目前,Jakarta EE继续发展并推出了相应的版本,其中包括了Servlet的相关规范和技术。
# 2 Servlet生命周期
Servlet生命周期包括初始化、服务处理和销毁阶段。在本节中,我们将详细介绍每个阶段的功能,并提供相应的代码示例。
# 初始化阶段
在初始化阶段,Servlet容器负责创建Servlet实例并调用其init()方法。我们可以在init()方法中进行一些初始化操作,例如加载配置文件、建立数据库连接等。
下面是一个简单的Servlet初始化示例:
import javax.servlet.*;
public class MyServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
// 初始化代码
// 可以获取Servlet配置信息和执行其他初始化操作
}
// 其他方法和代码省略...
}
# 服务处理阶段
在服务处理阶段,Servlet容器将根据请求调用Servlet的service()方法来处理客户端请求并生成响应。我们需要重写service()方法并编写处理逻辑。
以下是一个简单的Servlet服务处理示例:
import javax.servlet.*;
import javax.servlet.http.*;
public class MyServlet extends HttpServlet {
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 处理请求逻辑
// 可以通过request对象获取请求信息,通过response对象生成响应内容
}
}
# 销毁阶段
在销毁阶段,Servlet容器会调用Servlet的destroy()方法来进行清理工作。我们可以在destroy()方法中释放资源、关闭数据库连接等操作。
以下是一个简单的Servlet销毁示例:
import javax.servlet.*;
public class MyServlet implements Servlet {
// 初始化和其他方法省略...
public void destroy() {
// 清理代码
// 在Servlet容器关闭或重载Web应用程序时调用
}
}
# 3 Servlet配置和映射
Servlet的配置和映射对于Web应用程序的正确运行非常重要。
配置Servlet有两种方式:通过注解和通过部署描述符(web.xml)文件。
使用注解配置一个Servlet:
- 在Servlet类上使用
@WebServlet
注解,并指定该Servlet的映射URL或其他属性:
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
// Servlet代码...
}
在上述示例中,@WebServlet(urlPatterns = "/hello")
注解将Servlet映射到URL路径为 "/hello" 的请求。
部署应用程序时,确保支持Servlet 3.0及以上版本的Servlet容器,因为注解配置是在Servlet 3.0中引入的。
确保Servlet类在应用程序的类路径下,并正确部署到Servlet容器中。
通过以上步骤,Servlet就成功使用注解进行了配置。在应用程序启动时,Servlet容器会自动扫描并加载带有 @WebServlet
注解的Servlet类,并根据注解的配置信息进行映射和处理请求。
需要注意的是,在使用注解配置Servlet时,可以指定多个属性,例如 name
、description
、loadOnStartup
、initParams
等,根据需要进行配置。
使用web.xml配置一个Servlet:
配置文件地址是WEB-INF/web.xml。
进行配置
<web-app>
<!-- 其他配置省略... -->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
# 4 请求和响应对象
在Servlet中,请求和响应对象是处理客户端请求和生成服务器响应的关键组件。在本节中,我们将详细介绍HttpServletRequest和HttpServletResponse对象的功能和用法。
# HttpServletRequest对象
HttpServletRequest对象提供了访问HTTP请求的方法和属性。通过该对象,我们可以获取请求的URL、参数、头部信息等。
以下是一些HttpServletRequest对象的常用方法示例:
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取请求URL
String url = request.getRequestURL().toString();
// 获取请求方法
String method = request.getMethod();
// 获取请求参数
String paramValue = request.getParameter("paramName");
// 获取请求头信息
String userAgent = request.getHeader("User-Agent");
}
# HttpServletResponse对象
HttpServletResponse对象用于生成服务器的HTTP响应。通过该对象,我们可以设置响应的状态码、头部信息以及响应内容。
以下是一些HttpServletResponse对象的常用方法示例:
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应状态码
response.setStatus(HttpServletResponse.SC_OK);
// 设置响应头信息
response.setHeader("Content-Type", "text/html");
// 设置响应内容
PrintWriter out = response.getWriter();
out.println("<html><body>Hello, World!</body></html>");
out.close();
}
# 5 处理GET请求
GET请求是最常见的HTTP请求类型之一。在Servlet中,我们可以使用doGet()方法来处理GET请求,并从URL中获取参数。
以下是一个处理GET请求的示例:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取URL参数
String paramValue = request.getParameter("paramName");
// 处理GET请求逻辑...
}
# 6 处理POST请求
POST请求通常用于向服务器提交数据。在Servlet中,我们可以使用doPost()方法来处理POST请求,并从请求体中获取参数。
以下是一个处理POST请求的示例:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取表单参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 处理POST请求逻辑...
}
# 处理请求体
前端请求:
curl -X POST -H "Content-Type: application/json" -d '{
"username": "John",
"password": "123456"
}' http://example.com/api/login
可以看到这里有一个json的请求体。
在Java Servlet中,要处理请求体内容,特别是对于POST请求,可以通过HttpServletRequest对象提供的方法来获取请求体的内容。
对于POST请求,请求体的内容通常是通过表单提交的数据、JSON数据或其他格式的数据。以下是几种常见的处理请求体的方式:
- 通过getInputStream()方法读取请求体:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
InputStream inputStream = request.getInputStream();
// 使用inputStream读取请求体内容
// ...
}
通过调用getInputStream()
方法,可以获取请求体的输入流,然后可以使用输入流的读取方法(如read()
、readLine()
等)来读取请求体内容。
- 通过getReader()方法读取请求体:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BufferedReader reader = request.getReader();
// 使用reader读取请求体内容
// ...
}
通过调用getReader()
方法,可以获取请求体的字符流,然后可以使用BufferedReader
类的方法(如readLine()
)来逐行读取请求体内容。
需要注意的是,通过getInputStream()
方法和getReader()
方法获取请求体的内容时,只能使用其中一种方式,因为一旦通过其中一种方式读取了请求体,就无法再通过另一种方式读取。
在读取请求体内容后,具体的处理方式取决于请求体的格式和内容。例如,可以将请求体解析为表单参数、JSON对象或其他格式的数据,然后进行相应的处理和解析。
# 7 重定向和转发
重定向和转发是在Servlet中常用的技术,用于将请求从一个资源转发到另一个资源。
# 重定向
重定向是一种将请求从一个URL重定向到另一个URL的方式。在Servlet中,我们可以使用sendRedirect()方法来实现重定向。
以下是一个重定向示例:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 执行一些逻辑...
// 重定向到另一个URL
response.sendRedirect("https://www.example.com");
}
在上述示例中,当GET请求到达Servlet时,它会执行一些逻辑,并通过sendRedirect()方法将请求重定向到"https://www.example.com"。
# 转发
转发是将请求从当前Servlet转发到另一个资源(Servlet、JSP或静态文件)的过程。在Servlet中,我们可以使用RequestDispatcher对象来实现转发。
以下是一个转发示例:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 执行一些逻辑...
// 转发到另一个Servlet或JSP
RequestDispatcher dispatcher = request.getRequestDispatcher("/anotherServlet");
dispatcher.forward(request, response);
}
在上述示例中,当GET请求到达Servlet时,它会执行一些逻辑,并通过RequestDispatcher对象将请求转发到另一个Servlet或JSP(例如"/anotherServlet")。
转发可以将当前请求的所有信息(包括请求参数、头部信息等)传递给目标资源,从而实现数据的共享和处理流程的连贯性。
# 8 过滤器
过滤器是Servlet中一种强大的组件,它允许我们在请求到达Servlet之前或响应发送到客户端之前对其进行处理。
过滤器常用于请求和响应的预处理和后处理操作,如身份验证、日志记录和编码转换等。
以下是一个简单的过滤器示例:
import javax.servlet.*;
import java.io.IOException;
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器初始化代码
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 在请求被处理之前执行的代码
// 执行过滤链中的下一个过滤器或Servlet
chain.doFilter(request, response);
// 在响应发送到客户端之前执行的代码
}
@Override
public void destroy() {
// 过滤器销毁代码
}
}
# 配置过滤器
在Servlet中配置过滤器有两种方式:通过注解和通过部署描述符(web.xml)文件。
使用注解配置过滤器的步骤如下:
- 在过滤器类上使用
@WebFilter
注解,指定过滤器的名称和映射的URL模式或Servlet名称:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class LoggingFilter implements Filter {
// 过滤器代码...
}
在上述示例中,过滤器被映射到所有的URL模式,它将拦截所有的请求。
使用web.xml文件配置过滤器的步骤如下:
- 在web.xml文件中,添加
<filter>
元素来定义过滤器,包括过滤器的名称、类名和初始化参数(可选):
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>com.example.LoggingFilter</filter-class>
<init-param>
<param-name>param1</param-name>
<param-value>value1</param-value>
</init-param>
</filter>
- 在web.xml文件中,添加
<filter-mapping>
元素来映射过滤器到指定的URL模式或Servlet名称:
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在上述示例中,过滤器"LoggingFilter"被映射到所有的URL模式,它将拦截所有的请求。
- 部署应用程序时,确保支持Servlet 3.0及以上版本的Servlet容器,因为注解配置是在Servlet 3.0中引入的。
# 9 监听器
监听器是用于监听Servlet容器中事件的组件。通过监听器,我们可以在Web应用程序中捕获和响应各种事件,如Servlet的生命周期事件、会话创建和销毁事件等。以下是一个监听器示例:
import javax.servlet.*;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
// 会话创建时执行的代码
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// 会话销毁时执行的代码
}
}
# 配置监听器
在Servlet中配置监听器有两种方式:通过注解和通过部署描述符(web.xml)文件。
使用注解配置监听器是一种简便的方式,适用于基于注解的Servlet开发模式。在监听器类上添加@WebListener
注解即可将其声明为监听器。以下是一个示例:
import javax.servlet.*;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyListener implements ServletContextListener {
// 监听器的代码
}
通过部署描述符(web.xml)配置监听器允许更灵活地配置监听器,并适用于传统的Servlet开发模式。在web.xml文件中添加以下配置即可声明监听器:
<listener>
<listener-class>com.example.MyListener</listener-class>
</listener>
在<listener-class>
标签中指定监听器类的完全限定名。
可以在web.xml文件中配置多个监听器,它们将按照配置的顺序依次被调用。
以下是一个完整的web.xml文件示例,配置了一个监听器:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<listener>
<listener-class>com.example.MyListener</listener-class>
</listener>
<!-- 其他配置项 -->
</web-app>
# 10 Servlet异步处理
Servlet异步处理允许长时间运行的任务在不阻塞容器线程的情况下进行处理,从而提高应用程序的性能和吞吐量。
当涉及到处理需要较长时间才能完成的操作时,例如向外部API发起请求、执行复杂的计算或处理大量数据等情况,Servlet异步处理就变得非常有用。
举一个具体的场景,假设有一个在线图像处理的Web应用程序,用户可以上传图像并选择一些处理选项,例如裁剪、调整大小或添加滤镜等。在传统的同步处理方式下,当用户上传图像后,服务器需要完成所有图像处理操作,然后再将处理完的图像返回给用户,这可能需要一段时间,导致用户在等待过程中体验不佳。
通过使用Servlet异步处理,可以改善用户体验。以下是一个使用Servlet异步处理的简化示例:
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@WebServlet(urlPatterns = "/imageProcessing", asyncSupported = true)
public class ImageProcessingServlet extends HttpServlet {
private ExecutorService executorService;
@Override
public void init() throws ServletException {
executorService = Executors.newFixedThreadPool(10); // 创建线程池
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
final AsyncContext asyncContext = request.startAsync();
// 将长时间运行的图像处理任务提交给线程池执行
executorService.execute(() -> {
// 执行图像处理操作
// ...
try {
// 模拟耗时操作
Thread.sleep(5000);
// 完成异步处理
asyncContext.getResponse().getWriter().println("Image processing completed.");
asyncContext.complete();
} catch (Exception e) {
// 处理异常情况
// ...
}
});
// 继续处理其他请求
}
@Override
public void destroy() {
executorService.shutdown(); // 关闭线程池
}
}
在这个例子中,当用户上传图像后,Servlet会立即返回,并将图像处理任务提交给一个线程池进行异步处理。然后,用户可以继续浏览其他页面,而无需等待图像处理完成。一旦图像处理任务完成,异步上下文会被标记为已完成,最终响应会被发送给用户。
这种方式可以提高Web应用程序的性能和吞吐量,同时提升用户体验,使用户能够更快地获得响应并继续浏览其他内容。
# 11 文件上传和下载
文件上传和下载是Web应用程序中常见的需求。我们可以使用Servlet来处理文件的上传和下载操作。以下是一个文件上传和下载的示例:
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
@WebServlet(urlPatterns = "/upload")
@MultipartConfig
public class FileUploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part filePart = request.getPart("file");
String fileName = filePart.getSubmittedFileName();
InputStreaminputStream = filePart.getInputStream();
// 将文件保存到指定路径
FileOutputStream outputStream = new FileOutputStream("/path/to/save/" + fileName);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
// 文件上传成功的处理逻辑
// 文件下载示例
File file = new File("/path/to/file");
FileInputStream fileInputStream = new FileInputStream(file);
// 设置响应头
response.setContentType("application/octet-stream");
response.setContentLengthLong(file.length());
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
OutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
fileInputStream.close();
outputStream.close();
}
}
# 12 Cookie和Session管理
Cookie和Session是Web应用程序中用于存储和管理用户状态的重要机制。
Servlet提供了对Cookie和Session的支持。
以下是一个Cookie和Session的示例:
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
// 验证用户名和密码
if (isValid(username, password)) {
HttpSession session = request.getSession();
session.setAttribute("username", username);
// 创建并添加Cookie
Cookie cookie = new Cookie("username", username);
cookie.setMaxAge(3600); // 设置Cookie的过期时间为1小时
response.addCookie(cookie);
response.sendRedirect("/dashboard");
} else {
response.sendRedirect("/login");
}
}
private boolean isValid(String username, String password) {
// 验证用户名和密码的逻辑
return true;
}
}
# Cookie和Session的工作原理
Cookie和Session是在Web开发中用于维护用户状态的重要机制。它们在不同的层面上工作,并具有不同的工作原理。
# Cookie的工作原理
Cookie是在客户端(浏览器)中存储数据的小型文本文件。当服务器向客户端发送响应时,可以在响应中添加一个名为"Set-Cookie"的HTTP头来设置Cookie。客户端在接收到Cookie后,会将其保存在浏览器中。
当客户端向服务器发送新的请求时,会自动将与该域相关联的Cookie信息包括在请求的"Cookie"头中,以便服务器可以访问该信息。
Cookie的工作原理如下:
- 客户端向服务器发送请求。
- 服务器在响应中添加Set-Cookie头,将Cookie信息发送到客户端。
- 客户端接收到响应后,将Cookie保存在浏览器中。
- 客户端再次向服务器发送请求时,将Cookie信息自动包含在请求的Cookie头中。
- 服务器根据请求中的Cookie信息,可以获取与该客户端关联的状态数据。
Cookie通常用于存储少量的用户信息,例如用户ID、首选项或跟踪标识符。由于Cookie保存在客户端中,因此可以在多个页面之间共享数据。
# Session的工作原理
Session是在服务器端维护用户状态的一种机制。当用户第一次访问网站时,服务器会为该用户创建一个唯一的会话标识符(Session ID),并将其存储在Cookie中,或者通过URL重写将其包含在响应中。
客户端在接收到Session ID后,会将其保存在Cookie中或者通过URL参数传递到服务器端。服务器在接收到客户端的请求时,可以根据Session ID来识别该用户的会话,并从服务器端的存储中获取与该会话相关联的数据。
Session的工作原理如下:
- 客户端向服务器发送请求。
- 服务器在会话存储中查找与请求中的Session ID相关联的数据。
- 如果找到匹配的会话数据,服务器使用该数据来处理请求。
- 如果未找到匹配的会话数据,则创建一个新的会话,并分配一个唯一的Session ID。
- 服务器将Session ID发送给客户端,以便客户端在后续请求中使用。
Session通常用于存储较大量或敏感的用户信息,例如用户的购物车内容、登录状态或用户身份验证令牌等。由于Session数据存储在服务器端,因此相对于Cookie,Session更安全且能够存储更多的数据。
需要注意的是,为了维护与客户端之间的关联,服务器通常会使用一种机制(如Cookie或URL重写)将Session ID与客户端进行传递。具体的传递方式取决于服务器配置和客户端的支持情况。
如果使用Cookie传递Session ID,工作流程如下:
- 服务器在响应中设置名为"Set-Cookie"的HTTP头,将Session ID存储在客户端的Cookie中。
- 客户端在后续的请求中自动包含Cookie,其中包括Session ID。
- 服务器通过读取请求中的Cookie,获取Session ID并从会话存储中检索与该Session ID相关联的数据。
- 服务器使用会话数据处理请求。
如果使用URL重写传递Session ID,工作流程如下:
- 服务器在响应中生成带有Session ID的URL,例如:
http://example.com/page?sessionId=abc123
。 - 客户端在后续的请求中将Session ID包含在URL中。
- 服务器从请求的URL中提取Session ID,并从会话存储中检索与该Session ID相关联的数据。
- 服务器使用会话数据处理请求。
无论是使用Cookie还是URL重写,服务器在后续的请求中都会根据Session ID来识别和检索会话数据,以便为每个客户端维护其对应的状态。
需要注意的是,Session有一个过期时间,服务器可以配置Session的有效期。一旦Session过期或被销毁,相关的会话数据也将被清除。这通常在用户长时间不活动或手动注销时发生。
综上所述,Cookie和Session是常用的用户状态管理机制。Cookie存储在客户端,通过设置和发送HTTP头来传递数据;而Session存储在服务器端,通过Session ID进行关联和传递。根据具体的需求和安全性要求,可以选择适合的机制来维护用户的状态。
# 13 Servlet线程安全
Servlet线程安全是指在多线程环境下,Servlet能够正确处理并发请求,确保数据的一致性和正确性。
Servlet本身不是线程安全的,我们需要使用同步机制或使用线程安全的容器来实现Servlet的线程安全。
以下是一个线程安全的Servlet示例:
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet(urlPatterns = "/counter")
public class CounterServlet extends HttpServlet {
private int count = 0;
@Override
protected synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
count++;
response.getWriter().println("访问次数:" + count);
}
}
在上述示例中,我们使用了synchronized
关键字来确保对count
变量的访问是线程安全的,避免了并发访问导致的数据不一致问题。
# JSP基础
# 1 JSP概述
JSP(JavaServer Pages)是一种基于HTML的动态网页技术,它允许我们在网页中嵌入Java代码。
JSP页面在服务器端被解析和编译为Servlet,然后由Servlet容器进行处理。
JSP的发展历史如下:
JSP 1.0: JSP的最初版本,于1999年推出,作为Java的扩展规范之一,属于Java EE(当时称为J2EE)的一部分。JSP 1.0提供了基本的JSP功能,包括使用标签和脚本元素在HTML中嵌入Java代码。
JSP 1.1: 于2000年发布,引入了对JSP标签库的支持,允许开发人员创建自定义标签以增强JSP的功能。
JSP 1.2: 于2002年发布,引入了JSP Tag Files的概念,它允许将自定义标签定义在独立的文件中,提高了标签的可重用性和可维护性。
JSP 2.0: 于2003年发布,是JSP技术的一次重大升级。引入了Expression Language(EL)和JSTL(JSP Standard Tag Library)等新特性。EL允许在JSP中使用简化的表达式语言访问和操作数据,而JSTL提供了一组常用的标签和函数,简化了JSP页面的开发和维护。
JSP 2.1: 于2004年发布,引入了基于注解的自定义标签(Annotated Custom Tag)的支持,允许使用注解来声明和配置自定义标签,简化了标签处理器的开发和配置。
JSP 2.2: 于2009年发布,作为Java EE 6的一部分。JSP 2.2主要提供了对Java标准的支持,包括对Java 5中引入的注解和泛型的支持。
JSP 2.3: 于2013年发布,作为Java EE 7的一部分。JSP 2.3引入了对Java 7的支持,并提供了一些新的特性和改进,如更好的EL支持、跨上下文EL表达式的引用和静态资源包含等。
JSP 2.4: 于2017年发布,作为Java EE 8的一部分。JSP 2.4主要引入了一些增强和改进,以提高性能、安全性和可靠性。
JSP 3.0: 于2020年发布,作为Jakarta EE 9的一部分。JSP 3.0主要是迁移API从
javax.servlet.jsp
包到jakarta.servlet.jsp
包。JSP 3.1: 于2022年发布,作为Jakarta EE 10的一部分。JSP 3.1进行了很小的迭代,参考:Jakarta Server Pages 3.1 (opens new window)。
从Java EE 9开始,Java EE规范改名为Jakarta EE。目前,Jakarta EE继续发展并推出了相应的版本,其中包括了JSP的相关规范和技术。
# 2 JSP注释
JSP注释是一种用于在JSP页面中添加注释的方式,可以在JSP页面中添加注释来提供代码的说明、维护信息或其他相关备注。
JSP注释可以在JSP页面被解析和执行时被忽略,不会对页面的输出产生任何影响。
JSP注释有两种形式:隐式注释和显示注释。
# 隐式注释
隐式注释使用JSP的特殊语法,使用<%--
和--%>
将注释内容包围起来。
以下是隐式注释的示例:
<%-- 这是一个隐式注释 --%>
在上述示例中,<%--
和--%>
之间的内容会被解析器忽略,不会被输出到最终的页面。
# 显示注释
显示注释使用HTML风格的注释语法,即使用<!--
和-->
将注释内容包围起来。
以下是显示注释的示例:
<!-- 这是一个显示注释 -->
显示注释也会被解析器忽略,不会被输出到最终的页面。
无论是隐式注释还是显示注释,它们都可以在JSP页面的任何位置添加,包括在HTML标记、JSP脚本元素、EL表达式等之间。注释的内容可以是任意文本,用于提供对代码的解释、注解或其他相关信息。
# 3 JSP指令
JSP指令用于指示JSP引擎执行特定的操作。常见的JSP指令包括页面指令(page directive)、包含指令(include directive)和标签库指令(taglib directive)。
# 页面指令(page directive)
页面指令用于指示JSP引擎如何处理整个JSP页面。它们通常放置在JSP页面的顶部,以设置页面的属性和行为。
示例:
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
当使用 <%@ page %>
页面指令时,可以设置多个属性来定义页面的行为和特性。以下是常见的 <%@ page %>
属性列表:
language
:指定在 JSP 页面中使用的脚本语言。常见的取值是"java"
,表示使用 Java 作为脚本语言。extends
:指定生成的 Servlet 类的父类。可以指定自定义的 Servlet 类或继承关系。contentType
:指定响应的内容类型和字符集。例如,"text/html"
表示响应内容为 HTML 类型。pageEncoding
:指定 JSP 页面的字符编码方式。autoFlush
:指定是否自动刷新缓冲区。常见取值是"true"
或"false"
。buffer
:指定输出缓冲区的大小。可以使用字节单位(例如"8kb"
)或指定字节数。isThreadSafe
:指定生成的 Servlet 是否是线程安全的。常见取值是"true"
或"false"
。info
:提供关于 JSP 页面的描述信息。isErrorPage
:指定当前页面是否用作错误处理页面。常见取值是"true"
或"false"
。errorPage
:指定当前页面的错误处理页面,用于在出现错误时跳转到指定的错误处理页面。session
:指定会话(session)的使用方式。常见取值是"true"
、"false"
或"auto"
。isELIgnored
:指定是否忽略表达式语言(EL)的使用。常见取值是"true"
或"false"
。deferredSyntaxAllowedAsLiteral
:指定是否允许将延迟语法用作字面值。常见取值是"true"
或"false"
。trimDirectiveWhitespaces
:指定是否删除页面指令前后的空白字符。常见取值是"true"
或"false"
。imports
:指定需要导入的 Java 类。多个类名之间使用逗号分隔。errorOnUndeclaredNamespace
:指定是否在使用未声明的命名空间时生成错误。常见取值是"true"
或"false"
。
# 包含指令(include directive)
包含指令用于在JSP页面中包含其他文件的内容。这些文件可以是其他JSP页面、HTML文件或文本文件。
<%@ include file="header.jsp" %>
file
:指定要包含的文件路径。可以使用相对路径或绝对路径。
包含指令的作用是将指定文件的内容嵌入到当前的JSP页面中,就好像两个文件的内容合并在一起一样。
# 标签库指令(taglib directive)
标签库指令用于在JSP页面中引入自定义标签库或标准标签库(如JSTL)。标签库提供了一组可重用的自定义标签,用于简化和扩展JSP页面的功能。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
prefix
:指定标签库的前缀,用于在JSP页面中引用标签。uri
:指定标签库的唯一资源标识符(URI),用于标识标签库的位置。
标签库指令的作用是将标签库中定义的自定义标签引入到JSP页面中,以便在页面中使用这些标签。
通过使用页面指令、包含指令和标签库指令,开发人员可以更好地组织和管理JSP页面,引入外部资源并扩展页面的功能。这些指令为JSP提供了更大的灵活性和可扩展性。
# 4 JSP脚本元素
JSP脚本元素用于在JSP页面中嵌入Java代码。我们可以使用脚本元素执行任意的Java语句,包括变量声明、条件语句、循环语句等。以下是一些示例:
<%
String name = "John Doe";
int age = 25;
%>
<p>Name: <%= name %></p>
<p>Age: <%= age %></p>
# 5 JSP表达式
JSP表达式用于在JSP页面中输出表达式的结果。表达式可以是任何有效的Java表达式,并且会自动转换为字符串形式进行输出。
<p>1 + 2 = <%= 1 + 2 %></p>
<p>Hello, <%= "John" %>!</p>
# 6 JSP声明
JSP声明用于在JSP页面中定义变量、方法或类。声明的内容将被插入到生成的Servlet类的相应位置。
<%! int counter = 0; %>
<%
counter++;
%>
<p>Counter: <%= counter %></p>
# 7 JSP动作元素
JSP动作元素是一种特殊的标签,用于执行特定的动作或操作。常见的JSP动作元素包括include动作、forward动作和param动作等。
<jsp:include>
动作元素<jsp:include>
动作元素用于在JSP页面中包含其他页面或资源的内容。它类似于<%@ include %>
页面指令,但是<jsp:include>
动作元素是在运行时动态执行的,而不是在编译时静态处理的。
<jsp:include page="header.jsp" />
在上述示例中,<jsp:include>
动作元素用于包含名为 "header.jsp" 的页面或资源。被包含的页面或资源的内容将动态地合并到当前页面中,就好像两个页面的内容在运行时合并一样。
<jsp:forward>
动作元素<jsp:forward>
动作元素用于将请求转发到其他页面或资源。它类似于服务器端的重定向,但是<jsp:forward>
动作元素是在服务器端内部进行处理的,对客户端是透明的。
<jsp:forward page="error.jsp" />
在上述示例中,<jsp:forward>
动作元素将请求转发到名为 "error.jsp" 的页面或资源。服务器将停止当前页面的处理,并将请求发送到指定的页面或资源,然后将其响应返回给客户端。
<jsp:param>
动作元素<jsp:param>
动作元素用于向包含或转发的页面传递参数。它可以在包含或转发的过程中向目标页面传递数据,以便目标页面可以使用这些数据进行处理。
<jsp:include page="header.jsp">
<jsp:param name="username" value="John" />
<jsp:param name="age" value="25" />
</jsp:include>
在上述示例中,<jsp:param>
动作元素用于向名为 "header.jsp" 的页面传递两个参数:username
和 age
。目标页面可以通过在请求对象中获取这些参数来使用它们。
注意<jsp:include>
和 <% include %>
的不同:
<jsp:include>
和 <% include %>
是两种在 JSP 中包含其他页面内容的方式,它们有以下区别:
语法:
<jsp:include>
是一个动作元素,使用 XML 风格的标记语法,需要以<jsp:include>
开始和结束标记。而<% include %>
是一个 JSP 脚本元素,使用 Java 代码的尖括号脚本语法,不需要开始和结束标记。动态性:
<jsp:include>
是在运行时动态执行的,它会根据当前请求的情况动态地包含其他页面的内容。而<% include %>
是在 JSP 编译时静态处理的,它会在编译时将被包含的页面的内容插入到当前页面中。作用域:
<jsp:include>
在包含页面时,会将包含的页面的作用域与当前页面的作用域进行合并。包含页面的变量和方法可以在当前页面中使用。而<% include %>
是静态处理的,被包含的页面的作用域与当前页面的作用域是独立的。请求处理:
<jsp:include>
可以处理请求并在包含的页面中生成新的响应。它可以递归地进行页面包含,即被包含的页面中仍然可以使用<jsp:include>
来包含其他页面。而<% include %>
是简单地将被包含的页面的内容插入到当前页面中,不会重新生成响应。
# 8 JSP隐式对象
JSP隐式对象是在JSP页面中自动创建的特定对象,可以直接在JSP页面中使用。
常见的JSP隐式对象包括request、response、out、session和application等。我们将介绍这些隐式对象的用途和如何在JSP页面中使用它们。
<p>Request URL: <%= request.getRequestURL() %></p>
<p>Client IP: <%= request.getRemoteAddr() %></p>
<p>Session ID: <%= session.getId() %></p>
<p>Application Name: <%= application.getServletContextName() %></p>
# 9 EL表达式
JSP表达式语言(Expression Language,EL)是一种用于在JSP页面中访问和操作数据的简洁语法。EL提供了一种简单、直观的方式来访问变量、属性、集合和其他数据对象,以及执行基本的算术和逻辑运算。
# 基本语法
EL使用${}
语法来标识表达式。在${}
内部,我们可以引用变量、访问对象属性、调用方法以及执行一些简单的计算和比较操作。以下是一些示例:
<p>User Name: ${user.name}</p>
<p>Age: ${user.age}</p>
<p>Current Time: ${java.time.LocalDateTime.now()}</p>
<p>Sum: ${2 + 3}</p>
<p>Is Admin: ${user.role eq 'admin'}</p>
在上述示例中,${user.name}
表示访问名为user
的对象的name
属性。${2 + 3}
表示执行加法运算,结果为5
。${user.role eq 'admin'}
表示执行字符串相等性比较操作。
# EL的数据访问
EL可以访问多种类型的数据:
- 变量访问:EL可以引用JSP页面中声明的变量,包括通过JSP动作、JSP指令或JSP脚本元素定义的变量。
- 属性访问:EL可以访问JavaBean对象的属性,包括通过getters和setters方法定义的属性。
- 集合访问:EL支持访问List、Map和数组等集合对象,可以通过索引或键来访问集合中的元素。
- 隐式对象:EL提供了对JSP页面上下文中的隐式对象的访问,例如
pageContext
、request
、session
和application
等。
以下是一些示例:
<p>User Name: ${user.name}</p>
<p>Product Price: ${product.price}</p>
<p>First Element of List: ${list[0]}</p>
<p>Value from Map: ${map['key']}</p>
EL访问隐式对象的顺序是:
- PageContext(页面上下文):EL首先尝试从PageContext对象中获取属性值。
- Request(请求对象):如果在PageContext中未找到属性值,EL将尝试从请求对象中获取属性值。
- Session(会话对象):如果在请求对象中未找到属性值,EL将尝试从会话对象中获取属性值。
- Application(应用程序对象):如果在会话对象中未找到属性值,EL将尝试从应用程序对象中获取属性值。
EL按照上述顺序查找隐式对象,如果在任何一个对象中找到了所需的属性值,则停止查找并返回该值。
# EL的运算和比较
EL支持常见的算术和逻辑运算符,例如加法、减法、乘法、除法、与、或、非等。可以在EL表达式中使用这些运算符进行计算和比较。
<p>Sum: ${2 + 3}</p>
<p>Product: ${3 * 4}</p>
<p>Is Equal: ${3 == 3}</p>
<p>Logical AND: ${true && false}</p>
# EL的条件语句
EL提供了条件运算符(三元运算符)和空值判断运算符,用于在EL表达式中执行条件判断。
<p>Max Value: ${a > b ? a : b}</p>
<p>Default Value: ${user.name ?: 'Unknown'}</p>
EL的空值判断运算符,用于判断${user.name}
是否为空。如果${user.name}
不为null或空字符串,则返回${user.name}
的值;如果${user.name}
为null或空字符串,则返回'Unknown'
。
# EL的函数调用
EL支持调用静态方法和实例方法。您可以在EL表达式中调用自定义的方法或使用内置的函数。
<p>Length of String: ${fn:length(str)}</p>
<p>Current Date: ${myUtil.userNumber('5')}</p>
上述示例中,${fn:length(str)}
调用了JSTL的length
函数来计算字符串的长度。${myUtil.userNumber('5')}
调用了自定义的userNumber()
方法来获取当前日期。
# EL的字符串拼接
要在表达式中拼接字符,要使用+=符号:
${firstName += " - " += lastName}
# EL的运算符和保留字
EL支持多种运算符和保留字,用于执行更复杂的表达式和操作。其中包括算术运算符、关系运算符、逻辑运算符、条件运算符、空值判断运算符等。
以下是EL中常见的运算符和保留字:
算术运算符
+
:加法-
:减法*
:乘法/
:除法%
:取模
关系运算符 用于比较两个表达式的值,根据比较的结果返回一个布尔值(true或false)。关系运算符包括:
eq
或==
:等于ne
或!=
:不等于lt
或<
:小于gt
或>
:大于le
或<=
:小于等于ge
或>=
:大于等于
逻辑运算符 用于对一个或两个表达式进行逻辑运算,结果为布尔值(true或false)。逻辑运算符包括:
and
或&&
:逻辑与or
或||
:逻辑或not
或!
:逻辑非
empty
运算符
用于判断一个值或表达式是否为空。如果值为null或者是空字符串,empty
运算符返回true,否则返回false。
条件运算符
? :
:条件运算符(三元运算符)。根据条件的结果返回不同的值。
保留字 在EL中,有一些特殊的保留字具有特定的含义,例如:
true
:布尔型字面量,表示truefalse
:布尔型字面量,表示falsenull
:空值字面量
这些运算符和保留字可以组合使用,以实现复杂的表达式和条件判断。
# EL隐式对象
EL提供了一组隐式对象,这些对象在JSP页面中可直接访问,无需进行任何声明或初始化。这些隐式对象提供了方便的访问途径,以获取与请求、会话、应用程序等相关的信息。下面是EL中常见的隐式对象:
pageContext
:代表JSP页面的上下文对象。它提供了对JSP页面执行环境的访问,包括其他隐式对象的访问和管理。pageScope
:代表JSP页面范围内的对象,可以通过键来访问和设置JSP页面范围的属性。requestScope
:代表请求范围内的对象,可以通过键来访问和设置请求范围的属性。sessionScope
:代表会话范围内的对象,可以通过键来访问和设置会话范围的属性。applicationScope
:代表应用程序范围内的对象,可以通过键来访问和设置应用程序范围的属性。param
:代表请求参数的映射对象,可以通过参数名来获取请求参数的值。paramValues
:代表请求参数值的映射对象,可以通过参数名获取请求参数的多个值。header
:代表请求头信息的映射对象,可以通过请求头名称来获取请求头的值。headerValues
:代表请求头值的映射对象,可以通过请求头名称获取请求头的多个值。cookie
:代表客户端的Cookie信息的映射对象,可以通过Cookie名称来获取Cookie的值。initParam
:代表Servlet初始化参数的映射对象,可以通过初始化参数的名称来获取初始化参数的值。
这些隐式对象提供了方便的访问方式,使开发人员能够在JSP页面中轻松地获取和操作与请求、会话、应用程序等相关的信息。使用隐式对象可以简化代码,使代码更加清晰和易于理解。
# EL的集合操作
通过以下方式直接构建集合或映射:
{1,2,3} // set
[1,2,3] // list
[1, "two", [three,four]]
{"one":1, "two":2, "three":3} // map
stream方法从java.util.Collection或Java数组获取一个Stream,可以进行流操作:
books.stream().filter(b->b.category == 'history')
.map(b->b.title)
.toList()
[1,3,5,2].stream().sorted().toList()
EL支持的一些流操作包括:allMatch、anyMatch、average、count、distinct、filter、findFirst、flatMap、forEach、iterator、limit、map、max、min、noneMatch、peek、reduce、sorted、substream、sum、toArray、toList等。
可以在Operations on Collection Objects (opens new window) 查看详细信息。
# 10 JSP标准标签库(JSTL)
JSP标准标签库(JSTL)是一组自定义标签,提供了更高级和更灵活的功能,使JSP页面的开发更加简洁和易于维护。JSTL包括核心标签库、格式化标签库、函数标签库等。
# 核心标签库
核心标签库(c标签库)是JSTL中最常用和最基础的标签库,它提供了处理流程控制、条件判断、迭代循环等功能。以下是c标签库中常用的标签和示例代码:
# c:out
c:out
标签用于输出变量的值,并自动进行HTML转义。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="name" value="<script>alert('Hello');</script>" />
<c:out value="${name}" />
# c:set
c:set
标签用于设置一个变量的值。
<c:set var="name" value="John" />
<c:set var="age" value="25" />
还有scope
属性,可以设置范围,可选值有:page|request|session|application
# c:remove
c:remove
标签用于从作用域中移除一个变量。
<c:remove var="name" />
# c:if
c:if
标签用于执行条件判断。
<c:if test="${age >= 18}">
<p>You are an adult.</p>
</c:if>
# c:choose、c:when、c:otherwise
c:choose
、c:when
和c:otherwise
标签组合使用,用于实现多条件判断。
<c:choose>
<c:when test="${score >= 90}">
<p>Excellent!</p>
</c:when>
<c:when test="${score >= 60}">
<p>Pass.</p>
</c:when>
<c:otherwise>
<p>Fail.</p>
</c:otherwise>
</c:choose>
# c:url
c:url
标签用于构造URL。
<c:url value="/home.jsp" var="homeUrl">
<c:param name="name" value="John" />
</c:url>
<a href="${homeUrl}">Home</a>
# c:forEach
c:forEach
标签用于进行迭代循环,它支持迭代数组、集合和其他可迭代对象。
<c:forEach var="item" items="${items}">
<p>${item}</p>
</c:forEach>
其中,var
属性指定迭代变量的名称,items
属性指定要迭代的对象。在循环体内,可以通过${item}
来访问当前迭代的元素。
除了基本的迭代功能外,c:forEach
还支持其他属性,如begin
、end
、step
等,用于指定迭代的起始、结束和步长等。
<c:forEach var="i" begin="1" end="10" step="2">
<p>${i}</p>
</c:forEach>
上述示例将迭代从1到10的奇数,并在每次迭代时输出当前的数字。
c:forEach
标签还可以使用varStatus
属性获取迭代状态的对象,可以通过该对象获取迭代过程中的一些状态信息,如索引、计数、是否为第一个或最后一个元素等。以下是一些常用的varStatus
属性和使用方式:
<c:forEach var="item" items="${items}" varStatus="status">
<p>Index: ${status.index}</p>
<p>Count: ${status.count}</p>
<p>First: ${status.first}</p>
<p>Last: ${status.last}</p>
<p>Current: ${status.current}</p>
<p>Total: ${status.total}</p>
</c:forEach>
index
:当前迭代元素的索引(从0开始)。count
:当前迭代元素的计数(从1开始)。first
:是否为第一个迭代元素(true/false)。last
:是否为最后一个迭代元素(true/false)。current
:当前迭代元素的值。total
:总迭代次数。
通过使用这些属性,您可以在循环体内获取并利用迭代状态信息,从而实现更灵活的逻辑和展示效果。例如,根据索引值添加不同的样式、根据是否为最后一个元素显示特定内容等。
<c:forEach var="item" items="${items}" varStatus="status">
<p class="${status.index % 2 == 0 ? 'even' : 'odd'}">${item}</p>
<c:if test="${status.last}">
<p>This is the last item!</p>
</c:if>
</c:forEach>
上述示例根据索引的奇偶性为每个迭代元素应用不同的样式,并在最后一个元素时显示特定内容。
# c:forTokens
c:forTokens
标签用于将字符串按照指定的分隔符进行分割,并进行迭代操作。
<c:forTokens var="token" items="apple,banana,grape" delims=",">
<p>${token}</p>
</c:forTokens>
其中,var
属性指定迭代变量的名称,items
属性指定要进行分割的字符串,delims
属性指定分隔符。在循环体内,可以通过${token}
来访问当前迭代的元素。
上述示例将迭代字符串"apple,banana,grape"中的每个元素,并在每次迭代时输出当前的元素。
c:forTokens
和c:forEach
标签相似,还支持其他属性,如varStatus
、begin
、end
、step
等。
# 格式化标签库(fmt标签库)
格式化标签库(fmt标签库)提供了日期时间格式化、数字格式化等功能,可以使得在JSP页面中对数据进行格式化更加便捷。
# fmt:formatDate
fmt:formatDate
标签用于格式化日期时间。
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd" />
在上面的示例中,${now}
是一个表示当前日期时间的表达式,pattern
属性指定了日期时间的格式。
# fmt:parseDate
fmt:parseDate
标签用于解析日期时间字符串为日期对象。
<fmt:parseDate value="2021-05-14" var="date" pattern="yyyy-MM-dd" />
<p>Parsed Date: ${date}</p>
在上面的示例中,value
属性是要解析的日期时间字符串,var
属性指定了保存解析结果的变量,pattern
属性指定了日期时间的格式。
# fmt:formatNumber
fmt:formatNumber
标签用于格式化数字。
<fmt:formatNumber value="${price}" type="currency" currencyCode="USD" />
在上面的示例中,${price}
是一个表示数字的表达式,type
属性指定了格式化类型(如currency、percent、number等),currencyCode
属性指定了货币代码。
# fmt:parseNumber
fmt:parseNumber
标签用于解析数字字符串为数字对象。
<fmt:parseNumber value="123.45" var="number" />
<p>Parsed Number: ${number}</p>
在上面的示例中,value
属性是要解析的数字字符串,var
属性指定了保存解析结果的变量。
# fmt:message
fmt:message
标签用于获取国际化资源文件中的文本。
<fmt:setBundle basename="messages" />
<fmt:message key="welcome.message" />
在上面的示例中,basename
属性指定了国际化资源文件的基础名称,key
属性指定了要获取的文本对应的键。
# 函数标签库(fn)
需要在页面头部引入:
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
配合EL来使用:
<c:set var="name" value="John" />
<p>Name length: ${fn:length(name)}</p>
这些函数大部分都用来处理字符串,它们包括:
fn:length(string)
:返回字符串的长度。fn:contains(string, substring)
:检查字符串是否包含指定的子串。fn:startsWith(string, prefix)
:检查字符串是否以指定的前缀开始。fn:endsWith(string, suffix)
:检查字符串是否以指定的后缀结束。fn:substring(string, beginIndex [, endIndex])
:返回字符串的子串。可选参数endIndex
表示子串的结束索引。fn:indexOf(string, substring)
:返回子串在字符串中第一次出现的位置。fn:replace(string, target, replacement)
:替换字符串中的子串。fn:toUpperCase(string)
:将字符串转换为大写。fn:toLowerCase(string)
:将字符串转换为小写。fn:trim(string)
:去除字符串的前导和尾部空格。fn:split(string, delimiter)
:将字符串按照指定的分隔符分割为字符串数组。fn:join(array, delimiter)
:将字符串数组连接为单个字符串,并使用指定的分隔符。
# 自定义标签
JSP自定义标签(Custom Tag)是一种用于扩展JSP功能的技术。它允许开发人员创建自定义的标签,以便在JSP页面中重复使用特定的功能或逻辑。自定义标签可以简化页面的逻辑和代码,并提高代码的可重用性和可维护性。
# 传统自定义
JSP自定义标签有两种类型:标签处理器(Tag Handler)和标签文件(Tag File)。标签处理器是通过Java类实现的,而标签文件则是直接在JSP中定义的。
以下是创建JSP自定义标签的步骤和示例:
- 创建标签处理器类 首先,创建一个Java类来实现自定义标签的处理逻辑。这个类需要继承自javax.servlet.jsp.tagext.TagSupport类,并覆盖相应的方法来处理标签的开始、结束等操作。
package com.example.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class GreetingTag extends TagSupport {
@Override
public int doStartTag() throws JspException {
try {
pageContext.getOut().println("Hello, World!"); // 输出标签内容
} catch (Exception e) {
throw new JspException(e.getMessage());
}
return SKIP_BODY; // 跳过标签体的处理
}
}
- 创建标签库描述符(TLD) 创建一个XML文件,命名为Tag Library Descriptor(TLD)文件,用于描述自定义标签库的信息和标签的使用方式。
例如,创建名为c.tld
的TLD文件:
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>greeting</short-name>
<uri>http://example.com/tags/greeting</uri>
<tag>
<name>greeting</name>
<tag-class>com.example.tags.GreetingTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
在TLD文件中,定义了标签库的信息和一个名为greeting
的自定义标签。其中,tag-class
指定了标签处理器类的完全限定名,body-content
定义了标签的内容类型(例如:空标签体)。
tld需要文件放在WEB-INF下。
也可以放到WEB-INF的子文件夹下,不过需要在web.xml中配置:
<jsp-config>
<taglib>
<taglib-uri>http://example.com/tags/greeting</taglib-uri>
<taglib-location>/WEB-INF/tags/greeting.tld</taglib-location>
</taglib>
</jsp-config>
- 在JSP页面中引入自定义标签库
在要使用自定义标签的JSP页面中,引入自定义标签库。可以使用
<%@ taglib %>
指令来引入自定义标签库,指定URI和TLD文件的位置。
例如,在JSP页面的顶部添加以下指令:
<%@ taglib uri="http://example.com/tags/greeting" prefix="greeting" %>
- 使用自定义标签 在JSP页面中可以使用自定义标签。使用自定义标签时,使用指定的前缀和标签名称调用标签。
以下是一个示例,展示了如何在JSP页面中使用自定义标签:
<html>
<head>
<title>Custom Tag Example</title>
</head>
<body>
<h1>Greeting Example</h1>
<greeting:greeting/> <!-- 调用自定义标签 -->
</body>
</html>
在上述示例中,通过<greeting:greeting/>
调用了名为greeting
的自定义标签。
# tag文件自定义
非常抱歉,我之前理解错了您的意思。确实,在JSP 2.1版本中,您可以使用基于文件的自定义标签(File-based Custom Tag)来定义标签文件并将其放置在WEB-INF/tags
目录下。这种方式更加直观和方便。
以下是使用JSP 2.1的基于文件的自定义标签的步骤:
- 创建标签文件
在
WEB-INF/tags
目录下创建一个标签文件,它是一个以.tag
为扩展名的JSP文件。该文件中定义了自定义标签的处理逻辑和输出内容。
例如,创建名为greeting.tag
的标签文件:
<%@ tag body-content="empty" %>
<%@ attribute name="name" required="true" type="java.lang.String" %>
Hello, ${name}!
在标签文件中,您可以编写JSP代码来定义标签的行为和输出内容。在上述示例中,使用<%@ tag body-content="empty" %>
指令来指定标签的内容类型(空标签体)。
- 在JSP页面中引用自定义标签
在需要使用自定义标签的JSP页面中,使用
<jsp:taglib>
指令引用自定义标签库,并指定标签的URI和标签文件的位置。
例如,在JSP页面的顶部添加以下指令:
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="greeting" tagdir="/WEB-INF/tags" %>
在上述示例中,<%@ taglib prefix="greeting" tagdir="/WEB-INF/tags" %>
指令定义了标签库的前缀(greeting
)和标签文件所在的目录(/WEB-INF/tags
)。
- 使用自定义标签 在JSP页面中可以使用自定义标签。使用指定的前缀和标签名称调用标签。
以下是一个示例,展示了如何在JSP页面中使用基于文件的自定义标签:
<html>
<head>
<title>File-based Custom Tag Example</title>
</head>
<body>
<h1>Greeting Example</h1>
<greeting:greeting name="John" /> <!-- 调用自定义标签 -->
</body>
</html>
在上述示例中,通过<greeting:greeting/>
调用了名为greeting
的自定义标签。
使用基于文件的自定义标签可以更轻松地创建和维护自定义标签,并将其集中存放在WEB-INF/tags
目录下。这样的方式使得标签的定义更加模块化和可重用,提高了代码的可读性和可维护性。
请注意,基于文件的自定义标签需要JSP容器支持JSP 2.1或更高版本。
# 11 在Servlet中使用JSP页面
将JSP页面与Servlet结合使用是构建Java Web应用程序的常见做法。
下面是一些步骤,帮助您将JSP页面与Servlet进行结合:
创建一个Servlet类:在Java Web项目中创建一个继承自
HttpServlet
的Servlet类。这个类将处理与JSP页面相关的业务逻辑和请求响应。在Servlet中处理请求:在Servlet类的
doGet
或doPost
方法中编写处理请求的代码。您可以从请求对象中获取参数、执行必要的逻辑操作,然后将结果传递给JSP页面进行渲染。protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 处理请求逻辑 String message = "Hello, World!"; // 将数据设置为请求属性 request.setAttribute("message", message); // 转发到JSP页面 RequestDispatcher dispatcher = request.getRequestDispatcher("result.jsp"); dispatcher.forward(request, response); }
创建JSP页面:在Web应用程序的Web内容目录(WEB-INF)下创建一个JSP页面,用于显示Servlet处理的结果。在JSP页面中,您可以使用Java代码、JSTL(JSP标准标签库)和EL(表达式语言)来访问和显示来自Servlet的数据。
<html> <body> <h1>Result Page</h1> <p>${message}</p> </body> </html>
在部署描述符中配置Servlet和URL映射:打开
web.xml
文件,并添加Servlet的配置和URL映射,以便将请求发送到相应的Servlet。<servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>com.example.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/myservlet</url-pattern> </servlet-mapping>
访问JSP页面:启动您的Web服务器(如Tomcat),然后通过浏览器访问与Servlet映射的URL,例如
http://localhost:8080/yourapp/myservlet
。服务器将调用Servlet处理请求,并将结果转发到JSP页面进行渲染,最终在浏览器中显示。
# Spring MVC中的DispatcherServlet
Spring被广泛应用于Java企业级应用程序的开发,也是最主流的框架之一。
Spring MVC是一种基于MVC(模型-视图-控制器)设计模式的Web框架,用于构建灵活、可扩展和高性能的Java Web应用程序。
而Spring MVC框架本身就是基于Servlet构建的,这里我们介绍一下Spring MVC的核心组件:DispatcherServlet,它负责接收并分发所有的客户端请求。
主要看一下它是如何扩展了HttpServlet,并实现了自己的MVC模型的。
# 1 DispatcherServlet的作用
DispatcherServlet充当了应用程序的前端控制器(Front Controller)角色。它负责接收所有的客户端请求,并将请求分发给适当的处理程序(Handler)进行处理。
DispatcherServlet的主要功能包括:
- 处理请求分发:根据请求的URL,DispatcherServlet将请求分发给适当的处理程序(Controller)进行处理。
- 请求参数解析:DispatcherServlet负责解析请求参数,并将它们传递给处理程序进行处理。
- 视图解析和渲染:DispatcherServlet负责解析处理程序返回的视图名称,并将其转换为实际的视图对象,然后渲染视图并生成响应。
- 异常处理:DispatcherServlet处理处理程序中抛出的异常,并根据配置的异常处理策略选择适当的错误页面或错误处理逻辑。
- 拦截器支持:DispatcherServlet支持配置拦截器,可以在请求处理之前或之后执行预处理或后处理操作。
- 国际化支持:DispatcherServlet提供了对国际化和本地化的支持,可以根据请求的区域设置选择适当的消息资源进行国际化处理。
# 2 DispatcherServlet的配置
要在Spring MVC应用程序中使用DispatcherServlet,需要在web.xml
文件中进行配置。以下是一个示例配置:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在上述配置中,我们将DispatcherServlet配置为名为"dispatcher"的Servlet,并将URL模式设置为根路径"/",这意味着它将处理应用程序的所有请求。
contextConfigLocation
参数指定了DispatcherServlet的配置文件路径。在该配置文件中,我们可以定义处理程序映射、视图解析器、拦截器等。
# 3 DispatcherServlet的MVC模型
DispatcherServlet基于Spring MVC框架的设计模式实现了MVC(Model-View-Controller)模型。在该模型中:
- Model(模型):表示应用程序的业务数据和逻辑。它可以是一个简单的Java对象(POJO)或由Spring框架提供的数据访问对象(DAO)。
- View(视图):负责渲染数据并生成响应的部分。它通常是JSP、Thymeleaf或其他模板引擎。
- Controller(控制器):处理用户请求并决定使用哪个视图来渲染响应。它负责接收请求参数、调用适当的业务逻辑处理、准备模型数据,并选择适当的视图进行渲染。
DispatcherServlet通过以下方式实现MVC模型:
- 接收请求:DispatcherServlet接收客户端的HTTP请求。
- 处理器映射:DispatcherServlet使用处理器映射(Handler Mapping)将请求映射到适当的处理程序(Controller)。
- 处理器适配器:DispatcherServlet使用处理器适配器(Handler Adapter)执行处理程序中的业务逻辑,并将结果返回给DispatcherServlet。
- 模型和视图解析:DispatcherServlet使用模型和视图解析器(Model and View Resolver)解析处理程序返回的模型和视图,并准备渲染响应。
- 视图渲染:DispatcherServlet调用视图对象渲染模型数据,并生成响应。
- 响应发送:DispatcherServlet将生成的响应发送回客户端。
通过这种方式,DispatcherServlet将请求的处理过程与视图渲染过程解耦,实现了MVC模型的分离关注点和可扩展性。
更多内容请参考:Spring Web MVC (opens new window)
# 拾遗
从Java EE 8之后,Java 企业级应用的品牌名就改为了Jakarta EE,这会对Servlet涉及到的所有包名造成影响。
比如,Tomcat 9及之前的HttpServletRequest
包名是javax.servlet.http
,而Tomcat 10中包名变为了jakarta.servlet.http
。
上面讲了一些基础知识,不过没有给出项目的实战项目,可以参考以下示例:
# 总结
本指南涵盖了Servlet和JSP的基础知识和高级技术,以及Web应用程序的部署和配置。
我们深入讨论了Servlet生命周期、请求和响应处理、JSP标签库和EL表达式等内容。我们还探讨了Servlet和JSP的高级功能,如过滤器、监听器、文件上传和下载等。
希望本指南能够为您提供有关Servlet和JSP的全面指导,并帮助您构建强大和高效的Java Web应用程序。
祝你变得更强!