后端(二):Servlet

我们上一张聊的是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. 创建一个项目

后端(二):Servlet_第1张图片

这里创建一个 Maven 项目 JDK 是1.8 的。

后端(二):Servlet_第2张图片

创建好之后,有这个 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 的依赖:

后端(二):Servlet_第3张图片

往下一拉,哇!这么多,我们需要找于自己版本相匹配的, 小版本无所谓,我们选择下载人数最多的就好。

后端(二):Servlet_第4张图片

 点进去之后,将这一段复制下来就好了。

回到 pom.xml 中,我们需要加一个标签:

后端(二):Servlet_第5张图片

这就是依赖存放的位置。

只需要一个 这个双标签就好,这个双标签内可以放置多个依赖。

第一次可能会爆红,那可能是因为没有下载下来,为了确保它下载到了本地我们给它强制刷新以下:

后端(二):Servlet_第6张图片

如何还爆红,那么需要查看以下你的配置是否正确,将你安装的Maven 位置重新配置到 idea 上一般就解决问题了。

这个依赖我们目前就这样,其他的先不关注,等后面遇到一个再说一个。

3. 创建目录

当项目创建好了之后, IDEA 会帮我们自动创建出一些目录.:

后端(二):Servlet_第7张图片

这些目录中:

  • src 表示源代码所在的目录
  • main/java 表示源代码的根目录. 后续创建 .java 文件就放到这个目录中.
  • main/resources 表示项目的一些资源文件所在的目录. 此处暂时不关注.
  • test/java 表示测试代码的根目录. 此处暂时不关注

当然,只有这些目录还是不够滴,我们还需要创建一些新的目录/文件。

我们先需要一个 webapp 文件,在这个文件下再创建一个 WEB-INF 文件,再在WEB-INF 中创建一个 web.xml 文件。

如下图:

后端(二):Servlet_第8张图片

 WebApp 文件夹是一个专门为 Web 应用程序准备的文件夹,其中包含所有必要的文件和文件夹,以便在服务器上运行和托管 Web 应用程序;

WEB-INF 这个文件代表着目前项目的的结构。

这也是必须要这么写的(具体为啥,我暂时也不知道)。先这样写着,具体为啥,后面学到肯定是有的,到时候再说。

这里 web.xml 文件中需要添加以下内容:

        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >


    Archetype Created Web Application

我们把这段内容之间复制上去就好了。

4. 编写代码

后端(二):Servlet_第9张图片

我们的重点来了,我们之前不是需要引入依赖嘛,我们就是需要依赖中 提供的各个 api 。

而我们引入的 servlet 依赖,就包含了 HttpServlet 这个类。

还记得之前学习的网络编程那一块嘛?

我们这里重写了一个 doGet 方法,就是来处理 从客户端发送过来的请求, 这里提供的参数 req 就代表这请求,resp 就代表的响应。

这里是没有main 方法的,至于为什么,就回去看看之前网络编程那一块内容。

 回到 doGet 方法中,它默认就调用了 父类的doGet 方法,我们也可以点开看看 父类的 doGet 方法做了什么事情:

后端(二):Servlet_第10张图片

为了让,doGet 方法 执行我们自己编写的代码,所以我们要将这句 删掉,否则就会报错。

我们这里就随便写写,没有逻辑,主要是为了展示:

后端(二):Servlet_第11张图片

这个编写内容到这里就完了,Tomcat 内部会按照 HttpServlet 的引用方式调用 doGet 方法,

由于现在创建了这个子类,在 Tomcat 中大概率会形成如下代码:

HttpServlet servlet = new HttpServlet();
servlet.doGet(req, resp);

 具体的 Tomcat 源码我也不知道,以后有机会等我把源码看看,会再写一篇博客,讲讲这个。

虽然说,代码是写完了,我们是认识了这写代码,但是浏览器还不认识啊,我们得给它加上一个 Servlet path ,这个 路径马上就会提到了,马上就会认识了。

怎么加呢?如下图: 

后端(二):Servlet_第12张图片

我们这里参考参考人家的博客了解以下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 文件中添加两个标签:

war

    HelloWorld

含义如下:

 2.   打包

后端(二):Servlet_第13张图片

我们先单击 右边的 Maven 找到你要打包的目录,双击package ,就可以打包了;如果出了啥问题,我们可以将问题拷贝下来去搜索,一般都是需要添加几条命令就解决了(都很简单)。

后端(二):Servlet_第14张图片

打包成功,就会出现上图,我们需要将这个 war 包 copy 到tomcat 上:

 后端(二):Servlet_第15张图片

将war 包拷贝到该目录下。

6. 部署

这一步很简单,前面没问题的话,这一步就不会有问题。

我们直接运行 tomcat 服务器,tomcat 会自动解析 war 包。

后端(二):Servlet_第16张图片

他就成功解析出了这个文件。

7. 验证程序

 为了确保其他主机可以访问,我们先用自己的主机访问一次:

后端(二):Servlet_第17张图片

我们上述中的localhost 其实也就是 127.0.0.1 地址,这个叫做环回地址,表示就是你的主机地址(将其改为 127.0.0.1 也可以访问到);我的tomcat 也是部署在本机上的, 所以可以访问到这个网站,如果需要让其他主机访问到有两种情况:

  1. 同一个局域网中,可以在 Windows 命令提示符(即 cmd)中输入:ipconfig 找到自己在局域网下的 ip 地址。
  2. 不在同一个局域网下,那就必须得拿到你的外网 ip 地址,我们之前说到过,外网地址是无法直接访问内网地址的。

所以,如果遇到 404 不要慌

  1. 先查看自己的 地址是否写对了,
  2. 这里没问题,在就查看你的配置是否出了问题,
  3. 都没问题再看看你的操作是否出了问题,文件太多太杂的话,建议重新开一个空白的文件夹,重写。

我们上述讲到的都是一个项目的大致流程,并没有讲到 Servlet 的api,上面只是用到了一个 HttpServlet,接下来讲讲Servlet 的原生 api。

Servlet 的原生 api

HttpServlet

我们写的都是继承自 HttpServlet 这个类的,如上例题: 

后端(二):Servlet_第18张图片

我们需要知道,哪些方法是能够被重写的,也就是 HttpServlet 中都有哪些方法,方法的作用是干啥的。

init 在 HttpServlet 实例化之后被调用一次
destory   在 HttpServlet 实例不再使用的时候调用一次收到 HTTP 请求的时候调用
service   收到 GET 请求的时候调用(由 service 方法调用)
doGet 收到 GET 请求的时候调用(由 service 方法调用)
doPost 收到 POST 请求的时候调用(由 service 方法调用)
doPut/doDelete/doOptions/... 收到其他请求的时候调用(由 service 方法调用)
  • init 方法:在 HttpServlet 实例化之后被调用一次,使用这个方法来初始化相关工作;只有在首次收到请求的时候会出发,如果服务器没有重启,就不会再触发这个方法
  • destory 方法:  这个方法是该 webapp 被销毁(卸载之前)执行一次,用来处理一些收尾工作。一般来说,都不怎么使用这个方法,我们的 tomcat 端口 有两个;
  1. 8080 端口 叫做业务端口,8005 端口叫做管理窗口;
  2. 如果是通过 8005 这个管理窗口来关闭服务器,那么能执行destory 方法
  3. 如果我们采用直接杀死进程的方法来关闭服务器,那么就不会触发destory 方法
  • service  方法:每次收到路径匹配的请求,就会执行
  • doGet /  doPost 其实实在 service 中被调用的,一般不会重写 service ,只是重写 doXXX 方法就行了。

这里也是个面试题:Servlet 的生命周期

  1. init() 方法

  2. service() 方法 (这里包括了 doGet()和 doPost() 两个方法)

  3. destroy() 方法

后端(二):Servlet_第19张图片

这个图片是我抄的。

 其实学完了  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 对象

这里的方法我就不一一演示了,我们来挑选几个最重要的来看看就好。

后端(二):Servlet_第20张图片

 后端(二):Servlet_第21张图片

 后端(二):Servlet_第22张图片

getParameter  方法

在本章中,我们重点介绍 getParameter 这个方法。

以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。

为什么这里它作为重点呢?

我们要做网站,前后端之间的交互是非常重要的,而 getParameter 方法,就是 后端用来接收前端传递的数据。

前端在给后端传递数据 经常以如下几种方式传递:

  1. 通过 query string 传递
  2. 通过 body(form 表单) 传递
  3. 通过 body(json 这个常用)传递

通过 query string 传递

前后端在交互之前都需要相互约定,彼此用的是什么格式,相关程序员就知道后面具体该用哪个 api 已经知道需要怎么转换。

比如约定好 前端使用 query string 传递 账号密码,这个 query string 中的键值对都是程序员自定义的。

通过 body 传递(form)

相当于 body 里存储的数据格式,就和 query string 一样,但是 Content-type 是 application/x-www-form-urlencoded 这样的格式。

如果是这样的格式,此时也是通过 getParameter 来获取键值对了。

这里没有具体的栗子讲的可能不是很清楚,先将就将就,后面正式写项目再去补充。

通过 body 传递(json)

这个是咱的重点,在项目的时候,用的最多的就是这小子。

后端(二):Servlet_第23张图片

假设,前端发送了一个登录请求,可以通过抓包看看发送的请求

后端(二):Servlet_第24张图片

这里我们可以看到发送的账号和密码。

json 也是个键值对格式的数据,但是 Servlet 自身没有内置 json 解析功能,此时就需要借助第三方库(可以去Maven 中央仓库搜索一下)。

这个第三方库有很多,常见的有这几种:

fastjson、gson、jackson(这个属于spring官方指定库)。

这里就稍稍看个栗子就好了,后面会有具体的代码,到了再具体介绍。

后端(二):Servlet_第25张图片

假设左上角是请求的 body ,右下脚是我们约定的类,此时我们需要调用 objectMapper 这个方法下的 readValue 这个方法,对前端传输来的请求进行解析。

 这里 readValue 要做的事情:

  1. 解析 json 字符串,将其转化为若干个 键值对。
  2. 第二个参数 User.class 就是反射,拿到前后端之间约定的格式,然后依次为 蓝本 ,进行拆分 键值对,获取需要的信息。
  3. 遍历属性,根据属性的名字,去上述准备好的键值对里(可以通过反射来完成),查询,看看这个属性的名字是否存在对应的 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 也就这么回事,到时候多写写也就熟悉了。

纸上得来终觉浅 绝知此事要躬行。

继续加油吧!!!

你可能感兴趣的:(后端,JavaEE(初阶),servlet,tomcat,java)