作出网页后端的核心目标就是 , 基于 tomcat 编程进行网站后端的开发 , 肯定需要对 http 协议进行一系列操作 , 幸运的是 tomcat 已经把这些 http 相关的底层操作封装好了(监听端口 , 接收连接 , 读取请求 , 解析请求 , 构造请求对象等一系列操作) , 只需调用 tomcat 为我们提供的 api 即可. 而 Servlet 就是 Tomcat 给 java 提供的进行 web 开发的原生 api. 当然 Servlet 在企业中已经很少见了 , 但类似于Spring MVC 这样高效的 web 开发 api , 也是基于 Servlet 进行封装的 , 技术是层出不穷的 , 我们只有掌握最基础的框架 , 才能以不变应万变.
Servlet 是什么?
Servlet 是一种实现动态页面的技术. 是一组 Tomcta 提供给程序员的 API , 帮助程序员简单高效开发的一个 web api.
静态页面 vs 动态页面
- 静态页面: 就是内容始终固定的页面 , 即使 用户不同/时间不同/输入参数不同 , 页面内容也不会发生变化.
- 动态页面: 用户不同/时间不同/输入参数不同 , 页面内容会发生变化.
举个例子:
Tomcat的主页就是 静态页面
B站的主页就是动态页面
构建动态页面的技术有很多 , 每种语言都会有一些相关的库/框架来做这件事
Servlet 就是 Tomcat 这个 HTTP 服务器提供给 Java 的一组 API , 来完成构建动态页面这个任务 , 而不必关注 Socket , Http 协议格式 , 多线程并发等技术细节 , 降低了 web app 开发门槛 , 提高了开发效率.
Servlet 主要的工作
- 允许程序员注册一个类 , 在 Tomcat 收到某个特定的 HTTP 请求的时候 , 执行这个类中的一些代码.
- 帮助程序员解析 HTTP 请求 , 把 HTTP 请求从一个字符串解析成一个 HttpRequest对象
- 帮助程序员构造 Http 响应 , 程序员只要给指定的 HttpResponse 对象填写一些属性字段 , Servlet 就会自动的按照 Http 协议的方式构造一个 Http 响应字符串 , 并通过 Socket 写回到客户端.
第一个 Servlet 程序
首先第一步 , 预期写个 servlet 程序 , 部署到 tomcat 上 , 通过服务器访问 , 得到 hello world 字符串. 该 hello world 堪称史上最难 hello world
一共七步 , 除了编写 hello world 代码之外 , 其余步骤都是固定的.
- 创建项目
- 引入依赖
- 创建目录结构
- 编写代码
- 打包程序
- 部署程序
- 验证
1.创建项目
此处我们要创建一个 maven 项目 , maven 是个"工程管理"工具.
主要作用是:
- 规范目录结构
- 管理依赖(使用了什么第三方库 , 都处理好)
- 构建
- 打包
- 测试
- …
按照上图选择即可~~
如果首次使用 maven , 项目创建好后 , 会从中央仓库加载一些 maven 的依赖 , 需要联网且耐心等待 , 只有第一次使用会这样 , 后续创建就方便了.
maven 虽然是个独立的项目 , 但是不需要单独下载 , 因为 IDEA 已经自带了.
注意创建好的项目目录:
2.引入依赖
servlet 的 jar 包依赖
[Maven仓库](Maven Repository: Search/Browse/Explore (mvnrepository.com))
搜索 Servlet
对应与Tomcat 8 的版本号 , 我们需要下载 3.1.0
只需下面这段代码粘贴到pom.xml中.
Tips: 这里不是随便粘贴 , 而是创建一个子标签 , 粘贴到该子标签中.
如果首次使用依赖可能是红色(红色说明没有下载完) , 一般只要粘贴进来 , idea 的 maven 就会自动去maven仓库下载 , 这段配置就相当于该依赖在 maven 仓库的坐标.
3. 创建目录
- 在 main 目录下(和java , resources 并列) , 创建一个 webapp 目录.
- 在 webapp 下创建 WEB-INF 目录
- 在WEB-INF 目录下创建一个 web.xml 文件
Tips: 此处创建的目录和结构 , 一点都不能错.
给 web.xml 写点代码
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
以上代码无需背 , 当前写的 servlet 程序和以往写的代码相比有一个非常大的区别 , 那就是没有 main 方法 , main 方法可以视为是汽车的发动机 , 有发动机才能跑. 但目前车辆没有发动机 , 为了让汽车跑起来 , 可以挂个车头 , 让车头拽着跑即可. 我们写的 servlet 程序就是车厢 , tomcat就是车头 , 我们把写好的 servlet 程序放在 webapps 目录下就相当于把车厢挂到车头之后 , WEB-INF/web.xml 就是告诉 tomcat 哪些车厢(文件)是需要拉的.
4.编写代码
在 java目录底下创建一个.java文件用于编写代码
servlet api 里提供的现成的类 , 写 servlet 代码需要继承 HttpServlet , 这里继承的主要目的 , 是为了通过方法重写的方式 , 针对 HttpServlet 进行功能的扩展.
public class HelloServlet extends HttpServlet
重写 doGet 方法 , 父类中已有 doGet , 此处希望用子类版本替代父类的.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);这段代码一定要去掉,父类只会返回一个错误页面
}
我们写的 doGet 方法 , 无需我们手动调用 , 而是交给 Tomcat 来调用 , Tomcat 收到 get 请求 , 就会触发 doGet 方法. Tomcat 会构造好两个参数 , req(请求) 和 resp(响应).
- req: TCP Socket 中读出来的字符串 , 按照 HTTP 协议解析到的对象 , 该对象里的属性和HTTP请求报文格式相对应.
- resp: 是一个空的对象(不是 null , 只是 new 了个对象 , 里面的各种属性没设置) , 程序员需要在 doGet 中根据 req , 结合自己的业务逻辑构造出一个 resp 对象. doGet 做的工作就是根据请求计算响应 , 其中 resp 这个参数 , 本质上是一个"输出型参数". 在方法中将参数构造好之后 , 由调用者把结果写到 TCP Socket 里面传回到浏览器中.
编写代码:
// 这个仅仅是在服务器的控制台打印
System.out.println("hello world");
// 要想把 hello world 返回到客户端 , 需要以下代码
// getWriter 会得到一个 writer 对象
resp.getWriter().write("hello world");
此处的 Writer 对象从属与 resp 对象 , write 操作其实是往 resp 的 body 部分进行写入 , 等 resp 对象整个构造好了 , tomcat 会统一的转成 http 的响应个数 , 再写进 socket.
最后注意在类的最外部加上注解
@WebServlet("/hello")
注解的作用是 , 针对一个类/方法 , 进行额外"解释的说明" , 赋予这个类/方法额外的功能/含义.
此处这个 @WebServlet 注解 , 作用是把当前类和一个 http 请求路径关联起来.
doGet 是 Tomcat 收到 Get 请求的时候就会调用 , 具体要不要调用 doGet , 还得看当前 Get 请求的路径是啥 , 不同的路径可以触发不同的代码(关联到不同类上) , 一个Servlet 程序中 , 可以有很多的 Servlet 类 , 每个 Servlet 类都可以关联到不同的路径(对应到不同的资源) , 因此多个 Servlet 就可以实现不同的功能.
例如: 我去餐馆吃鸡肉卷
我发起请求: 老板来个 . 鸡肉卷~~http://ip:port/鸡肉卷?葱=多放
老板收到请求后 , 就会按照"制作鸡肉卷"的流程来做饭(根据请求计算响应)
如果我还有两个请求:
5.打包程序
程序编译好之后(得到一些 .class 文件) , 把这些 .class 文件打成压缩包
jar 就是一种 .class 构成的压缩包 , 但此处要打的是 war 包 , jar 只是一个普通的 java 程序 , war 则是tomcat 专属的用来描述 webapp 的程序 , 一个 war 就是一个 webapp.
借助 maven 点击 package 即可
此时打的包只是 jar 包 , 要想部署到 tomcat 上需要打成 war 包.
<packaging>war</packaging>
打 war 包还需添加插件否则会报错 , 打 jar 包无需添加 , finalName 则是给 war 包重命名.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
</plugin>
</plugins>
<finalName>hello_servlet</finalName>
</build>
打包成功后 target 目录下会多一个 war 包.
IDE 明明可以直接运行 , 我们为什么还要这么麻烦的打包?
因为 IDE 直接运行是在本地 , 工作时开发环境(自己写代码的电脑)和运行环境(另一个服务器)很可能不是同一个环境.
6.部署
把打包好的 war 拷贝到 tomcat 的 webapps 目录中即可 , 然后启动 tomcat.
7.验证
打开浏览器, 输入 url , 访问写好的代码.
Tips: 路径名一定要正确.
小结:
浏览器地址栏中国输入 URL , 浏览器就构造了一个对应的 http Get请求 , 发送给 tomcat , tomcat 就会根据第一级路径 , 确定具体的 webapp. 根据第二级路径, 确定是调用了哪个类 , 最后通过 Get/Post 方法确定调用 HelloServlet 的哪个方法(doGet, doPost…)
简而言之:
Http 服务器 , 也是基于 TCP 的服务器 , 创建 TCP server socket 绑定端口 , accept 接收请求 , 收到请求之后 , 从 socket 读取请求 , 把收到的请求构造成 HttpServletRquest 对象 , 再根据请求里的 URL 两级路径 , 确定一个 Servlet 类.(Http 服务器里提前构造好的一个 hash 保存路径和类) , 创建对应 Servlet 实例 , 根据 http 方法决定 Servlet 类的方法 , 执行完方法之后得到 HttpServletResponse 对象 , 按照 http 协议构造 HTTP 响应(得到字符串) , 写回到 socket 即可.
Tomcat 插件高效部署:
上述操作部分过程可以简化 , 例如: 创建项目有项目模板 , 打包部署有固定插件.
因此 IDEA 提供了一些 API , 可以让程序员开发插件 , 对 IDEA 的现有功能进行扩展.
1.打包部署项目的常见插件有 Smart Tomcat(更简单好用)
2.下载好插件之后 , 首次使用 SmartTomcat 需要配置 , 点击 Edit Configuration
3. 添加 add SmartTomcat
5.配置一下 Tomcat 的 context path 路径 , 其余的都不用管.
Tips: context path 开头不要大写字母 , 因为浏览器会把context path 默认转成小写 , 导致无法正确访问页面.
Smart Tomcat 加载单个 webapp 运行 , 与本地的 tomcat 不在一起 , idea 调用 tomcat , 会让 tomcat 加载当前项目的目录 , context path 默认是项目名称 , 如果项目名称开头大写可以修改一下. 这个过程没有打 war 包 , 也没有拷贝和解压缩的过程 , 但是像 webapps 下的一些内容(访问 127.0.0.1:8080 显示欢迎页面)就没有了.
6.运行 Tomcat 即可
7.如下图所示即运行成功:
Tips: 不要点击 idea 中的链接 , 一定 404 , 因为不包含 servlet path.
8.访问网页
常见错误:
出现 404:
- 路径错误
- webapp 未正确部署
- web.xml 写错了
- 少写了 Servlet Path
- Servlet Path 与 URL 不匹配
出现 405
Tips: 一定要注释掉父类的 super.doGet 方法 , 因为父类默认返回一个 405 的错误页面.
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
出现 500:
本质上就是代码报错 , 日志中会明确告诉你异常调用栈中哪一行代码出错.
出现"空白页面":
代码中没写 resp.getWriter.write()
出现"无法访问此网站"
tomcat 没启动 , 检查 tomcat 是不是启动好 , 查看目录中有啥异常 , 常见是端口冲突.