目录
一、Tomcat的下载与使用
1.1 认识Tomcat
我们知道http协议是HTTP客户端和HTTP服务器之间交互数据的格式,Java Socket可以用来构造HTTP客户端,同样HTTP服务器也可以使用Java Socket实现。
Tomcat就是一个基于java实现的一个开源免费,广泛应用的HTTP服务器。
1.2 安装Tomcat
在 Tomcat 官网下载即可. 这里我们使用 Tomcat 8(由于后续使用的servle是3.1 版本,因此这里使用tomcat8).
对其进行解压缩即可:解压缩的目录最好不要带中文,或者标点符号。
Ps:tomcat是基于java实现的,因此需要电脑安装上jdk才行,否则运行不了。
1.3 认识Tomcat的目录结构
- bin:存放各种启动/停止的脚本,.sh后缀的是在Linux上运行,.bat是在windows上运行,其中的startup.bat是启动服务(windows上的)
- conf:相关的配置文件
- lib:tomcat依赖的库。
- logs:运行时的日志文件,程序出问题时候可以通过这个来排查。
- temp:临时的文件夹,无需关心
- webapps:存放我们要运行的web application 的文件夹,对于我们是经常使用的。
- work:Tomcat内部进行预编译的文件夹,无需关心。
剩下的都为一些文档,无需关心。
1.4 启动服务器
在 bin 目录中, 双击 startup.bat 即可启动 Tomcat 服务器
看到形如以下内容的日志, 说明启动成功:
注意:
在 Windows 上通过 cmd 方式启动 Tomcat 会出现乱码. 但是不影响 Tomcat 的使用.
乱码的原因是 Tomcat 默认按照 UTF-8 的编码方式处理中文. 而 windows 的 cmd 默认是 GBK 编
码.
如果使用 Linux 或者 IDEA 中的终端来启动 Tomcat, 则没有乱码问题. 因此此处的乱码我们暂时不
处理.
进入欢迎界面(执行该步骤的前提是Tomcat服务器是开启的状态,没有被关闭)
在浏览器中输入 127.0.0.1:8080 即可看到 Tomcat 的默认欢迎页面:
分析:
这里的8080(端口号)是tomcat默认的,类似于MySQL的默认端口为3306.当然默认端口是可以修改的。
启动失败怎么办?
最常见的启动失败原因是端口号被占用.
Tomcat 启动的时候默认会绑定 8080 和 8005 端口.
如果有其他进程已经绑定了这两个端口中的任意一个, 都会导致 Tomcat 不能启动.
在命令行中使用 netstat -ano | findstr 8080 确定看 8080 是否被其他进程绑定,如果是的话,根据提供的PID到任务管理器中, 把对方进程干掉,再重新启动 Tomcat 一般就可以解决问题
1.5 部署静态页面
理解 "静态"
静态页面也就是内容始终固定的页面. 即使 用户不同/时间不同/输入的参数不同 , 页面内容也不会发生变化. (除非网站的开发人员修改源代码, 否则页面内容始终不变).
对应的, 动态页面指的就是 用户不同/时间不同/输入的参数不同, 页面内容会发生变化.
举一个例子:
比如搜狗搜索的主页就是一个静态页面:
而哔哩哔哩就是一个动态的页面:
可以简单的认为:
静态页面就是简单的html页面,而动态页面是html+数据。
部署单个HTML文件
1) 创建 hello.html
我们可以把自己写好的 HTML 部署到 Tomcat 中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>hello</div>
</body>
</html>
2) 把 hello.html 拷贝到 Tomcat 的 webapps/ROOT 目录中:
3) 在浏览器中通过 URL http://127.0.0.1:8080/hello.html 来访问(127.0.0.1 为环回 IP, 表示当前主机):
部署带有 CSS / JavaScript / 图片 的 HTML
实际开发时我们的 HTML 不仅仅是单一文件, 还需要依赖一些其他的资源: CSS, JavaScript, 图片等.这些资源也要一起部署过去:
1) 创建 hello2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>静态页面</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<img src="doge.jpg" alt="">
<script src="app.js"></script>
</body>
</html>
2) 创建 style.css
img {
width: 500px;
height: 500px;
}
3) 创建 app.js:
console.log("hello");
4) 准备一个 doge.jpg
5) 把以上四个文件都拷贝到 Tomcat 的 webapps/ROOT 中
6) 在浏览器中通过 http://127.0.0.1:8080/hello2.html 来访问页面
分析:
通过 Fiddler 抓包, 可以发现此时浏览器和服务器之间有 4 个 HTTP 请求/响应的交互:
- 在浏览器地址栏里输入 http://127.0.0.1:8080/hello2.html 会触发一次 GET 请求. 这个请求会拿到 hello2.html 的内容.
- 浏览器解析 hello2.html, 其中的 link 标签, img 标签, script 标签都会分别触发一次 GET 请求. 请求的 路径 分别为 /style.css , /doge.jpg , /app.js
部署 HTML 到单独的目录中
实际开发中我们的 HTML 可能不止一个, 依赖的 CSS / JavaScript 文件也可能比较多. 这个时候就不适合全都拷贝到 webapps/ROOT 目录中了(这就会显的比较乱).
我们可以创建一个单独的目录, 和 ROOT 并列, 来存放我们要部署的内容.
1) 在 webapps 中创建目录 HelloApp, 和 ROOT 目录并列
2) 把刚才创建的 hello2.html, style.css, doge.jpg, app.js 拷贝到 HelloApp 目录中(为了结构更清楚, 我们在 HelloApp 中又创建了一些子目录, css , img , js 来分别放 css , 图片,JavaScript 文件)
3)调整 hello2.html 的代码, 把引用 css, js, 图片的路径进行微调:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>静态页面</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<img src="img/doge.jpg" alt="">
<script src="js/app.js"></script>
</body>
</html>
4) 在浏览器中通过 http://127.0.0.1:8080/HelloApp/hello2.html:
通过抓包可以看到, 浏览器和服务器之间同样是 4 次 HTTP 请求/响应 的交互:
但是可以看到路径上和之前发生了变化:
由于我们把这些文件都放到了 HelloApp 目录中, 通过 GET 请求访问这些文件时的路径也要带上
HelloApp,此处的 HelloApp 称为 Application Path (应用路径) 或者 Context Path (上下文路径)
二、实现一个简单的Servlet程序
2.1 什么是servlet
Servlet(Server Applet),全称Java Servlet。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类。
这里我们所指的servlet是前者,是一组 Tomcat 提供给程序猿的 API, 帮助程序猿简单高效的开发一个 web app.
Servlet 主要做的工作
- 允许程序猿注册一个类, 在 Tomcat 收到某个特定的 HTTP 请求的时候, 执行这个类中的一些代码.
- 帮助程序猿解析 HTTP 请求, 把 HTTP 请求从一个字符串解析成一个 HttpRequest 对象.
- 帮助程序猿构造 HTTP 响应. 程序猿只要给指定的 HttpResponse 对象填写一些属性字段, Servlet
- 就会自动的安装 HTTP 协议的方式构造出一个 HTTP 响应字符串, 并通过 Socket 写回给客户端
简而言之, Servlet 是一组 Tomcat 提供的 API, 让程序猿自己写的代码能很好的和 Tomcat 配合起来, 从而更简单的实现一个 web app.
而不必关注 Socket, HTTP协议格式, 多线程并发等技术细节, 降低了 web app 的开发门槛, 提高了开发效率。
2.2 实现servlet程序
一共分为七个步骤:分别为创建项目,引入依赖,创建目录结构,编写代码,打包程序,部署程序,验证。
在创建项目之前我们先来了解一下maven:
Maven 是一个项目管理工具,可以对 Java 项目进行规范目录结构,构建,管理依赖,打包,测试等.....
2.2.1 创建项目
这里我们是需要创建一个maven项目,
创建完成:
- main中存放的是业务代码,其java文件夹中放java代码,resource中放程序所依赖的文件(配置文件,数据文件,图片,图标,声音等。
- test中的java文件夹存放的是测试代码。
- porm.xml是maven项目总的配置文件。
2.2.2 引入依赖
maven项目创建完毕后,会自动生成pom.xml文件。我们需要在prom.xml中引入Servlet API 依赖的jar包
1)在中央仓库 https://mvnrepository.com/ 中搜索 "servlet", 一般第一个结果就是:
2) 选择版本. 一般我们使用 3.1.0 版本
3) 把中央仓库中提供的 xml 复制到项目的 pom.xml 中:
代码:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Servlet_1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
关于 groupId, artifactId, version
这几个东西暂时我们不关注. 啥时候需要关注呢? 如果我们要把这个写的代码发布到中央仓库上,
那么就需要设定好这几个 ID 了.
- groupId: 表示组织名称
- artifactId: 表示项目名称
- version: 表示版本号
中央仓库就是按照这三个字段来确定唯一一个包的。
2.2.3 创建目录
在main目录下,和java目录并列,创建一个webapp目录,然后在 webapp 目录内部创建一个 WEB-INF 目录, 并创建一个 web.xml 文件:
并在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>
webapp 目录就是未来部署到 Tomcat 中的一个重要的目录. 当前我们可以往 webapp 中放一些静态资源, 比如 html , css 等.
在这个目录中还有一个重要的文件 web.xml. Tomcat 找到这个文件才能正确处理 webapp 中的动态资源.
2.2.4 编写代码
在java目录中创建一个HelloServlet,代码如下:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这是在服务器的控制台上打印hello
System.out.println("hello");
//要想把hello返回给客户端,需要使用以下代码,getWriter会得到一个Writer对象。
resp.getWriter().write("hello");
}
}
分析:
- HttpServlet是刚刚我们从maven中下载的Servlet的jar包,一般写servlet代码就是继承这个类,这里继承的目的主要是针对HttpServlet进行功能的扩展。
- HttpServletRequest 表示HTTP请求,Tomcat按照HTTP请求的格式把 字符串 格式的请求转成一个HttpServletRequest 对象,后续想获取请求中的信息(方法,url,header,body等)都是通过这个对象来获取的。
- 这里的doGet方法,不需要自己手动调用,而是交给Tomcat来调用,一般情况下,Tomcat收到get请求后,就会触发doGet方法,随即Tomcat会构造好两个参数,req和resp,但是具体要不要调用这个类的doGet方法,还是得看GET请求的路径是什么,不同的路径可以触发不同的代码。这是因为一个Servlet程序中,可以有很多Servlet类,每个Servlet类都可以关联到不同的路径(对应到不同的资源),也就是说每个Servlet对应每种不同的功能。
- req是来自于TCP socket中读出来的字符串按照HTTP协议解析得到的对象,该对象中的属性就算和HTTP请求报文格式相对应的。
- resp则是一个输出型参数,是一个空对象(不是null的意思,只是new了一个对象,里面的各种属性没有设置),需要程序员根据req,在doGet内部结合业务逻辑构造一个resp对象。
- @WebServlet是一个注解,这里的作用是把当前这个类和一个HTTP请求的路径关联起来,注解在java中是一个特殊的类,Java专门定义了一种语法糖来实现注解,注解的作用是针对一个类/方法,进行额外的“解释说明”,赋予这个类/方法额外的功能/含义。
- getWriter()会得到一个Writer对象,那么 resp.getWriter().write("hello,world")中:writer对象是从属于resp对象的,此时进行的write操作其实就是往resp的body部分进行写入,等resp对象构造完成之后,tomcat会统一的转成http响应的格式,再写入socket(Ps:Writer和Reader都是字符流,InputStream和OutputStream是字节流)
2.2.5 打包程序
问:为何要打包程序?
答:向以往的那些代码,都是带有main方法的,就像是一个小汽车.汽车自带发动机.自己就能跑.
而 Servlet 写的代码,相当于是个“车厢”,自己没有发动机.可以把这个车厢挂到车头后面让车头拉着它跑.车头就是 Tomcat ~~(tomcat 带 main,也就是带发动机),就需要把写好的代码挂到火车头后面~~ 打包 + 部署
使用 maven 进行打包. 打开 maven 窗口 (一般在 IDEA 右侧就可以看到 Maven 窗口, 如果看不到的话,可以通过 菜单 -> View -> Tool Window -> Maven 打开)
然后展开 Lifecycle , 双击 package 即可进行打包
如果比较顺利的话, 能够看到 SUCCESS 这样的字样:
打包成功后, 可以看到在 target 目录下, 生成了一个 jar 包:
这样的 jar 包并不是我们需要的, Tomcat 需要识别的是另外一种 war 包格式.
另外这个 jar 包的名字太复杂了, 我们也希望这个名字能更简单一点。
war 包和 jar 包的区别
jar 包是普通的 java 程序打包的结果. 里面会包含一些 .class 文件.换而言之,jar就算.class构造的压缩包。
war 包是 java web 的程序, 里面除了会包含 .class 文件之外, 还会包含 HTML, CSS, JavaScript, 图
片, 以及其他的 jar 包. 打成 war 包格式才能被 Tomcat 识别。
这里需要我们手动将jar转变成war包,在prom.xml中添入以下代码:
这里的finalName里面的字段就是 Context path(也就是war包的名称)。
分析:
- packaging中描述的是打哪种包(默认是jar)
- finalName中是打包后的名称
再次点击maven,展开 Lifecycle , 双击 package 即可进行打包
2.2.6 部署
把刚才打包好的war拷贝到tomcat的webapps目录中即可,接着启动tomcat。
2.2.7 验证
运行结果:
分析:
这里的hello_wervlet是第一级路径,也叫做context path/ application path.
context path 路径如下(也是war包名称):
这里的hello是第二级路径,叫做servlet path(是注解里面所填的)
2.2.8 Servlet小结
将url在浏览器地址栏里面输入之后,浏览器就构造了一个对应的HTTP GET请求,发送给Tomcat,tomcat就会根据第一级路径,确定了具体的webapp:
找到这个目录之后,就会加载里面的类,尤其会重点识别由@WebServlet注解修饰的类,在注解中找到/hello这个路径,之后创建实例(只会创建一次,如果之前创建过了,后续会复用上次的实例),之后再解析出HTTP请求的方法是什么,再根据请求调用HelloServlet的方法(doGet,doPost......).与此同时,TomCat还会构造出HttpServletRequest对象和 HttpServletResponse对象。
2.3 使用smart Tomcat插件进行优化部署
手动拷贝 war 包到 Tomcat 的过程比较麻烦. 我们还有更方便的办法.此处我们使用 IDEA 中的 Smart Tomcat 插件完成这个工作:
什么是插件?
插件是一种电脑程序,透过和应用程序(例如网页浏览器,电子邮件客户端)的互动,用来替应用程序增加一些所需要的特定的功能。最常见的有游戏、网页浏览器的插件和媒体播放器的插件。
1)安装smart Tomcat插件:
安装完毕之后需重启Idea。
2)配置Smart Tomcat插件(记得配置完成点击窗口右下角的OK)
3)点击OK之后。右上角就变成了这样:
点击绿色的三角号, IDEA 就会自动进行编译, 部署, 启动 Tomcat 的过程
此时可以观察到,这里就没有出现乱码的情况了:
4)访问欢迎界面
看到这里我们知道Tomcat其实是有两种运行方式的:
a.通过bin目录下的startup运行,之后手动将war包拷贝过去
b.让idea直接调用tomcat,让tomcat加载当前项目中的目录(tomcat支持启动的时候显示的指定一个特定的webapp目录,相当于是让tomcat加载单个webapp运行):
这个是没有打war包的,虽然程序可以正常运行,但是之前webapps下已有的内容(比如欢迎界面)就没了:
5)访问页面
注意这里所对应的关系:
使用Smart Tomcat部署的时候,我们发现Tomcat的webapps内部并没有被拷贝一个war包,也没有看到解压缩的内容,得出:Smart Tomcat相当于是在Tomcat启动的时候直接引用了项目中的webapp和target目录
2.3.1 访问出错怎么办?
出现404
404表示用户访问的资源不存在,大概率是URL路径写的不正确。
需要注意Context Path 和 Servlet Path是否漏写或者写错。
亦或者是web.xml没写,或者写错:
其实在代码运行的时候,就有提示:
出现405
405表示对应的HTTP请求方法没有实现(简单来说:请求的方法在服务器端没有找到):
还有可能是调用了父类的doGet方法,根据源码,如果在Http版本号为1.1的时候就会返回405:
以下为调用父类doGet方法且有在页面输出信息:(如果仅是调用父类doGet方法,只会出现405,而不会有问号)
出现500
这种情况一般发生在代码出现异常的时候出现,就是我们的代码没有处理异常,异常就会向上传递,Tomcat会进行处理,这时候的页面异常状态码就是500。这个问题好解决,因为页面会把出现异常的原因以及出现异常的位置告诉你。
比如我们给代码来一个空指针异常:
小结
熟悉 HTTP 协议能够让我们调试问题事半功倍.
- 4xx 的状态码表示路径不存在, 往往需要检查 URL 是否正确, 和代码中设定的 Context Path 以及Servlet Path 是否一致.
- 5xx 的状态码表示服务器出现错误, 往往需要观察页面提示的内容和 Tomcat 自身的日志, 观察是否存在报错.