理解 Servlet 和 JSP
最初接触 Java 的时候用它来做一些桌面程序,所以对 Java Web 方向没有深入研究过。后来接触了 SSM 和 Spring Boot 等框架之后渐渐转向了 Web 方向。不过,对于 Servlet 和 JSP,虽然经常在招聘信息和别人推荐的 Java 学习路线上面看到,对它们的理解一直处于模糊的状态。最近,专门找了相关的资料进行学习,花了两三个小时就大致看完了它们在 Java Web 中的地位和原理,特写此文记录一些理解。
1、Servlet 和 JSP 的重要性
首先,我们日常 Web 开发中很少接触 Servlet 和 JSP 是因为:
- 对于 Servlet,我们一直在使用。之所以没有印象是因为它们被封装进了框架中,比如 Spring MVC. 尽管我们使用 Spring MVC,还是要在 web.xml 中配置 Servlet 的信息。这就是 Servlet 配置,而 Servlet 的实现则交给了 Spring MVC 处理。
- 对于 JSP,如果我们做的是一些简单的页面,那么我们可以直接使用 Spring MVC 和 JSP 进行编写。这在一些项目中仍然在使用。但对于一些比较大型的网站,因为前后端分离,所以 Java Web 则更多地负责给前端提供 RESTFul 类型的接口。因此,我们接触和使用 JSP 的机会变少了而已。
尤其是 Spring Boot 出来之后,甚至 Tomcat 都被作为 Spring Boot 整体的一部分。我们接触 Servlet 和 JSP 的基础越来越少,那么我们还有必要学习它们吗?
答案是 YES. 这是因为 Spring MVC 也好,Spring Boot 也罢,本质上只是对 Servlet 进行了封装,简化了开发流程,使我们能够把更多的精力放在业务逻辑实现上。但如果想要深入研究和理解这些框架的原理,就无法绕开 Servlet 和 JSP 了。
2、Servlet、JSP 以及 Tomcat 之间的关系
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,用来接收请求并对请求进行处理,并返回响应结果。Servlet 可以处理任何类型的请求,不过多数情况下,我们用它来处理来自浏览器的 Http 请求。我们可以在 Servlet 根据请求信息,将一个 HTML 作为文本响应给客户端,并最终呈现一个页面给用户。
而 JSP(全称 Java Server Pages)是一种动态网页开发技术,以 Java 作为脚本语言,文件后缀名为 *.jsp
。从形式上面看 JSP 包含了 Java 和 HTML 代码。当请求达到服务器的时候,当服务器识别出该请求是 JSP 请求之后,会将该请求传递给 JSP 引擎。JSP 引擎从磁盘中载入 JSP 文件,然后将它们转化为 Servlet(Java 代码)。然后就得到了 JSP 对应的 Servlet,之后就将其作为 Servlet 使用,接收请求,处理,并返回结果。
所以,我们可以理解成 JSP 是 Servlet 的一种简化使用方式。因为在 Servlet 中,你不得不以文字的形式拼接出一个 HTML 字符串并返回。而使用 JSP,把责任反了过来,在 HTML 中嵌入 Java 代码,然后再把整个文件翻译成 Servlet 代码。
那么 Tomcat 呢?Tomcat 是 Servlet 容器,除了 Tomcat,还有 Jetty 可以作为 Servlet 容器。本质上,这类的容器做了一些工作,来提升我们 Servlet 程序的效率,比如对多线程进行管理等等。所以,从这个角度上将,我们的 Servlet 更像是一个接口,我们定义了要实现的逻辑,然后它们被放进 Tomcat 中执行。(Tomcat 本身也就是启了一个 main 函数和一个无限循环来不断对请求进行处理,所以,我们的 Servlet 对 Tomcat 而言只是一段代码。)
3、Servlet 的使用和规范
3.1 Servlet 的基础用例
Servlet 可以使用 javax.servlet 和 javax.servlet.http 包创建。Servlet 有三个生命周期方法它们会在各自的生命周期中被调用:
init()
方法进行初始化。service()
方法来处理客户端的请求。destroy()
方法终止。
service() 方法中会对请求进行处理。以 HttpServlet 为例,它会在 service() 方法中,根据请求的 Method 信息调用 doGet()
、doPost()
和 doPut()
等方法。以下面的代码为例,
public class HelloWorld extends HttpServlet {
private String message;
@Override
public void init() throws ServletException {
// 执行必需的初始化
message = "Hello World";
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应内容类型
response.setContentType("text/html");
// 实际的逻辑是在这里
PrintWriter out = response.getWriter();
out.println("<h1>" + message + "</h1>");
}
@Override
public void destroy() {
// 什么也不做
}
}
这里用来处理 GET 类型的请求,并通过 HttpServletResponse 中获取的输出流将最终的 HTML 返回给客户端。这里的处理比较简单,如果需要获取其他的信息,比如 Cookie、Session 等信息都可以从 HttpServletRequest 中进行获取。如果想要将 Cookie、Session 等返回给客户端,也是通过 HttpServletResponse 进行输出。如果是 POST 类型提交的表单,我们也是使用 HttpServletRequest 和 HttpServletResponse 进行读写的。它们提供了哪些方法,以及如何进行读写我们不再进行详细说明,参考相关的 API 或者示例代码即可。(可以参考:Servlet-教程)。
按照上面这样写完了 Servlet 之后,我们还需要在 Tomcat 中配置才行。默认情况下,Servlet 应用程序位于路径 Tomcat 安装目录/webapps/ROOT
下,且类文件放在 Tomcat 安装目录/webapps/ROOT/WEB-INF/classes
中。如果您有一个完全合格的类名称 com.myorg.MyServlet
,那么这个 Servlet 类必须位于 WEB-INF/classes/com/myorg/MyServlet.class
中。
现在,让我们把 HelloWorld.class 复制到 Tomcat 安装目录/webapps/ROOT/WEB-INF/classes
中,并在位于 Tomcat 安装目录/webapps/ROOT/WEB-INF/
的 web.xml 文件中创建以下条目:
<web-app>
<servlet>
<servlet-name>HelloWorld</servlet-name>
<servlet-class>HelloWorld</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorld</servlet-name>
<url-pattern>/HelloWorld</url-pattern>
</servlet-mapping>
</web-app>
这里的 <servlet>
标签配置了 Servlet 的类,<servlet-mapping>
标签中配置了给 Servlet 用来处理的 URL 的类型。
3.2 Servlet 过滤器
配置在 Web.xml 中的内容,除了 Servlet 还有 Servlet 过滤器。过滤器用来动态地拦截请求和响应,处理,并交给 Servlet 执行。可以将一个或多个 Servlet 过滤器附加到一个 Servlet 或一组 Servlet。可以被用来进行字符串编码、身份验证过滤器、数据压缩、加密、触发资源访问事件、图像转换过滤器等。
过滤器是需要用到 javax.servlet.Filter 接口,它也提供了三个生命周期相关的方法:
init()
初始化过滤器的时候调用doFilter()
该方法完成实际的过滤操作destroy()
在销毁过滤器实例前调用该方法
在使用的时候,我们只需要覆写这三个方法,然后将其注册到 web.xml 中即可。比如,
// 实现 Filter 类
public class LogFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
// 获取初始化参数
String site = config.getInitParameter("Site");
// 输出初始化参数
System.out.println("网站名称: " + site);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 输出站点名称
System.out.println("站点网址:http://www.runoob.com");
// 把请求传回过滤链
chain.doFilter(request,response);
}
public void destroy() {
/* 在 Filter 实例被 Web 容器从服务移除之前调用 */
}
}
然后,将它注册到 web.xml 中,
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<filter>
<filter-name>LogFilter</filter-name>
<filter-class>com.runoob.test.LogFilter</filter-class>
<init-param>
<param-name>Site</param-name>
<param-value>菜鸟教程</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LogFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<!-- 类名 -->
<servlet-name>DisplayHeader</servlet-name>
<!-- 所在的包 -->
<servlet-class>com.runoob.test.DisplayHeader</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DisplayHeader</servlet-name>
<!-- 访问的网址 -->
<url-pattern>/TomcatTest/DisplayHeader</url-pattern>
</servlet-mapping>
</web-app>
从上面的配置文件中可以看出,配置过滤器的方式和配置 Servlet 一样,都是先通过标签定义本身,然后再定义一个 mapping 并指定要匹配的 url. 注意:web.xml 中的 filter-mapping 元素的顺序决定了 Web 容器应用过滤器到 Servlet 的顺序。
4、JSP
4.1 JSP 的生命周期
JSP 生命周期中所走过的几个阶段:
- 编译阶段:Servlet 容器编译 Servlet 源文件,生成 Servlet 类;
- 初始化阶段:加载与 JSP 对应的 Servlet 类,创建其实例,并调用它的初始化方法,可以通过覆写
jspInit()
实现该阶段的逻辑; - 执行阶段:调用与 JSP 对应的 Servlet 实例的服务方法;
- 销毁阶段:调用与 JSP 对应的 Servlet 实例的销毁方法,然后销毁 Servlet 实例,可以通过覆写
jspDestroy()
实现该阶段的逻辑。
以下是 JSP 的一个示例,它的作用的原理就是,将 HTML 部分变成字符串,然后 <%! %>
包裹的 Java 代码转换成 Servlet 的代码。最终执行 Java 代码之后将完整的字符串返回给客户端,
<!-- 解决中文编码问题 -->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<title>life.jsp</title>
</head>
<body>
<%!
private int initVar=0;
private int serviceVar=0;
private int destroyVar=0;
%>
<%!
public void jspInit(){
initVar++;
System.out.println("jspInit(): JSP被初始化了"+initVar+"次");
}
public void jspDestroy(){
destroyVar++;
System.out.println("jspDestroy(): JSP被销毁了"+destroyVar+"次");
}
%>
<%
serviceVar++;
System.out.println("_jspService(): JSP共响应了"+serviceVar+"次请求");
String content1="初始化次数 : "+initVar;
String content2="响应客户请求次数 : "+serviceVar;
String content3="销毁次数 : "+destroyVar;
%>
<h1>菜鸟教程 JSP 测试实例</h1>
<p><%=content1 %></p>
<p><%=content2 %></p>
<p><%=content3 %></p>
</body>
</html>
4.2 JSP 的语法
JSP 的脚本程序:可以包含任意量的Java语句、变量、方法或表达式,语法格式是:
<% 代码片段 %>
JSP 声明:可以声明一个或多个变量、方法,供后面的 Java 代码使用。语法格式:
<%! declaration; [ declaration; ]+ ... %>
JSP 表达式:包含的脚本语言表达式,先被转化成 String,然后插入到表达式出现的地方。语法格式:
<%= 表达式 %>
JSP 注释:常用的主要有两种:
<%-- 注释 --%>
JSP注释,注释内容不会被发送至浏览器甚至不会被编译<!-- 注释 -->
HTML注释,通过浏览器查看网页源代码时可以看见注释内容
JSP 指令:用来设置与整个JSP页面相关的属性。语法格式:
<%@ directive attribute="value" %>
这里有三种指令标签:
指令 | 描述 |
---|---|
<%@ page ... %> | 定义页面的依赖属性,比如脚本语言、error 页面、缓存需求等等 |
<%@ include ... %> | 包含其他文件 |
<%@ taglib ... %> | 引入标签库的定义,可以是自定义标签 |
JSP 行为:使用 XML 语法结构来控制 Servlet 引擎。语法格式:
<jsp:action_name attribute="value" />
一些可用的 JSP 行为标签:
JSP 隐含对象:JSP 支持九个自动定义的变量,江湖人称隐含对象。这九个隐含对象的简介见下表:
其他:JSP 的控制语句、运算符号和变量类型与 Java 一致。