JavaWeb三大组件之Servlet详解:Servlet使用案例、继承关系、生命周期、会话跟踪 以及 服务器内部转发和客户端重定向 详解

1 什么是Servlet

  • Servlet 是JavaEE 规范之一。规范就是接口
  • Servlet 就JavaWeb 三大组件之一。三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器
  • Servlet 是运行在服务器上的一个java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端

2 手动实现Servlet

  • 编写一个类去实现Servlet 接口
  • 实现servlet 方法,处理请求,并响应数据
  • 到web.xml 中去配置servlet 程序的访问地址servlet 程序

2.1 代码实现

首先我们需要再IDEA中配置Tomcat服务器

创建一个web工程

在这里插入图片描述

编写Servlet

public class AddServlet extends HttpServlet {

    /*
    响应post请求
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String fname = req.getParameter("fname");
        System.out.println(fname);

    }
}

创建表单

用post方式提交表单,请求路径为 /add

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="add" method="post">
        名称:<input type="text" name="fname"/><br/>
        价格:<input type="text" name="price"/><br/>
        库存:<input type="text" name="fcount"/><br/>
        备注:<input type="text" name="remark"/><br/>
        <input type="submit" value="添加" />
    </form>
</body>
</html>

在web.xml中配置servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>AddServlet</servlet-name>
        <servlet-class>com.njupt.AddServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AddServlet</servlet-name>
        <url-pattern>/add</url-pattern>
    </servlet-mapping>
</web-app>

代码执行结果

浏览器发送请求
在这里插入图片描述
后台打印结果

在这里插入图片描述

2.2 代码执行流程分析

3 Servlet的继承关系

在这里插入图片描述

继承关系

javax.servlet.Servlet 接口
	javax.servlet.GenericServlet 抽象类
		javax.servlet.http.HttpServlet 抽象子类

相关方法

javax.servlet.Servlet接口:

  • void init(config) :初始化方法
  • void service(request,response):服务方法,该方法最终由 javax.servlet.http.HttpServlet 类实现
  • void destory():销毁方法

3.1 HttpServlet 之 service()方法解析

这里代码的意思就是根据不同的请求方式,调用向对应的方法来处理,在 2.1 中是post请求,因此我们先来看看 this.doPost(req, resp); 方法。

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

3.2 HttpServlet 之 doPost()方法解析

如果我们没有重写doPost()方法或者doGet()方法,那么就会默认执行父类HttpServlet中的方法,会返回405错误。

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String msg = lStrings.getString("http.method_post_not_supported");
    this.sendMethodNotAllowed(req, resp, msg);
}

private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
	// 获取协议,一般就是 HTTP 协议
    String protocol = req.getProtocol();
    if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
        resp.sendError(405, msg);
    } else {
        resp.sendError(400, msg);
    }

}

3.3 总结

  • 继承关系: HttpServlet -> GenericServlet -> Servlet
  • Servlet中的核心方法: init() , service() , destroy()
  • 服务方法: 当有请求过来时,service方法会自动响应(其实是tomcat容器调用的)
    • 在HttpServlet中我们会去分析请求的方式:到底是get、post、head还是delete等等;
    • 然后再决定调用的是哪个do开头的方法;
    • 那么在HttpServlet中这些do方法默认都是405的实现风格,要我们子类去实现对应的方法,否则默认会报405错误
    • 因此,我们在新建Servlet时,我们才会去考虑请求方法,从而决定重写哪个do方法。

4 Servlet 的生命周期

4.1 代码演示

创建Servlet类

public class DemoServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        System.out.println("正在初始化。。。");
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("正在服务。。。");
    }

    @Override
    public void destroy() {
        System.out.println("正在销毁。。。");
    }
}

在web.xml中进行配置

<servlet>
    <servlet-name>DemoServlet</servlet-name>
    <servlet-class>com.njupt.DemoServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>DemoServlet</servlet-name>
    <url-pattern>/test01</url-pattern>
</servlet-mapping>

第一次发请求控制台打印结果

在这里插入图片描述

之后发请求控制台打印结果

在这里插入图片描述

关闭容器控制台打印结果

在这里插入图片描述

4.2 案例分析

生命周期:从出生到死亡的过程就是生命周期。对应Servlet中的三个方法:init(),service(),destroy()

默认情况下

  • 第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service())
  • 从第二次请求开始,每一次都是服务;
  • 当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法。

通过案例我们发现:

  • Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。
  • 默认情况下,第一次请求时,tomcat才会去实例化,初始化,然后再服务
    • 这样的好处是什么? 提高系统的启动速度
    • 这样的缺点是什么? 第一次请求时,耗时较长
  • 因此得出结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机

我们可以通过< load-on-startup >来设置servlet启动的先后顺序,数字越小,启动越靠前,最小值 0

<servlet>
    <servlet-name>DemoServlet</servlet-name>
    <servlet-class>com.njupt.DemoServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DemoServlet</servlet-name>
    <url-pattern>/test01</url-pattern>
</servlet-mapping>

Servlet在容器中是:单例的、线程不安全的

  • 单例:所有的请求都是同一个实例去响应。
  • 线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生了变化。
  • 我们已经知道了servlet是线程不安全的,给我们的启发是: 尽量的不要在servlet中定义成员变量。如果不得不定义成员变量,那么不要去:① 不要去修改成员变量的值不要去根据成员变量的值做一些逻辑判断

5 Servlet 会话跟踪(cookie & session)

大家在看这一小节之前可以先了解一下HTTP、cookir 、session

参考:https://blog.csdn.net/qq_36389060/article/details/124086547

5.1 什么是会话跟踪技术

HTTP 是无状态的:服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的

会话跟踪技术:cookie、session

  • 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session;
  • 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器;
  • 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
  • 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息;
    • 如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息;
    • 如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
  • 此时,当我们在该网站的不同的网页中左右横跳的时候,通过跟随request中的cookie中的 sessionID ,使用相同的session。
  • 当过了一段时间后,,服务器中对应的session的生命周期过去,session中的内容写入数据库,该session被清除。
  • 此时再访问页面,会使用cookie重新登录,服务器重新创建session,重复上述过程

根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态

5.2 会话跟踪代码演示

public class Demo02Servlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取Session,获取不到创建一个新的
        HttpSession session = req.getSession();
        System.out.println("Session ID: " + session.getId());

    }
}
<servlet>
    <servlet-name>Demo02Servlet</servlet-name>
    <servlet-class>com.njupt.Demo02Servlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Demo02Servlet</servlet-name>
    <url-pattern>/test02</url-pattern>
</servlet-mapping>

浏览器第一次发送请求:http://localhost:8080/test02

查看服务端控制台打印结果
在这里插入图片描述
查看浏览器控制台打印结果
在这里插入图片描述
可以看到Set-cookie字段中的JESEEIONID 和 服务端打印的 SessionID是一样。

浏览器第二次发送请求:http://localhost:8080/test02,此时还会从新创建一个Session吗?答案是不会的。

查看服务端控制台打印结果:两此控制台打印结果是一样的
在这里插入图片描述
查看浏览器控制台打印结果:Response Headers 中没有Set-cookie字段
在这里插入图片描述

5.3 常用的API

  • request.getSession()获取当前的会话,没有则创建一个新的会话
  • request.getSession(true) -> 效果和不带参数相同
  • request.getSession(false) -> 获取当前会话,没有则返回null,不会创建新的
  • session.getId() -> 获取sessionID
  • session.isNew() 判断当前session是否是新的
  • session.getMaxInactiveInterval():session的非激活间隔时长,默认1800秒(半小时)
  • session.setMaxInactiveInterval()
  • session.invalidate():强制性让会话立即失效

5.4 session保存作用域

  • session保存作用域是和具体的某一个session对应的
  • 常用的API:
    • session.setAttribute(k,v):向当前session保存作用域保存数据
    • Object session.getAttribute(k):获取当前session保存作用域中的数据
    • removeAttribute(k):删除当前session保存作用域中的数据

6 服务器内部转发 & 客户端重定向

服务器内部转发:request.getRequestDispatcher("...").forward(request,response);

  • 一次请求响应的过程,对于客户端而言,内部可能经过了多少次转发,只是客户端不知道的罢了。
  • 注意:服务器内部转发时,客户端浏览器的地址栏是没有变化的
    在这里插入图片描述

客户端重定向: response.sendRedirect("....");

  • 客户端重定向是 两次请求响应的过程。服务端执行重定向时,会返回给客户端浏览器302,从而让客户端浏览器冲顶向到另一个URL。
  • 注意:客户端重定向,客户端浏览器的地址栏是有变化的

在这里插入图片描述

7 Servlet 的保存作用域

原始情况下,保存作用域我们可以认为有四个:

  • page:页面级别,现在几乎不用
  • request一次请求响应范围
  • session一次会话范围
  • application整个应用程序范围

7.1 request 保存作用域

客户端重定向之后还能获取到name吗?

肯定是不行的,因为这是两次HTTP请求,request作用域不同。
在这里插入图片描述

代码示例:

public class DemoServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("name","lilili");
        resp.sendRedirect("/test02");
    }

}
public class Demo02Servlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getAttribute("name"));
    }
}

输出结果:未获取到
在这里插入图片描述

服务器内部转发 之后能获取到name吗?

这是可以的,因为属于同一次HTTP请求。
在这里插入图片描述
代码示例:

public class DemoServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("name","lilili");
        req.getRequestDispatcher("test02").forward(req,resp);
    }

}
public class Demo02Servlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getAttribute("name"));
    }
}

输出结果:获取到了
在这里插入图片描述

7.2 session 保存作用域

不同客户端能互相访问对方的session作用域吗?

肯定是不行的。

7.3 application 保存作用域(ServletContext)

不用客户端能访问的applicaion的内容是相同的吗?

是相同的,下面的例子是能访问到的。
在这里插入图片描述

public class DemoServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = req.getServletContext();
        servletContext.setAttribute("name","lilili");
        resp.sendRedirect("test02");
    }

}
public class Demo02Servlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = req.getServletContext();
        System.out.println(servletContext.getAttribute("name"));
    }
}

8 路径问题说明

在这里插入图片描述
在tomcat配置页面中,http://localhost:8080/ 指向的是 web目录下的文件
在这里插入图片描述

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熠熠98

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值