我们上一张聊的是Tomcat,它其实就是一个 HTTP 服务器,而Servlet 是基于 Tomcat 的 原生api ,除了 Servlet,后面还有聊到很多 api 。
Servlet 是什么
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。
我们先来稍稍了解以下Servlet 的运行原理;
Servlet运行原理
我们说 Servlet 是基于 Tomcat 的,那么Servlet 也要实现 服务器的功能。
当Web服务器接收到一个HTTP请求时,它会先判断请求内容——如果是静态网页数据,Web服务器将会自行处理,然后产生响应信息;如果牵扯到动态的数据,Web服务器会将请求转交给Servlet容器。此时Servlet容器会找到对应的处理该请求的Servlet实例来处理,结果会送回Web服务器,再由Web服务器传回用户端。
针对同一个Servlet,Servlet容器会在第一次收到http请求时建立一个Servlet实例,然后启动一个线程。第二次收到http请求时,Servlet容器无须建立相同的Servlet实例,而是启动第二个线程来服务客户端请求。所以多线程方式不但可以提高Web应用程序的执行效率,也可以降低Web服务器的系统负担。
上述来自:理解Servlet工作原理 - 简书 (jianshu.com)
从上述该原理可知,Servlet 并非一个单独执行的程序,而是写一个代码片段,穿插在 Tomcat 中。
上节课我们只是看到了部署的效果图,那么这里将会介绍servlet 是如何穿插在 Tomcat 中的。
创建第一个Servlet 项目
我们主要分七步走:
1. 创建一个项目
这里创建一个 Maven 项目 JDK 是1.8 的。
创建好之后,有这个 pom.xml;
第一次创建好右下角应该会有一个提示:
Maven projects need to be imported
Import Changed Import Auto-import
选后面一个就好
2. 引入依赖
上面的 pom.xml 作用就是依赖存放的位置。
Maven Repository: Search/Browse/Explore (mvnrepository.com)
上面的网站是 Maven 中央仓库,我们所需要的依赖一般都来自这里。
我们在 Maven 中央仓库搜索自己需要引入的依赖,例如需要下载 Servlet 的依赖:
往下一拉,哇!这么多,我们需要找于自己版本相匹配的, 小版本无所谓,我们选择下载人数最多的就好。
点进去之后,将这一段复制下来就好了。
回到 pom.xml 中,我们需要加一个标签:
<dependencies> </ dependencies> 这就是依赖存放的位置。
只需要一个 这个双标签就好,这个双标签内可以放置多个依赖。
第一次可能会爆红,那可能是因为没有下载下来,为了确保它下载到了本地我们给它强制刷新以下:
如何还爆红,那么需要查看以下你的配置是否正确,将你安装的Maven 位置重新配置到 idea 上一般就解决问题了。
这个依赖我们目前就这样,其他的先不关注,等后面遇到一个再说一个。
3. 创建目录
当项目创建好了之后, IDEA 会帮我们自动创建出一些目录.:
这些目录中:
- src 表示源代码所在的目录
- main/java 表示源代码的根目录. 后续创建 .java 文件就放到这个目录中.
- main/resources 表示项目的一些资源文件所在的目录. 此处暂时不关注.
- test/java 表示测试代码的根目录. 此处暂时不关注
当然,只有这些目录还是不够滴,我们还需要创建一些新的目录/文件。
我们先需要一个 webapp 文件,在这个文件下再创建一个 WEB-INF 文件,再在WEB-INF 中创建一个 web.xml 文件。
如下图:
WebApp 文件夹是一个专门为 Web 应用程序准备的文件夹,其中包含所有必要的文件和文件夹,以便在服务器上运行和托管 Web 应用程序;
WEB-INF 这个文件代表着目前项目的的结构。
这也是必须要这么写的(具体为啥,我暂时也不知道)。先这样写着,具体为啥,后面学到肯定是有的,到时候再说。
这里 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>
我们把这段内容之间复制上去就好了。
4. 编写代码
我们的重点来了,我们之前不是需要引入依赖嘛,我们就是需要依赖中 提供的各个 api 。
而我们引入的 servlet 依赖,就包含了 HttpServlet 这个类。
还记得之前学习的网络编程那一块嘛?
我们这里重写了一个 doGet 方法,就是来处理 从客户端发送过来的请求, 这里提供的参数 req 就代表这请求,resp 就代表的响应。
这里是没有main 方法的,至于为什么,就回去看看之前网络编程那一块内容。
回到 doGet 方法中,它默认就调用了 父类的doGet 方法,我们也可以点开看看 父类的 doGet 方法做了什么事情:
为了让,doGet 方法 执行我们自己编写的代码,所以我们要将这句 删掉,否则就会报错。
我们这里就随便写写,没有逻辑,主要是为了展示:
这个编写内容到这里就完了,Tomcat 内部会按照 HttpServlet 的引用方式调用 doGet 方法,
由于现在创建了这个子类,在 Tomcat 中大概率会形成如下代码:
HttpServlet servlet = new HttpServlet();
servlet.doGet(req, resp);
具体的 Tomcat 源码我也不知道,以后有机会等我把源码看看,会再写一篇博客,讲讲这个。
虽然说,代码是写完了,我们是认识了这写代码,但是浏览器还不认识啊,我们得给它加上一个 Servlet path ,这个 路径马上就会提到了,马上就会认识了。
怎么加呢?如下图:
我们这里参考参考人家的博客了解以下Servlet 的注解:
@WebServlet注解(Servlet注解) (biancheng.net)
5. 打包
我们这里的代码并不能直接运行,必须放到 Tomcat 中才可以运行(这一步就是部署)。
部署的前提就是要打包;
对于一个项目,都不会只有一个 .java 文件,进一步就会产生很多 .clsass 文件,此时将这些 .class 文件打成一个压缩包在进行拷贝就很有必要了。
我们有一些常见的 压缩包: 以 .zip 、 .rar 为后缀。
Java中 常见的有两个:.jar 、 .war
jar 包就是普通的 Java程序打包出来的,而 war 包是专门为 Tomcat 部署 定制的。
jar 包 和 war 包 其实本质上没有区别,都是把一堆 .class 文件打包进去,但是 war 包是属于 tomcat 格式的;里面会带有特定的目录格式和文件,例如:web.xml
后续 tomcat 要识别这些内容加载 webapp
来看看如何打包:
1. 先在 pom.xml 文件中添加两个标签:
<packaging>war</packaging> <build> <finalName>HelloWorld</finalName> </build>
含义如下:
2. 打包
我们先单击 右边的 Maven 找到你要打包的目录,双击package ,就可以打包了;如果出了啥问题,我们可以将问题拷贝下来去搜索,一般都是需要添加几条命令就解决了(都很简单)。
打包成功,就会出现上图,我们需要将这个 war 包 copy 到tomcat 上:
将war 包拷贝到该目录下。
6. 部署
这一步很简单,前面没问题的话,这一步就不会有问题。
我们直接运行 tomcat 服务器,tomcat 会自动解析 war 包。
他就成功解析出了这个文件。
7. 验证程序
为了确保其他主机可以访问,我们先用自己的主机访问一次:
我们上述中的localhost 其实也就是 127.0.0.1 地址,这个叫做环回地址,表示就是你的主机地址(将其改为 127.0.0.1 也可以访问到);我的tomcat 也是部署在本机上的, 所以可以访问到这个网站,如果需要让其他主机访问到有两种情况:
- 同一个局域网中,可以在 Windows 命令提示符(即 cmd)中输入:ipconfig 找到自己在局域网下的 ip 地址。
- 不在同一个局域网下,那就必须得拿到你的外网 ip 地址,我们之前说到过,外网地址是无法直接访问内网地址的。
所以,如果遇到 404 不要慌
- 先查看自己的 地址是否写对了,
- 这里没问题,在就查看你的配置是否出了问题,
- 都没问题再看看你的操作是否出了问题,文件太多太杂的话,建议重新开一个空白的文件夹,重写。
我们上述讲到的都是一个项目的大致流程,并没有讲到 Servlet 的api,上面只是用到了一个 HttpServlet,接下来讲讲Servlet 的原生 api。
Servlet 的原生 api
HttpServlet
我们写的都是继承自 HttpServlet 这个类的,如上例题:
我们需要知道,哪些方法是能够被重写的,也就是 HttpServlet 中都有哪些方法,方法的作用是干啥的。
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在 HttpServlet 实例不再使用的时候调用一次收到 HTTP 请求的时候调用 |
service | 收到 GET 请求的时候调用(由 service 方法调用) |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions/... | 收到其他请求的时候调用(由 service 方法调用) |
- init 方法:在 HttpServlet 实例化之后被调用一次,使用这个方法来初始化相关工作;只有在首次收到请求的时候会出发,如果服务器没有重启,就不会再触发这个方法
- destory 方法: 这个方法是该 webapp 被销毁(卸载之前)执行一次,用来处理一些收尾工作。一般来说,都不怎么使用这个方法,我们的 tomcat 端口 有两个;
- 8080 端口 叫做业务端口,8005 端口叫做管理窗口;
- 如果是通过 8005 这个管理窗口来关闭服务器,那么能执行destory 方法
- 如果我们采用直接杀死进程的方法来关闭服务器,那么就不会触发destory 方法
- service 方法:每次收到路径匹配的请求,就会执行
- doGet / doPost 其实实在 service 中被调用的,一般不会重写 service ,只是重写 doXXX 方法就行了。
这里也是个面试题:Servlet 的生命周期
init() 方法
service() 方法 (这里包括了 doGet()和 doPost() 两个方法)
destroy() 方法
这个图片是我抄的。
其实学完了 doGet()和 doPost() 两个方法 HttpServlet 类就基本可以结束了。
HttpServletRequest
当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成
HttpServletRequest 对象。
核心方法如下:
方法 | 描述 |
String getProtocol() | 返回请求协议的名称和版本。 |
String getMethod() | 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。 |
String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请 求的 URL 的一部分。 |
String getContextPath() | 返回指示请求上下文的请求 URI 部分。 |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串。 |
Enumeration getParameterNames() | 返回一个 String 对象的枚举,包含在该请求中包含的参数的名 称。 |
String getParameter(String name) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。 |
String[] getParameterValues(String name) | 返回一个字符串对象的数组,包含所有给定的请求参数的值,如 果参数不存在则返回 null。 |
Enumeration getHeaderNames() | 返回一个枚举,包含在该请求中包含的所有的头名。 |
String getHeader(String name) | 以字符串形式返回指定的请求头的值。 |
String getCharacterEncoding() | 返回请求主体中使用的字符编码的名称。 |
String getContentType() | 返回请求主体的 MIME 类型,如果不知道类型则返回 null。 |
int getContentLength() | 以字节为单位返回请求主体的长度,并提供输入流,或者如果长 度未知则返回 -1。 |
InputStream getInputStream() | 用于读取请求的 body 内容. 返回一个 InputStream 对象 |
这里的方法我就不一一演示了,我们来挑选几个最重要的来看看就好。
getParameter 方法
在本章中,我们重点介绍 getParameter 这个方法。
以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。
为什么这里它作为重点呢?
我们要做网站,前后端之间的交互是非常重要的,而 getParameter 方法,就是 后端用来接收前端传递的数据。
前端在给后端传递数据 经常以如下几种方式传递:
- 通过 query string 传递
- 通过 body(form 表单) 传递
- 通过 body(json 这个常用)传递
通过 query string 传递
前后端在交互之前都需要相互约定,彼此用的是什么格式,相关程序员就知道后面具体该用哪个 api 已经知道需要怎么转换。
比如约定好 前端使用 query string 传递 账号密码,这个 query string 中的键值对都是程序员自定义的。
通过 body 传递(form)
相当于 body 里存储的数据格式,就和 query string 一样,但是 Content-type 是 application/x-www-form-urlencoded 这样的格式。
如果是这样的格式,此时也是通过 getParameter 来获取键值对了。
这里没有具体的栗子讲的可能不是很清楚,先将就将就,后面正式写项目再去补充。
通过 body 传递(json)
这个是咱的重点,在项目的时候,用的最多的就是这小子。
假设,前端发送了一个登录请求,可以通过抓包看看发送的请求
这里我们可以看到发送的账号和密码。
json 也是个键值对格式的数据,但是 Servlet 自身没有内置 json 解析功能,此时就需要借助第三方库(可以去Maven 中央仓库搜索一下)。
这个第三方库有很多,常见的有这几种:
fastjson、gson、jackson(这个属于spring官方指定库)。
这里就稍稍看个栗子就好了,后面会有具体的代码,到了再具体介绍。
假设左上角是请求的 body ,右下脚是我们约定的类,此时我们需要调用 objectMapper 这个方法下的 readValue 这个方法,对前端传输来的请求进行解析。
这里 readValue 要做的事情:
- 解析 json 字符串,将其转化为若干个 键值对。
- 第二个参数 User.class 就是反射,拿到前后端之间约定的格式,然后依次为 蓝本 ,进行拆分 键值对,获取需要的信息。
- 遍历属性,根据属性的名字,去上述准备好的键值对里(可以通过反射来完成),查询,看看这个属性的名字是否存在对应的 value ,如果存在,九八 value 复制到该属性中。
HttpServletRequest 使用这个类,主要就是用于获取到请求各个方面的信息,尤其是前端传过来的 自定义 数据。
HttpServletResponse
方法 | 描述 |
void setStatus(int sc) | 为该响应设置状态码。 |
void setHeader(String name, String value) | 设置一个带有给定的名称和值的 header. 如果 name 已经存在, 则覆盖旧的值. |
void addHeader(String name, String value) | 添加一个带有给定的名称和值的 header. 如果 name 已经存在, 不覆盖旧的值, 并列添加新的键值对 |
void setContentType(String type) | 设置被发送到客户端的响应的内容类型。 |
void setCharacterEncoding(String charset) | 设置被发送到客户端的响应的字符编码(MIME 字符集)例如, UTF-8。 |
void sendRedirect(String location) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端。 |
PrintWriter getWriter() | 用于往 body 中写入文本格式数据. |
OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据. |
这里也不过多介绍了,看名字大概都能猜到是啥意思。
其实这个 api 也就这样,没啥难的,学会上面的一个,另外两个都是触类旁通的,后面的 sping 等等,也就这样,没听过名字听起来就好高大上,学完了 Servlet 也就这么回事,到时候多写写也就熟悉了。
纸上得来终觉浅 绝知此事要躬行。
继续加油吧!!!