目录
1. 第一个 Servlet 程序:使用 Servlet 写 hello world
1.1 创建项目
1.2 引入依赖
1.3 创建目录
1.4 开始写代码
1.5 打包代码
1.6 部署
1.7 验证程序
2. 简化上述 Servlet 程序
3. Servlet 程序中常见问题
3.1 404
3.2 405
3.3 500
3.4 出现“空白页面”
3.5 出现“无法访问此网站”
4. Servlet API
4.1 init 方法
4.2 destroy
4.3 service
新技术层出不穷,但是基础的东西、底层的东西,是不会变的,Servlet 。虽然工作中不大会直接使用 Servlet,但是学习 Servlet ,有助于我们学习其他更加成熟,更加方便的框架,比如(Sring、MVC)。
Servlet 是一种实现动态页面的技术。
动态页面 与 静态页面
静态页面指的是内容始终固定的页面,即使用户不同/时间不同/输入的参数不同,页面内容也不会发生变化。(除非网站的开发人员修改源代码,否则页面内容始终不变)。
动态页面指的就是用户不同/时间不同/输入的参数不同,页面内容会发生变化。
静态网页,形如:
动态网页,类似于 B 站。
构建动态网页的技术有很多,每种语言都有一些相关的库/框架来做这件事。
Servlet 就是 Tomcat 这个 HTTP 服务器提供给 Java 的一组 API,来完成构建动态页面这个任务。
需要经历七个固定步骤。
在创建项目这一步里,需要用到 maven 这个构建工具。其功能是帮助我们构建、测试以及打包一个项目。
为什么需要 maven 呢?
作为新手程序员,已经习惯直接在 idea 里,点一下绿色的三角号来编译运行一个程序。这是因为这时候接触的程序比较简单。可是在公司的项目中,就会涉及很多个模块,这些模块存在依赖关系,比如运行先后的问题,甚至还可能依赖很多第三方库。因此就诞生了一系列的构造工具来解决上述问题。
Java 的构建工具有:Ant(老的构建工具)、Maven(当前比较主流的构建工具)以及 Gradle(更新的构建工具,目前主要是 Android 生态使用)。
创建一个 maven 项目:
打开一个 IDEA ,创建一个新项目:
首次使用 maven 项目,IDEA 会从互联网上加载很多的依赖,这需要一定的时间以及通畅的网络(maven 的服务器在国外)。
一个 maven 项目,首先得有一个 pom.xml 的配置文件:
jdk 里有很多内置的 api,比如 String、Thread、List/Map、Random、Scanner、InputStream/OutputSream。前面说了,Servlet 是 tomcat 提供的 api,但他不是标准库自带的,而是第三方库,需要额外下载安装 Servlet 的 jar 包。
去中央仓库搜索 Servlet :
选择哪个 servlet 版本下载呢?一定要与 tomacat 的版本相匹配才行。 3.1.0 与 tomcat 8.5 是相互匹配的。
除了可以直接点 jar下载之外,还可以借助 Maven 来下载。
将红框框住的代码复制粘贴,给 pom.xml 新增一个
只要把这一段代码往里一拷贝,IDEA 会自动调用 maven,从中央仓库下载该 jar 包。首次使用这里会标记成红色,一旦下载完成了,会变成白色。
打开 maven 面板,点击刷新按钮:
jar 包是被下载到本地的一个隐藏目录中了:
需要手动创建以下目录和文件:
这里的目录结构,目录位置,目录名字务必保证一字不错!
web.xml 是供 tomcat 使用的。tomcat 从 webapps 目录中加载 webapp,就是以 web.xml 为依据的。复制粘贴以下代码到 web.xml 中:
Archetype Created Web Application
IDEA 对于 java 代码的识别是很准确的,红了就是有错,但对于其他的文件,就不一定了。所以上述代码标红了,不用太担心。
HttpServlet 这个类来自于从中央仓库下载的 jar 包。
重写父类中的 doGet 方法。
HTTP 请求:Tomcat 收到请求,把这个请求按照 HTTP 协议的格式,解析成一个对象。
HTTP 响应: 此处响应的对象,是一个空的对象。需要咱们在 doGet 中,设置响应的一些数据(比如响应的 body、header 以及状态码)。只要把属性设置到这个 resp 对象中,Tomcat 就会自动根据响应对象构造一个 HTTP 响应字符串,通过 socket 返回给客户端。
这个方法不是程序员手动调用的,而是 Tomcat 在合适的时机,自动调用。
以下是 Tomcat 的代码:
HttpServlet servlet = new HelloServlet();
......
// Tomcat 收到一个 HTTP 请求后,解析成 HttpServletRequest 对象
HttpServletRequest req = ...... ; //通过一系列的代码解析,按照 HTTP 协议的格式解析字符 //串
HttpServletResponse resp = new HttpServletResponse(); //Tomcat 再创建一个空的属性,
//里面的属性还没有设置
servlet.doGet(req, resp); // Tomcat 调用执行 doGet 方法
// doGet 根据 req 对象,结合业务逻辑,把 resp 生成出来
可以看出,程序员写的 doGet 是其中的一个部分,大部分环节都是现成的。这种代码的编写方式,便是框架(framework)。所谓框架,就是已经把绝大部分功能都写好了,不能随便乱写,只能按照人家的规范要求,写其中的一小段逻辑,把这段逻辑插入到整体的框架中。
super.doGet(req, resp),调用的是父类的 doGet 方法。点进 doGet 的父类方法:
发现父类的 doGet 会报错,405。所以在写自己的 doGet 方式时,要把 super.doGet(req, resp) 给删掉。
//给 HelloServlet 写注释
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 这是在 服务器的控制台中,打印了字符串
System.out.println("hello world");
// 这是在 resp 的 body 写入 hello world 字符串,这个内容就会被
// HTTP 响应返回给浏览器,显示到浏览器页面上。
resp.getWriter().write("hello world");
//resp.getWriter 得到了 resp 内部特有的 Writer 对象,是一个字符流
}
}
注解就是:
@xxxx
通常用来修饰一个类或者一个方法
最早认识的注解是
@Override
对于一个服务器程序来说,基本工作流程:
1. 读取请求并解析
2. 根据请求计算响应
3. 把响应写回给客户端
上述工作流程跟 UDP 和 TCP 回显服务器工作流程一样。1、3 已经被 Tomcat 完成了,只需要关注 2。
上述程序,不能直接独立运行,而是必须放到 Tomcat 上运行,也就是部署。而部署的前提是先打包。
对于规模比较大的项目,里面会包含很多 .java 文件,进一步会产生很多的 .class ,所以把这些 .class 打成一个压缩包,再进行拷贝,比较好。
平时见到的压缩包,为 rar、zip等,在 java 中,使用的压缩包为 jar,war。普通的 Java 程序打包成 jar,而部署给 tomcat 的程序打包成 war。两者本质上没有区别,都是把一堆 .class 文件打包好。但 war 包是属于 tomcat 的专属格式,里面会有一些特定的目录结构和文件,比如 web.xml。后续 tomcat 要识别这些内容来加载 webapp。
打开 maven 控制面板,双击 package:
打包操作:
1. 检查代码中是否存在一些依赖,依赖是否安装好。(比如 servlet 依赖)
2. 把代码进行编译,生成一堆 .class 文件
3. 把这些 .class 文件以及 web.xml 按照一定的格式进行打包
当前打的包是 jar 包,而不是 war 包。为了打出来 war 包,需要调整 pom.xml 。重新打开 pom.xml。
在 <\project> 标签上,写一个
打出来的 war 包,起名为:
打好的 war 包,是一个普通的压缩包,可以使用压缩工具 (winrar) 打开。但并不需要手动解压缩,而是直接把整个 war 交给 tomcat ,tomcat 能自动解压缩。
把打好的 war 包,拷贝到 tomcat 的 webapps 目录中。
之后启动 Tomcat:
这个日志看着虽然乱码了,但是告诉我们,这个包已经部署成功了。
不是 Tomcat 一启动,写的 doGet 就能执行。doGet 遇到 GET 请求,就会执行该方法。但也不是随便收到一个 GET 请求,就能执行 doGet,前提是,请求的 URL 的路径要匹配。
地址栏上的路径分为两级:
1. hello_servlet,称为 Context Path / Application Path,标识了一个 webapp,也就是这个 webapp 的目录名 / war 包名。(一个 Tomcat 上可以有多个 webapp,以此来表明用哪一个)
2. hello,称为 Servlet Path,标识当前请求要调用哪个 Servlet 类的 doGet。因为一个 webapp 中,可以有多个 Servlet ,自然就有多个 doGet 了。
重点简化 5 和 6,让 5 和 6 简化成一键式完成,需要借助 IDEA 的插件来完成这个工作。
首次使用 tomcat 需要配置一下:
如果我们的程序是拷贝 war 包到 webapps 中运行,此时 Context Path 是 war 包名字。如果我们的程序是使用 Smart Tomcat 运行,Context Path 是在上述配置中,手动设置的,默认是项目名字。
配置成功之后,IDEA 的右上角就出现了一个三角号按钮:
以后运行代码就可以直接点击绿色三角号了。打包部署一键完成!
此时,tomcat 的日志就会在 IDEA 中显示了,不会再单独弹出 cmd,这样乱码的问题就解决了。虽然满屏幕都是红色,但不一定是错误的。
当我们使用 Tomcat 运行之后,再在 IDEA 中运行,就出现以下问题:
这是因为,启动 Tomcat 需要绑定两个端口号,8080(业务端口),8005(管理端口)。而一个端口号只能被一个进程绑定。
所以当我们把黑框框的服务器关掉之后,再次去运行 IDEA 之后,就不会出现这个问题了。
在日志的最后一行,可以看到一行蓝色的url:
可以看到的是,这个路径只有 Context Path,而没有 Servlet Path!点开之后一定是 404!
smart tomcat 的运行方式和之前拷贝到 webapps 中,存在本质区别。在运行 Tomcat 时,通过特定参数:
来指定 Tomcat 加载某个特定目录中的 webapp。所以上述过程,既不会打包,也不会拷贝。这是开发和调试阶段使用的方式,而如果是部署到生产环境,还是得打包,拷贝的方式。
表示用户访问的资源不存在,大概率是 URL 的路径写的不正确。
1. 请求的路径写错了,Context Path 和 Servlet Path 写对了吗?
2. 路径写对了,但是 war 包没有被正确加载。比如 web.xml 写错了,就会导致 war 包不能被正确加载;或者 如果有两个 Servlet 的 Servlet Path 相同,也会导致不能正确加载。
1. 表示发送请求的方法,和代码不匹配,比如代码写的是 doPost,但你发的请求是 GET !
2. 虽然方法和代码匹配,但忘记干掉 super.doXXX 了
这是最常见,也是最好解决的。500 意味着你的服务器代码抛出异常了!
服务器没有返回任何数据。
一般是 Tomcat 启动失败了。
关掉 smart tomcat 之后,再刷新以下页面,就会出现以下错误:
Servlet API 有很多,重点掌握三个类即可!
1. HttpServlet
2. HttpServletRequest
3. HttpServletResponse
2 和 3 有啥属性,有啥方法,只要熟悉 HTTP 协议,就很清楚了。
写一个 Servlet 程序,都要继承这个 HttpServlet 类,也就是说,这个 HttpServlet 中都有啥方法:
方法名称 | 调用时机 |
init | 在 HttpServlet 实例化之后被调用一次 |
destroy | 在 HttpServlet 实例不再使用时调用一次 |
service | 收到 HTTP 请求时调用 |
doGet | 收到 GET 请求时调用(由 service 方法调用) |
doPost | 收到 POST 请求时调用(由 service 方法调用) |
doPut/doDelete/doOptions/...... | 收到其他请求时调用(由 service 方法调用) |
HttpServlet 被实例化之后,首次收到匹配请求时,会调用一次,使用这个方法来做一些初始化相关的工作。
//给 HelloServlet 写注释
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("执行 init");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 这是在 服务器的控制台中,打印了字符串
System.out.println("hello world");
// 这是在 resp 的 body 写入 hello world 字符串,这个内容就会被
// HTTP 响应返回给浏览器,显示到浏览器页面上。
resp.getWriter().write("hello world");
//resp.getWriter 得到了 resp 内部特有的 Writer 对象,是一个字符流
}
}
运行程序之后,控制台上并没有执行 init 方法。
上述红框中的请求,会触发 HelloServlet 类的 doGet 的执行,但在首次执行 doGet 之前,会先调用 init。后续多次请求,也只会执行 doGet 。
该方法是 webapp 被卸载/被销毁之前执行一次,用来做一些收尾工作。但这里并不建议大家使用 destroy。
1. 如果通过 8005 管理端口,来停止服务器,此时 destroy 能执行。
2. 如果直接杀死进程的方式来停止服务器,此时 destroy 执行不了。
而上述两种停止服务器的方式,在绝大多数情况下,方式 2 更多,这就让 destroy 派不上用场。
Tomcat 启动会使用两个端口:
1. 8080 业务端口,类似于于工作微信
2. 8005 管理端口,类似于生活微信
每次收到路径匹配的请求,都会执行。doGet / doPost 其实是在 service 中调用。一般不会重写 service,只要重写 doXXX 就行了。
Servlet 的生命周期(面试题)
init 初始情况下调用一次
destroy 是结束之前调用一次
service 每次收到路径匹配的请求都调用一次