Servlet小结

Servlet是什么

Servlet 是一种实现动态页面的技术 . 是一组 Tomcat 提供给程序猿的 API, 帮助程序猿简单高效的开发一个 web app。

第一个Servlet程序

1.创建项目

菜单 -> 文件 -> 新建项目 ->选择Maven

2.引入依赖

Maven 项目创建完毕后 , 会自动生成一个 pom.xml 文件。
我们需要在 pom.xml 中引入 Servlet API 依赖的 jar 包。
1. 在中央仓库https://mvnrepository.com/ 中搜索 "servlet", 一般第一个结果就是。

2.选择版本,一般选用3.1.0

Servlet的版本要和Tomcat的版本匹配。

具体版本对应在https://tomcat.apache.org/whichversion.html 可以查到。

3.把中央仓库中的xml复制到项目中的pom.xml。

其中<dependencies>标签内部放置项目依赖的jar包,maven会自动下载依赖到本地。

<?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>message_wall</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

    </dependencies>

</project>

3.创建目录

1)创建webapp目录

main 目录下 , java 目录并列 , 创建一个 webapp 目录 ( 注意 , 不是 webapps)。

2)创建web.xml

然后在 webapp 目录内部创建一个 WEB - INF 目录 , 并创建一个 web.xml 文件。
3)编写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 中的动
态资源。

4.编写代码

java 目录中创建一个类 HelloServlet, 代码如下 :
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        System.out.println("hello");
        resp.getWriter().write("hello");
   }
}

当满足以下三个条件时,Tomcat就会找到这个类,在合适的时候进行调用。

a)创建的类要继承自HttpServlet。

b)这个类需要使用@WebServlet注解,关联上一个HTTP路径。

c)这个类需要实现doXXX方法。

5.打包程序

使用 maven 进行打包 . 打开 maven 窗口 ( 一般在 IDEA 右侧就可以看到 Maven 窗口 , 如果看不到的话 ,可以通过 菜单 -> View -> Tool Window -> Maven 打开 )
然后展开 Lifecycle , 双击 package 即可进行打包。

打包成功以后,可以看到在target目录下,生成了一个jar包。

这样的 jar 包并不是我们需要的 , Tomcat 需要识别的是另外一种 war 包格式 .
另外这个 jar 包的名字太复杂了 , 我们也希望这个名字能更简单一点.
pom.xml 中新增一个 packing 标签 , 表示打包的方式是打一个 war .
<packaging>war</packaging>
pom.xml 中再新增一个 build 标签 , 内置一个 finalName 标签 , 表示打出的 war 包的名字是
HelloServlet。
<build>
    <finalName>ServletHelloWorld</finalName>
</build>

完整的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</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </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>

    <packaging>war</packaging>

    <build>
        <finalName>java1091</finalName>
    </build>

</project>
重新使用 maven 打包 , 可以看到生成的新的 war 包的结果。

6.部署程序

war 包拷贝到 Tomcat webapps 目录下 .

7.验证程序

此时通过浏览器访问 http://127.0.0.1:8080/ServletHelloWorld/hello
就可以看到结果了
注意 : URL 中的 PATH 分成两个部分 , 其中 HelloServlet Context Path, hello Servlet Path

更方便的部署方法

手动拷贝 war 包到 Tomcat 的过程比较麻烦 . 我们还有更方便的办法 .
此处我们使用 IDEA 中的 Smart Tomcat 插件完成这个工作

配置好了以后,点击绿色三角运行即可。

Servlet运行原理

Servlet 的代码中我们并没有写 main 方法 , 那么对应的 doGet 代码是如何被调用的呢 ? 响应又是如何返回给浏览器的?

1. 接收请求 :
  • 用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求 .
  • 这个 HTTP 请求会经过网络协议栈逐层进行 封装 成二进制的 bit , 最终通过物理层的硬件设备转换成光信号/ 电信号传输出去 .
  • 这些承载信息的光信号 / 电信号通过互联网上的一系列网络设备 , 最终到达目标主机 ( 这个过程也需要网络层和数据链路层参与).
  • 服务器主机收到这些光信号 / 电信号 , 又会通过网络协议栈逐层进行 分用 , 层层解析 , 最终还原成HTTP 请求 . 并交给 Tomcat 进程进行处理 ( 根据端口号确定进程 )
  • Tomcat 通过 Socket 读取到这个请求 ( 一个字符串 ), 并按照 HTTP 请求的格式来解析这个请求 , 根据请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 类 . 再根据当前请求的方法 (GET/POST/...), 决定调用这个类的 doGet 或者 doPost 等方法 . 此时我们的代码中的doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息 .

2.根据请求计算响应

  • 在我们的 doGet / doPost 方法中 , 就执行到了我们自己的代码 . 我们自己的代码会根据请求中的一些信息, 来给 HttpServletResponse 对象设置一些属性 . 例如状态码 , header, body .

3.返回响应

  • 我们的 doGet / doPost 执行完毕后 , Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好的对象转换成一个符合 HTTP 协议的字符串 , 通过 Socket 把这个响应发送出去 .
  • 此时响应数据在服务器的主机上通过网络协议栈层层 封装 , 最终又得到一个二进制的 bit , 通过物理层硬件设备转换成光信号/ 电信号传输出去
  • 这些承载信息的光信号 / 电信号通过互联网上的一系列网络设备 , 最终到达浏览器所在的主机 ( 这个过程也需要网络层和数据链路层参与)
  • 浏览器主机收到这些光信号 / 电信号 , 又会通过网络协议栈逐层进行 分用 , 层层解析 , 最终还原成HTTP 响应 , 并交给浏览器处理
  • 浏览器也通过 Socket 读到这个响应 ( 一个字符串 ), 按照 HTTP 响应的格式来解析这个响应 . 并且把body 中的数据按照一定的格式显示在浏览器的界面上 .

Tomcat的伪代码如下:

1.Tomcat初始化流程

class Tomcat {
 // 用来存储所有的 Servlet 对象
 private List<Servlet> instanceList = new ArrayList<>();
 public void start() {
       // 根据约定,读取 WEB-INF/web.xml 配置文件;
        // 并解析被 @WebServlet 注解修饰的类
       
        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. 
        Class<Servlet>[] allServletClasses = ...;
        
        // 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
            // 这里是利用 java 中的反射特性做的
           // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
            
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
       }
        
        // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.init();
       }
        
        // 利用我们之前学过的知识,启动一个 HTTP 服务器
        // 并用线程池的方式分别处理每一个 Request
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);
        
        while (true) {
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
               doHttpRequest(socket); 
           });
       }
        // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.destroy();
       }
   }
    
    public static void main(String[] args) {
        new Tomcat().start();
   }
}

2.Tomcat处理请求流程

class Tomcat {
    void doHttpRequest(Socket socket) {
        // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
        HttpServletRequest req = HttpServletRequest.parse(socket);
        HttpServletRequest resp = HttpServletRequest.build(socket);
        
        // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内
容
        // 直接使用我们学习过的 IO 进行内容输出
        if (file.exists()) {
            // 返回静态内容
            return;
       }
        
        // 走到这里的逻辑都是动态内容了
        
        // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
        // 最终找到要处理本次请求的 Servlet 对象
        Servlet ins = findInstance(req.getURL());
        
        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
       ins.service(req, resp); 
       } catch (Exception e) {
            // 返回 500 页面,表示服务器内部错误
       }
   }
}

3.Servlet的service方法的实现

class Servlet {
    public void service(HttpServletRequest req, HttpServletResponse resp) {
        String method = req.getMethod();
        if (method.equals("GET")) {
            doGet(req, resp);
       } else if (method.equals("POST")) {
            doPost(req, resp);
       } else if (method.equals("PUT")) {
            doPut(req, resp);
       } else if (method.equals("DELETE")) {
            doDelete(req, resp);
       } 
       ......
   }
}

Servlet API

HttpServlet

方法名称
调用时机
init
HttpServlet 实例化之后被调用一次
destroy
HttpServlet 实例不再使用的时候调用一次
service
收到 HTTP 请求的时候调用
doGet
收到 GET 请求的时候调用 ( service 方法调用 )
doPost
收到 POST 请求的时候调用 ( service 方法调用 )
doPut/doDelete/doOptions/...
收到其他请求的时候调用 ( service 方法调用 )
Servlet 生命周期". (也就是描述了一个 Servlet 实例从生到死的过程).
注意 : HttpServlet 的实例只是在程序启动时创建一次 . 而不是每次收到 HTTP 请求都重新创建实例 .

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 对象 .
注意: 请求对象是服务器收到的内容, 不应该修改. 因此上面的方法也都只是 "读" 方法, 而不是 "写"
方法.

HttpServletResponse

Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应 , 然后把响应的数据设置到 HttpServletResponse 对象中 .
然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式 , 转成一个字符串 , 并通过Socket 写回给浏览器 .
方法
描述
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 中写入二进制格式数据 .
注意: 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序猿设置的. 因此上面的方
法都是 "写" 方法.

Cookie和Session

回顾 Cookie

HTTP 协议自身是属于 "无状态" 协议:默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系.
但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的,例如登陆网站成功后, 第二次访问的时候服务器就能知道该请求是否是已经登陆过了.
Cookie的作用是让同一个客户端和服务端之间,多次通信产生联系。(粗略理解为Cookie可以帮助保存一些信息,如账号密码等,下次重新打开该网页的时候,这些内容还存在)

理解会话机制(Session)

服务器同一时刻收到的请求是很多的 . 服务器需要区分清楚每个请求是从属于哪个用户 , 就需要在服务器这边记录每个用户令牌以及用户的信息对应关系
会话的本质就是一个 " 哈希表 ", 存储了一些键值对结构 . key 就是令牌的 ID(token/sessionId), value 就是用户信息( 用户信息可以根据需求灵活设计 ).
令牌ID-用户信息
通过令牌ID,判断是哪个客户端来访问,再传递出对应的用户信息。

Cookie Session 的区别

  • Cookie 是客户端的机制. Session 是服务器端的机制.
  • Cookie Session 经常会在一起配合使用. 但是不是必须配合.
  • 完全可以用 Cookie 来保存一些数据在客户端. 这些数据不一定是用户身份信息, 也不一定是 token / sessionId
  • Session 中的 token / sessionId 也不需要非得通过 Cookie / Set-Cookie 传递.

核心方法

HttpServletRequest 类中的相关方法
HttpServletResponse 类中的相关方法
方法
描述
void addCookie(Cookie cookie)
把指定的 cookie 添加到响应中 .
HttpSession 类中的相关方法
一个 HttpSession 对象里面包含多个键值对 . 我们可以往 HttpSession 中存任何我们需要的信息 .
方法
描述
Object getAttribute(String
name)
该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回null
void setAttribute(String
name, Object value)
该方法使用指定的名称绑定一个对象到该 session 会话
boolean isNew()
判定当前是否是新创建出的会话
Cookie 类中的相关方法
每个 Cookie 对象就是一个键值对
方法
描述
String getName()
该方法返回 cookie 的名称。名称在创建后不能改变。 ( 这个值是 Set-Cooke 字段设置给浏览器的 )
String getValue()
该方法获取与 cookie 关联的值
void setValue(String
newValue)
该方法设置与 cookie 关联的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值