Servlet与HTTP协议实战解析

一、关于一个web站点的欢迎页面

1.什么是一个web站点的欢迎页面?

  • 对于一个webapp来说,我们是可以设置它的欢迎页面的。

  • 设置了欢迎页面之后,当你访问这个webapp的时候,或者访问这个web站点的时候,没有指定任何"资源路径",这个时候会默认访问你的欢迎页面。

  • 我们一般的访问方式是:

  • 如果我们访问的方式是:

    • http://localhost:8080/servlet06 如果我们访问的就是这个站点,没有指定具体的资源路径。它默认会访问谁呢?

    • 默认会访问你设置的欢迎页面。

2.怎么设置欢迎页面呢?

  • 第一步:我在IDEA工具的web目录下新建了一个文件login.html
  • 第二步:在web.xml文件中进行了以下的配置
复制代码
<welcome-file-list>
        <welcome-file>login.html</welcome-file>
    </welcome-file-list>
    • 注意:设置欢迎页面的时候,这个路径不需要以"/"开始。并且这个路径默认是从webapp的根下开始查找。
  • 第三步:启动服务器,浏览器地址栏输入地址

3.如果在webapp的根下新建一个目录,目录中再给一个文件,那么这个欢迎页该如何设置呢?

  • 在webapp根下新建page1

  • 在page1下新建page2目录

  • 在page2目录下新建page.html页面

  • 在web.xml文件中应该这样配置

    <welcome-file-list> <welcome-file>page1/page2/page.html</welcome-file> </welcome-file-list>

注意:路径不需要以"/"开始,并且路径默认从webapp的根下开始找。

4.一个webapp是可以设置多个欢迎页面的

复制代码
<welcome-file-list>
    <welcome-file>page1/page2/page.html</welcome-file>
    <welcome-file>login.html</welcome-file>
</welcome-file-list>
  • 注意:越靠上的优先级越高。找不到的继续向下找。

5.你有没有注意一件事:当我的文件名设置为index.html的时候,不需要在web.xml文件中进行配置欢迎页面。这是为什么?

演示:

  • 这是因为小猫咪Tomcat服务器已经提前配置好了。

  • 实际上配置欢迎页面有两个地方可以配置:

    • 一个是在webapp内部的web.xml文件中。(在这个地方配置的属于局部配置)

    • 一个是在CATALINA_HOME/conf/web.xml文件中进行配置。(在这个地方配置的属于全局配置)

复制代码
<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>
    • Tomcat服务器的全局欢迎页面是:index.html index.htm index.jsp。如果你一个web站点没有设置局部的欢迎页面,Tomcat服务器就会以index.html index.htm index.jsp作为一个web站点的欢迎页面。
  • 注意原则:局部优先原则。(就近原则)

6.欢迎页可以是一个Servlet吗?

  • 当然可以。

  • 你不要多想,欢迎页就是一个资源,既然是一个资源,那么可以是静态资源,也可以是动态资源。

  • 静态资源:index.html welcome.html .....

  • 动态资源:Servlet类。

  • 步骤:

    • 第一步:写一个Servlet

      public class WelcomeServlet extends HttpServlet {
      @Override
      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();
      out.print("

      welcome to 世界!

      ");
      }
      }

第二步:在web.xml文件中配置servlet

第三步:在web.xml文件中配置欢迎页

WelcomeServlet类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.print("<h1>welcome to 世界!</h1>");
    }
}

二、关于WEB-INF目录

  • 在WEB-INF目录下新建了一个文件:welcome.html

  • 打开浏览器访问:http://localhost:8080/servlet07/WEB-INF/welcome.html 出现了404错误。

  • 注意:放在WEB-INF目录下的资源是受保护的。在浏览器上不能够通过路径直接访问。所以像HTML、CSS、JS、image等静态资源一定要放到WEB-INF目录之外。

三、HttpServletRequest接口详解

  • HttpServletRequest是一个接口,全限定名称:jakarta.servlet.http.HttpServletRequest

  • HttpServletRequest接口是Servlet规范中的一员。

  • HttpServletRequest接口的父接口:ServletRequest

1.HttpServletRequest接口的实现类谁写的? HttpServletRequest对象是谁给创建的?

RequestTestServlet类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class RequestTestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        // org.apache.catalina.connector.RequestFacade@642e8513
        out.print(request);


}}
  • 通过测试:org.apache.catalina.connector.RequestFacade 实现了 HttpServletRequest接口

    public class RequestFacade implements HttpServletRequest {}

  • 测试结果说明:Tomcat服务器(WEB服务器、WEB容器)实现了HttpServletRequest接口,还是说明了Tomcat服务器实现了Servlet规范。而对于我们javaweb程序员来说,实际上不需要关心这个,我们只需要面向接口编程即可。

  • 我们关心的是HttpServletRequest接口中有哪些方法,这些方法可以完成什么功能!!!!

2.HttpServletRequest对象中都有什么信息?都包装了什么信息?

  • HttpServletRequest对象是Tomcat服务器负责创建的。这个对象中封装了什么信息?封装了HTTP的请求协议。

  • 实际上是用户发送请求的时候,遵循了HTTP协议,发送的是HTTP的请求协议,Tomcat服务器将HTTP协议中的信息以及数据全部解析出来,然后Tomcat服务器把这些信息封装到HttpServletRequest对象当中,传给了我们javaweb程序员。

  • javaweb程序员面向HttpServletRequest接口编程,调用方法就可以获取到请求的信息了。

3.request和response对象的生命周期?

  • request对象和response对象,一个是请求对象,一个是响应对象。这两个对象只在当前请求中有效。

  • 一次请求对应一个request。

  • 两次请求则对应两个request。

  • .....

4.HttpServletRequest接口中有哪些常用的方法?

怎么获取前端浏览器用户提交的数据?

复制代码
Map<String,String[]> getParameterMap() 这个是获取Map
Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key
String[] getParameterValues(String name) 根据key获取Map集合的value
String getParameter(String name)  获取value这个一维数组当中的第一个元素。这个方法最常用。
// 以上的4个方法,和获取用户提交的数据有关系。

思考:如果是你,前端的form表单提交了数据之后,你准备怎么存储这些数据,你准备采用什么样的数据结构去存储这些数据呢?

代码测试:

RequestTestServlet类:

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class RequestTestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        // org.apache.catalina.connector.RequestFacade@642e8513
        out.print(request);
}
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }

}

register.html

  • 前端提交的数据格式:username=abc&userpwd=111&aihao=s&aihao=d&aihao=tt

  • 我会采用Map集合来存储:

注意:前端表单提交数据的时候,假设提交了120这样的"数字",其实是以字符串"120"的方式提交的,所以服务器端获取到的一定是一个字符串的"120",而不是一个数字。(前端永远提交的是字符串,后端获取的也永远是字符串。)

5.手工开发一个webapp。测试HttpServletRequest接口中的相关方法。

test.html

复制代码
<!DOCTYPE html>
<html>
<head>
    <!-- 设置字符编码为 UTF-8 -->
    <meta charset="UTF-8">
    <title>测试 Servlet</title>
</head>
<body>
<form action="test" method="post">
    <label for="name">姓名:</label>
    <input type="text" id="name" name="name"><br>
    <label for="email">邮箱:</label>
    <input type="text" id="email" name="email"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
复制代码
TestServlet类
复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Map;


public class TestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        // 测试 request.getParameterMap()
        Map<String, String[]> parameterMap = request.getParameterMap();
        out.println("<h2>参数映射:</h2>");
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] values = entry.getValue();
            out.print(key + ": ");
            for (String val : values) {
                out.print(val + " ");
            }
            out.println();
        }

        // 测试 request.getParameterNames()
        Enumeration<String> names = request.getParameterNames();
        out.println("<h2>参数名称:</h2>");
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            out.println(name);
        }

        // 测试 request.getParameterValues()
        String[] values = request.getParameterValues("name");
        out.println("<h2>参数 'name' 的值:</h2>");
        if (values != null) {
            for (String val : values) {
                out.println(val);
            }
        } else {
            out.println("'name' 没有值");
        }

        // 测试 request.getParameter()
        String value = request.getParameter("name");
        out.println("<h2>参数 'name' 的单个值:</h2>");
        if (value != null) {
            out.println(value);
        } else {
            out.println("'name' 没有值");
        }
    }
} 

6.request对象实际上又称为"请求域"对象。

⑴应用域对象是什么?

  • ServletContext (Servlet上下文对象。)

  • 什么情况下会考虑向ServletContext这个应用域当中绑定数据呢?

    • 第一:所有用户共享的数据。

    • 第二:这个共享的数据量很小。

    • 第三:这个共享的数据很少的修改操作。

    • 在以上三个条件都满足的情况下,使用这个应用域对象,可以大大提高我们程序执行效率。

    • 实际上向应用域当中绑定数据,就相当于把数据放到了缓存(Cache)当中,然后用户访问的时候直接从缓存中取,减少IO的操作,大大提升系统的性能,所以缓存技术是提高系统性能的重要手段。

⑵你见过哪些缓存技术呢?

  • 字符串常量池

  • 整数型常量池 [-128~127],但凡是在这个范围当中的Integer对象不再创建新对象,直接从这个整数型常量池中获取。大大提升系统性能。

  • 数据库连接池(提前创建好N个连接对象,将连接对象放到集合当中,使用连接对象的时候,直接从缓存中拿。省去了连接对象的创建过程。效率提升。)

  • 线程池(Tomcat服务器就是支持多线程的。所谓的线程池就是提前先创建好N个线程对象,将线程对象存储到集合中,然后用户请求过来之后,直接从线程池中获取线程对象,直接拿来用。提升系统性能)

  • 后期你还会学习更多的缓存技术,例如:redis、mongoDB.....

⑶ServletContext当中有三个操作域的方法:

⑷"请求域"对象

  • "请求域"对象要比"应用域"对象范围小很多。生命周期短很多。请求域只在一次请求内有效。

  • 一个请求对象request对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。

  • 请求域对象也有这三个方法:

    • 请求域和应用域的选用原则?

      • 尽量使用小的域对象,因为小的域对象占用的资源较少。

AServlet 类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取系统当前时间
        Date nowTime = new Date();

        // 将系统当前时间绑定到请求域当中
        request.setAttribute("sysTime", nowTime);

        // 取出来
        Object sysTime = request.getAttribute("sysTime");
        //输出到浏览器
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.print("系统当前时间是:" + sysTime);

}}

这段代码是典型的Java Web开发(如Servlet)中处理请求和响应的逻辑,主要作用是通过请求域(Request Scope)传递数据,并将服务器当前时间输出到浏览器。以下是逐行解释:


①. 获取系统当前时间
复制代码
Date nowTime = new Date();
  • 作用 :创建一个Date对象,表示代码执行时的系统当前时间(精确到毫秒)。

②. 将时间绑定到请求域
复制代码
request.setAttribute("sysTime", nowTime);
  • 作用 :将当前时间存入请求域(Request Scope) ,键为"sysTime",值为nowTime对象。

  • 请求域特点

    • 数据仅在当前请求生命周期内有效(如转发到JSP或另一个Servlet时仍可访问)。

    • 若直接返回响应(非转发),则请求域数据在响应完成后自动销毁。


③. 从请求域中取出时间
复制代码
Object sysTime = request.getAttribute("sysTime");
  • 作用 :通过键"sysTime"从请求域中获取存储的对象(此处为Date类型)。

  • 注意 :返回类型是Object,需根据实际类型强制转换(如Date sysTime = (Date) request.getAttribute("sysTime");)。


④. 输出到浏览器
复制代码
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("系统当前时间是:" + sysTime);
  • 作用

    • response.setContentType("text/html"):设置响应内容类型为HTML(告知浏览器按HTML解析)。

    • response.getWriter():获取响应输出流PrintWriter对象。

    • out.print(...):将字符串"系统当前时间是:+ sysTime"写入响应体,发送给浏览器。

  • 实际效果 :浏览器会显示类似系统当前时间是:Mon Jul 01 14:30:45 CST 2024的内容(Date默认调用toString()方法)。


⑤完整流程图示
复制代码
客户端发起请求 → Servlet获取当前时间 → 存入请求域 → 取出数据 → 通过响应输出到浏览器

⑥关键细节
  1. 请求域 vs. 其他作用域

    • 请求域(Request):数据仅在一次请求中有效(适用于转发场景)。

    • 会话域(Session):数据在一次会话中有效(如用户登录状态)。

    • 应用域(Application):数据在整个Web应用生命周期中有效(全局共享)。

  2. 直接响应 vs. 请求转发

    • 若直接通过response输出(如本例),请求域数据仅在当前Servlet中有效。

    • 若使用RequestDispatcher转发请求(如request.getRequestDispatcher("page.jsp").forward(request, response);),则目标页面(JSP/Servlet)仍可访问请求域数据。

  3. 潜在问题

    • 未处理字符编码 :中文字符可能乱码,需添加response.setCharacterEncoding("UTF-8")

    • 未关闭流PrintWriter通常无需手动关闭,容器会自动处理。


⑦示例改进建议
复制代码
// 设置响应编码防止中文乱码
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

// 强制类型转换(更安全)
Date sysTime = (Date) request.getAttribute("sysTime");
out.print("系统当前时间是:" + sysTime);

⑧典型应用场景
  1. Servlet到JSP的数据传递

    • Servlet处理业务逻辑并存储结果到请求域 → 转发到JSP渲染页面。
  2. 中间件数据传递

    • 在过滤器(Filter)或拦截器中修改请求域数据,供后续Servlet使用。
  3. API返回动态数据

    • 直接通过响应输出动态生成的内容(如实时时间、计算结果)。

⑨总结

这段代码的核心目的是通过请求域传递服务器时间,并将结果输出到浏览器,展示了Java Web开发中请求域的基本使用和响应操作。理解请求域的生命周期和与其他作用域的区别,是掌握Java Web数据传递的关键。

7.跳转

BServlet 类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class BServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 从请求域当中取出绑定的数据
        Object sysTime = request.getAttribute("sysTime");

        //输出到浏览器
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.print("系统当前时间是:" + sysTime);
    }
}

AServlet类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取系统当前时间
        Date nowTime = new Date();

        // 将系统当前时间绑定到请求域当中
        request.setAttribute("sysTime", nowTime);
}}

出现 sysTimenull 的原因是 AServlet 和 BServlet 不在同一个请求链中,导致 BServlet 无法访问 AServlet 设置的请求域数据。以下是详细分析和解决方案:


⑴.问题原因

  1. 请求域的生命周期

    • 请求域(request 对象)的数据仅在当前请求范围内有效

    • 如果直接通过浏览器访问 BServlet(例如输入 http://localhost:8080/app/BServlet),则 BServlet 处理的是一个全新的请求 ,其请求域中自然没有 sysTime 数据。

  2. 未触发数据传递

    • 若未通过**请求转发(Forward) 包含(Include)**将请求从 AServlet 传递到 BServlet,则两个 Servlet 的 request 对象是独立的。

⑵.代码示例分析

代码结构:
复制代码
// AServlet(未完整)
public class AServlet extends HttpServlet {
    protected void doGet(...) {
        Date nowTime = new Date();
        request.setAttribute("sysTime", nowTime);  // 存入请求域
        // 缺少转发到 BServlet 的代码!
    }
}

// BServlet
public class BServlet extends HttpServlet {
    protected void doGet(...) {
        Object sysTime = request.getAttribute("sysTime");  // 新请求中取不到值
        out.print("系统当前时间是:" + sysTime);  // 输出 null
    }
}
执行流程:
复制代码
浏览器直接访问 BServlet → 创建新请求 → 请求域无数据 → sysTime 为 null

⑶.思考

复制代码
// 这样做可以吗?
// 在AServlet当中new一个BServlet对象,然后调用BServlet对象的doGet方法,把request对象传过去。
// 这个代码虽然可以实现功能,但是Servlet对象不能自己由程序员来new。自己new的Servlet对象生命周期不受Tomcat服务器的管理。
/*BServlet bServlet = new BServlet();
bServlet.doGet(request, response);*/

⑷怎么转发?代码怎么写?

执行流程:
复制代码
浏览器访问 AServlet → AServlet 设置请求域 → 转发到 BServlet 
→ BServlet 读取同一请求域 → 输出时间
第一步:获取请求转发器对象
复制代码
RequestDispatcher dispatcher = request.getRequestDispatcher("/b");

作用

这行代码就像你打电话给前台说:"帮我叫一下隔壁办公室的小王过来处理这个任务!"

  • request:当前用户的请求(比如用户访问了一个网页)。
  • /b :告诉服务器要找的目标资源(比如一个Servlet或JSP页面,路径以/开头表示从Web应用的根目录开始)。
  • RequestDispatcher:就是一个"传话筒"或"调度员",它负责找到目标资源(小王),并把当前的任务(请求)交给它。

第二步:调用转发方法完成跳转
复制代码
dispatcher.forward(request, response);

作用

这行代码就像前台把用户交给小王处理,但用户全程以为自己一直在和你(当前组件)打交道。

  • forward() :让服务器内部把当前请求和响应的"接力棒"传递给目标资源(/b)。
  • requestresponse:当前用户的请求和响应对象被原封不动地传递给目标资源,目标资源可以继续处理这个请求,并生成最终的响应结果。

整体效果
  1. 服务器内部协作

    • 你(当前组件)把任务转交给小王(目标组件),但用户完全不知道这个过程。
    • 浏览器的地址栏不会变(还是原来的URL),用户感觉就像在和一个页面互动。
  2. 数据共享

    • 如果你在转发前在请求中存了数据(比如 request.setAttribute("name", "Alice")),目标资源(/b)可以直接读取这些数据。
    • 这就像你和小王共享同一个笔记本,写在笔记本上的内容彼此都能看到。
  3. 与"重定向"的区别

    • 如果用"重定向"(response.sendRedirect("/b")),浏览器会收到一个"跳转指令",必须重新发送请求到/b,地址栏会变成/b的URL。
    • forward()是服务器内部跳转,只发生一次请求,地址栏保持原URL。

举个栗子

假设你是一个外卖平台的Servlet,用户点了一个披萨:

  1. 用户访问你的Servlet(/order),你发现需要计算配送费。
  2. 你通过RequestDispatcher找到"配送费计算器"组件(/calculator),并调用forward()将请求转交给它。
  3. "配送费计算器"计算完成后,把结果返回给用户,但用户全程看到的URL还是/order,以为是同一个页面在处理。

关键点总结
  • RequestDispatcher:是服务器内部的"调度员",负责传递请求。
  • forward()服务器端跳转,共享请求和响应对象,地址栏不变。
  • 适用场景:需要多个组件协作处理请求,且希望用户感觉是"一个页面"在工作(比如表单处理后跳转到结果页)。

代码示例演示:

AServlet类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取系统当前时间
        Date nowTime = new Date();

        // 将系统当前时间绑定到请求域当中
        request.setAttribute("sysTime", nowTime);


        // 一行代码搞定转发。
        request.getRequestDispatcher("/b").forward(request, response);

      

    }
}

BServlet类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class BServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 从请求域当中取出绑定的数据
        Object sysTime = request.getAttribute("sysTime");

        //输出到浏览器
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.print("系统当前时间是:" + sysTime);
    }
}

8.两个Servlet怎么共享数据?

  • 将数据放到ServletContext应用域当中,当然是可以的,但是应用域范围太大,占用资源太多。不建议使用。

  • 可以将数据放到request域当中,然后AServlet转发到BServlet,保证AServlet和BServlet在同一次请求当中,这样就可以做到两个Servlet,或者多个Servlet共享同一份数据。

9.转发的下一个资源必须是一个Servlet吗?

  • 不一定,只要是Tomcat服务器当中的合法资源,都是可以转发的。例如:html....

  • 注意:转发的时候,路径的写法要注意,转发的路径以"/"开始,不加项目名。

AServlet类

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取系统当前时间
        Date nowTime = new Date();

        // 将系统当前时间绑定到请求域当中
        request.setAttribute("sysTime", nowTime);


        // 转发到一个Servlet,也可以转发到一个HTML,只要是WEB容器当中的合法资源即可。
       request.getRequestDispatcher("/test.html").forward(request, response);

    }
}

test.html

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test html</title>
</head>
<body>
<h1>test html page</h1>
</body>
</html>

10.关于request对象中两个非常容易混淆的方法:

11.HttpServletRequest接口的其他常用方法:

复制代码
package oop1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class RequestTestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {


        // 获取客户端的IP地址
        String remoteAddr = request.getRemoteAddr();
        System.out.println("客户端的IP地址:" + remoteAddr);

        // 这个方法使用比较多。(动态获取应用的根路径。)
        String contextPath = request.getContextPath();
        System.out.println("应用的根路径:" + contextPath);

        // 获取请求方式
        String method = request.getMethod();
        System.out.println(method); // GET

        // 获取请求的URI
        String requestURI = request.getRequestURI();
        System.out.println(requestURI); // /aaa/testRequest

        // 获取servlet路径
        String servletPath = request.getServletPath();
        System.out.println(servletPath);
}

}

二、使用纯Servlet做一个单表的CRUD操作

1.使用纯粹的Servlet完成单表【对部门的】的增删改查操作。(B/S结构的。)

实现步骤

  • 第一步:准备一张数据库表。(sql脚本)

    部门表

    drop table if exists dept;
    create table dept(
    deptno int primary key,
    dname varchar(255),
    loc varchar(255)
    );
    insert into dept(deptno, dname, loc) values(10, 'XiaoShouBu', 'BEIJING');
    insert into dept(deptno, dname, loc) values(20, 'YanFaBu', 'SHANGHAI');
    insert into dept(deptno, dname, loc) values(30, 'JiShuBu', 'GUANGZHOU');
    insert into dept(deptno, dname, loc) values(40, 'MeiTiBu', 'SHENZHEN');
    commit;
    select * from dept;

  • 第二步:准备一套HTML页面(项目原型)【前端开发工具使用HBuilder】

    • 把HTML页面准备好

    • 然后将HTML页面中的链接都能够跑通。(页面流转没问题。)

    • 应该设计哪些页面呢?

      • 欢迎页面:index.html

      • 列表页面:list.html(以列表页面为核心,展开其他操作。)

      • 新增页面:add.html

      • 修改页面:edit.html

      • 详情页面:detail.html

列表页面list.html

复制代码
<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>部门列表页面</title>
    <meta name="viewport"
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <style>
        table {
            border-collapse: collapse;
            width: 80%;
            margin: 0 auto;
        }

        th,
        td {
            border: 1px solid #ccc;
            padding: 8px;
            text-align: center;
        }
    </style>
</head>

<body>
    <h1>部门列表</h1>
    <hr />
    <table>
        <tr>
            <th>序号</th>
            <th>部门编号</th>
            <th>部门名称</th>
            <th>操作</th>
        </tr>
        <tr>
            <td>1</td>
            <td>10</td>
            <td>销售部</td>
            <td>
                <a href="">删除</a>
                <a href="edit.html">修改</a>
                <a href="detail.html">详情</a>
            </td>
        </tr>
        <tr>
            <td>2</td>
            <td>20</td>
            <td>研发部</td>
            <td>
                <a href="">删除</a>
                <a href="edit.html">修改</a>
                <a href="detail.html">详情</a>
            </td>
        </tr>
        <tr>
            <td>3</td>
            <td>30</td>
            <td>运营部</td>
            <td>
                <a href="">删除</a>
                <a href="edit.html">修改</a>
                <a href="detail.html">详情</a>
            </td>
        </tr>
    </table>
    <hr />
    <a href="add.html">新增部门</a>
</body>

</html>

欢迎页面:index.html

复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>欢迎使用oa系统</title>
</head>
<body>
    <a href="list.html">查看列表详情</a>
</body>
</html>
  • 新增页面:add.html

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>新增部门</title> </head> <body>

    新增部门


    <form action="list.html" method="get"> 部门编号
    部门名称
    部门位置

    </form> </body> </html>
  • 修改页面:edit.html

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>修改部门</title> </head> <body>

    修改部门


    <form action="list.html" method="get"> 部门编号
    部门名称
    部门位置

    </form> </body> </html>
  • 详情页面:detail.html

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>部门详情</title> </head> <body>

    部门详情


    部门编号:20
    部门名称:销售部
    部门位置:北京
    </body> </html>
  • 第三步:分析我们这个系统包括哪些功能?

    • 什么叫做一个功能呢?

      • 只要 这个操作连接了数据库,就表示一个独立的功能。
    • 包括哪些功能?

      • 查看部门列表

      • 新增部门

      • 删除部门

      • 查看部门详细信息

      • 跳转到修改页面

      • 修改部门

  • 第四步:在IDEA当中搭建开发环境

    • 创建一个webapp(给这个webapp添加servlet-api.jar和jsp-api.jar到classpath当中。)

    • 向webapp中添加连接数据库的jar包(mysql驱动)

      • 必须在WEB-INF目录下新建lib目录,然后将mysql的驱动jar包拷贝到这个lib目录下。这个目录名必须叫做lib,全部小写的。
    • JDBC的工具类

jdbc.properties

复制代码
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc
user=root
password=abc123

DBUtil工具类

复制代码
package utils;

import java.sql.*;
import java.util.ResourceBundle;

/**
 * JDBC的工具类
 */
public class DBUtil {

    // 静态变量:在类加载时执行。
    // 并且是有顺序的。自上而下的顺序。
    // 属性资源文件绑定
    private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc");
    // 根据属性配置文件key获取value
    private static String driver = bundle.getString("driver");
    private static String url = bundle.getString("url");
    private static String user = bundle.getString("user");
    private static String password = bundle.getString("password");

    static {
        // 注册驱动(注册驱动只需要注册一次,放在静态代码块当中。DBUtil类加载的时候执行。)
        try {
            // "com.mysql.jdbc.Driver" 是连接数据库的驱动,不能写死。因为以后可能还会连接Oracle数据库。
            // 如果连接oracle数据库的时候,还需要修改java代码,显然违背了OCP开闭原则。
            // OCP开闭原则:对扩展开放,对修改关闭。(什么是符合OCP呢?在进行功能扩展的时候,不需要修改java源代码。)
            //Class.forName("com.mysql.jdbc.Driver");

            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接对象
     * @return conn 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        // 获取连接
        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }

    /**
     * 释放资源
     * @param conn 连接对象
     * @param ps 数据库操作对象
     * @param rs 结果集对象
     */
    public static void close(Connection conn, Statement ps, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
    • 将所有HTML页面拷贝到web目录下。
  • 第五步:实现第一个功能:查看部门列表

    • 我们应该怎么去实现一个功能呢?

      • 建议:你可以从后端往前端一步一步写。也可以从前端一步一步往后端写。都可以。但是千万要记住不要想起来什么写什么。你写代码的过程最好是程序的执行过程。也就是说:程序执行到哪里,你就写哪里。这样一个顺序流下来之后,基本上不会出现什么错误、意外。

2.从哪里开始?

      • 假设从前端开始,那么一定是从用户点击按钮那里开始的。
  • 第一:先修改前端页面的超链接,因为用户先点击的就是这个超链接。

    查看部门列表

第二:编写web.xml文件

第三:编写DeptListServlet类继承HttpServlet类。然后重写doGet方法。

复制代码
package com.bjpowernode.oa.web.action;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class DeptListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
}

第四:在DeptListServlet类的doGet方法中连接数据库,查询所有的部门,动态的展示部门列表页面.

  • 分析list.html页面中哪部分是固定死的,哪部分是需要动态展示的。

  • list.html页面中的内容所有的双引号要替换成单引号,因为out.print("")这里有一个双引号,容易冲突。

  • 现在写完这个功能之后,你会有一种感觉,感觉开发很繁琐,只使用servlet写代码太繁琐了。

    while(rs.next()){
    String deptno = rs.getString("a");
    String dname = rs.getString("dname");
    String loc = rs.getString("loc");

    复制代码
      out.print("			<tr>");
      out.print("				<td>"+(++i)+"</td>");
      out.print("				<td>"+deptno+"</td>");
      out.print("				<td>"+dname+"</td>");
      out.print("				<td>");
      out.print("					<a href=''>删除</a>");
      out.print("					<a href='edit.html'>修改</a>");
      out.print("					<a href='detail.html'>详情</a>");
      out.print("				</td>");
      out.print("			</tr>");

    }

第五步:查看部门详情。

  • 建议:从前端往后端一步一步实现。首先要考虑的是,用户点击的是什么?用户点击的东西在哪里?

    • 一定要先找到用户点的"详情"在哪里。找了半天,终于在后端的java程序中找到了

      详情

    • 详情 是需要连接数据库的,所以这个超链接点击之后也是需要执行一段java代码的。所以要将这个超链接的路径修改一下。

    • 注意:修改路径之后,这个路径是需要加项目名的。"/oa/dept/detail"

  • 技巧:

    out.print("详情");

  • 重点:向服务器提交数据的格式:uri?name=value&name=value&name=value&name=value

  • 这里的问号,必须是英文的问号。不能中文的问号。

解决404的问题。写web.xml文件。

复制代码
<servlet>
    <servlet-name>detail</servlet-name>
    <servlet-class>oop1.web.action.DeptDetailServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>detail</servlet-name>
    <url-pattern>/dept/detail</url-pattern>
</servlet-mapping>

编写一个类:DeptDetailServlet继承HttpServlet,重写doGet方法。

    • 在doGet方法当中:连接数据库,根据部门编号查询该部门的信息。动态展示部门详情页。
  • 第六步:删除部门

    • 怎么开始?从哪里开始?从前端页面开始,用户点击删除按钮的时候,应该提示用户是否删除。因为删除这个动作是比较危险的。任何系统在进行删除操作之前,是必须要提示用户的,因为这个删除的动作有可能是用户误操作。(在前端页面上写JS代码,来提示用户是否删除。)

      删除

      <script type="text/javascript"> function del(dno){ if(window.confirm("亲,删了不可恢复哦!")){ document.location.href = "/oa/dept/delete?deptno=" + dno; } } </script>
  • 以上的前端程序要写到后端的java代码当中:

    • DeptListServlet类的doGet方法当中,使用out.print()方法,将以上的前端代码输出到浏览器上。
  • 解决404的问题:

    • http://localhost:8080/oa/dept/delete?deptno=30

    • web.xml文件

      <servlet> <servlet-name>delete</servlet-name> <servlet-class>oop1.web.action.DeptDelServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>delete</servlet-name> <url-pattern>/dept/delete</url-pattern> </servlet-mapping>
  • 编写DeptDelServlet继承HttpServlet,重写doGet方法。

删除成功或者失败的时候的一个处理(这里我们选择了转发,并没有使用重定向机制。)

  • 第七步:新增部门

    • 注意:最后保存成功之后,转发到 /dept/list 的时候,会出现405,为什么?

      • 第一:保存用的是post请求。底层要执行doPost方法。

      • 第二:转发是一次请求,之前是post,之后还是post,因为它是一次请求。

      • 第三:/dept/list Servlet当中只有一个doGet方法。

      • 怎么解决?两种方案

        • 第一种:在/dept/list Servlet中添加doPost方法,然后在doPost方法中调用doGet。

        • 第二种:重定向。

  • 第八步:跳转到修改部门的页面

  • 第九步:修改部门

3.全部代码示例:

DBUtil、jdbc.properties上面有了

⑴ DeptListServlet类

复制代码
package oop1.web.action;


import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oop1.utils.DBUtil;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DeptListServlet extends HttpServlet {

    // 处理post请求
    /*@Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }*/

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {



        //根据数据库动态输出内容
        // 获取应用的根路径
        String contextPath = request.getContextPath();

        // 设置响应的内容类型以及字符集。防止中文乱码问题。
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("	<head>");
        out.print("		<meta charset='utf-8'>");
        out.print("		<title>部门列表页面</title>");

        out.print("<script type='text/javascript'>");
        out.print("    function del(dno){");
        out.print("        if(window.confirm('亲,删了不可恢复哦!')){");
        out.print("            document.location.href = '"+contextPath+"/dept/delete?deptno=' + dno");
        out.print("        }");
        out.print("    }");
        out.print("</script>");

        out.print("	</head>");
        out.print("	<body>");
        out.print("		<h1 align='center'>部门列表</h1>");
        out.print("		<hr >");
        out.print("		<table border='1px' align='center' width='50%'>");
        out.print("			<tr>");
        out.print("				<th>序号</th>");
        out.print("				<th>部门编号</th>");
        out.print("				<th>部门名称</th>");
        out.print("				<th>操作</th>");
        out.print("			</tr>");
        /*上面一部分是死的*/

        // 连接数据库,查询所有的部门
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 获取连接
            conn = DBUtil.getConnection();
            // 获取预编译的数据库操作对象
            String sql = "select deptno as a,dname,loc from dept";
            ps = conn.prepareStatement(sql);
            // 执行SQL语句
            rs = ps.executeQuery();
            // 处理结果集
            int i = 0;
            while(rs.next()){
                String deptno = rs.getString("a");
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");

                out.print("			<tr>");
                out.print("				<td>"+(++i)+"</td>");
                out.print("				<td>"+deptno+"</td>");
                out.print("				<td>"+dname+"</td>");
                out.print("				<td>");
                out.print("					<a href='javascript:void(0)' onclick='del("+deptno+")'>删除</a>");
                out.print("					<a href='"+contextPath+"/dept/edit?deptno="+deptno+"'>修改</a>");
                out.print("					<a href='"+contextPath+"/dept/detail?fdsafdsas="+deptno+"'>详情</a>");
                out.print("				</td>");
                out.print("			</tr>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            DBUtil.close(conn, ps, rs);
        }

        /*下面一部分是死的*/
        out.print("		</table>");
        out.print("		<hr >");
        out.print("		<a href='"+contextPath+"/add.html'>新增部门</a><br/>");
        out.print(contextPath);

        out.print("	</body>");
        out.print("</html>");
    }
}

⑵DeptDetailServlet类

复制代码
package oop1.web.action;


import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oop1.utils.DBUtil;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DeptDetailServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("	<head>");
        out.print("		<meta charset='utf-8'>");
        out.print("		<title>部门详情</title>");
        out.print("	</head>");
        out.print("	<body>");
        out.print("		<h1>部门详情</h1>");
        out.print("		<hr >");

        // 获取部门编号
        // /oa/dept/detail?fdsafdsas=30
        // 虽然是提交的30,但是服务器获取的是"30"这个字符串。
        String deptno = request.getParameter("fdsafdsas");

        // 连接数据库,根据部门编号查询部门信息。
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "select dname,loc from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            rs = ps.executeQuery();
            // 这个结果集一定只有一条记录。
            if(rs.next()){
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");

                out.print("部门编号:"+deptno+" <br>");
                out.print("部门名称:"+dname+"<br>");
                out.print("部门位置:"+loc+"<br>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, rs);
        }

        out.print("		<input type='button' value='后退' onclick='window.history.back()'/>");
        out.print("	</body>");
        out.print("</html>");
    }
}

⑶ DeptEditServlet 类

复制代码
package oop1.web.action;


import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oop1.utils.DBUtil;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DeptEditServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 获取应用的根路径。
        String contextPath = request.getContextPath();

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("	<head>");
        out.print("		<meta charset='utf-8'>");
        out.print("		<title>修改部门</title>");
        out.print("	</head>");
        out.print("	<body>");
        out.print("		<h1>修改部门</h1>");
        out.print("		<hr >");
        out.print("		<form action='"+contextPath+"/dept/modify' method='post'>");

        // 获取部门编号
        String deptno = request.getParameter("deptno");
        // 连接数据库,根据部门编号查询部门的信息。
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "select dname, loc as location from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            rs = ps.executeQuery();
            // 这个结果集中只有一条记录。
            if(rs.next()){
                String dname = rs.getString("dname");
                String location = rs.getString("location"); // 参数"location"是sql语句查询结果列的列名。
                // 输出动态网页。
                out.print("                部门编号<input type='text' name='deptno' value='"+deptno+"' readonly /><br>");
                out.print("                部门名称<input type='text' name='dname' value='"+dname+"'/><br>");
                out.print("                部门位置<input type='text' name='loc' value='"+location+"'/><br>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, rs);
        }

        out.print("			<input type='submit' value='修改'/><br>");
        out.print("		</form>");
        out.print("	</body>");
        out.print("</html>");
    }
}

⑷DeptDelServlet类

复制代码
package oop1.web.action;


import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oop1.utils.DBUtil;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class DeptDelServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 根据部门编号,删除部门。
        // 获取部门编号
        String deptno = request.getParameter("deptno");
        // 连接数据库删除数据
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            // 开启事务(自动提交机制关闭)
            conn.setAutoCommit(false);
            String sql = "delete from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            // 返回值是:影响了数据库表当中多少条记录。
            count = ps.executeUpdate();
            // 事务提交
            conn.commit();
        } catch (SQLException e) {
            // 遇到异常要回滚
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, null);
        }

        // 判断删除成功了还是失败了。
        if (count == 1) {
            //删除成功
            //仍然跳转到部门列表页面
            //部门列表页面的显示需要执行另一个Servlet。怎么办?转发。
            //request.getRequestDispatcher("/dept/list").forward(request, response);
            response.sendRedirect(request.getContextPath() + "/dept/list");
        }else{
            // 删除失败
            //request.getRequestDispatcher("/error.html").forward(request, response);
            response.sendRedirect(request.getContextPath() + "/error.html");
        }
    }
}

⑸DeptModifyServlet类

复制代码
package oop1.web.action;


import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oop1.utils.DBUtil;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class DeptModifyServlet  extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 解决请求体的中文乱码问题。
        request.setCharacterEncoding("UTF-8");

        // 获取表单中的数据
        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");
        // 连接数据库执行更新语句
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            String sql = "update dept set dname = ?, loc = ? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, dname);
            ps.setString(2, loc);
            ps.setString(3, deptno);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, null);
        }

        if (count == 1) {
            // 更新成功
            // 跳转到部门列表页面(部门列表页面是通过Java程序动态生成的,所以还需要再次执行另一个Servlet)
            //request.getRequestDispatcher("/dept/list").forward(request, response);

            response.sendRedirect(request.getContextPath() + "/dept/list");
        }else{
            // 更新失败
            //request.getRequestDispatcher("/error.html").forward(request, response);
            response.sendRedirect(request.getContextPath() + "/error.html");
        }

    }
}

⑹DeptSaveServlet类

复制代码
package oop1.web.action;


import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import oop1.utils.DBUtil;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class DeptSaveServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取部门的信息
        // 注意乱码问题(Tomcat10不会出现这个问题)
        request.setCharacterEncoding("UTF-8");
        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");

        // 连接数据库执行insert语句
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            String sql = "insert into dept(deptno, dname, loc) values(?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            ps.setString(2, dname);
            ps.setString(3, loc);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, null);
        }

        if (count == 1) {
            // 保存成功跳转到列表页面
            // 转发是一次请求。
            //request.getRequestDispatcher("/dept/list").forward(request, response);

            // 这里最好使用重定向(浏览器会发一次全新的请求。)
            // 浏览器在地址栏上发送请求,这个请求是get请求。
            response.sendRedirect(request.getContextPath() + "/dept/list");

        }else{
            // 保存失败跳转到错误页面
            //request.getRequestDispatcher("/error.html").forward(request, response);

            // 这里也建议使用重定向。
            response.sendRedirect(request.getContextPath() + "/error.html");
        }

    }
}

add.html

复制代码
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>新增部门</title>
	</head>
	<body>
		<h1>新增部门</h1>
		<hr >
		<form action="/oa/dept/save" method="post">
			部门编号<input type="text" name="deptno"/><br>
			部门名称<input type="text" name="dname"/><br>
			部门位置<input type="text" name="loc"/><br>
			<input type="submit" value="保存"/><br>
		</form>
	</body>
</html>

edit.html

复制代码
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>修改部门</title>
	</head>
	<body>
		<h1>修改部门</h1>
		<hr >
		<form action="list.html" method="get">
			部门编号<input type="text" name="deptno" value="20" readonly /><br>
			部门名称<input type="text" name="dname" value="销售部"/><br>
			部门位置<input type="text" name="loc" value="北京"/><br>
			<input type="submit" value="修改"/><br>
		</form>
	</body>
</html>

error.html

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>error</title>
</head>
<body>
<h1>操作失败,<a href="javascript:void(0)" onclick="window.history.back()">返回</a></h1>
</body>
</html>

index.html

复制代码
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>欢迎使用OA系统</title>
	</head>
	<body>
	<!--前端超链接发送请求的时候,请求路径以"/"开始,并且要带着项目名-->
		<a href="/oa/dept/list">查看部门列表</a>
	</body>
</html>

4.如何串起来

以下是用户点击部门详情链接后的具体流程及代码执行步骤:

⑴ 用户点击详情链接

用户在前端页面点击如下链接:

复制代码
<a href="/项目名/dept/detail?fdsafdsas=40">详情</a>
  • 触发操作 :浏览器发送 GET 请求 到 URL /项目名/dept/detail,并携带参数 fdsafdsas=30

⑵ Servlet 匹配与请求处理

  • Servlet 映射 :根据 web.xml 或注解配置,URL /dept/detail 映射到 DeptDetailServlet

  • 调用 doGet 方法

复制代码
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
    // 处理请求逻辑
}

⑶ 获取请求参数

  • 参数提取

    String deptno = request.getParameter("fdsafdsas"); // 获取参数值 "40"

  • 注意:参数名 fdsafdsas 是随意命名的,实际应为 deptno(代码中存在命名问题)。

⑷ 连接数据库查询部门详情

  • 执行 SQL 查询

    Connection conn = DBUtil.getConnection();
    String sql = "select dname, loc from dept where deptno = ?";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setString(1, deptno); // 参数值 "40"
    ResultSet rs = ps.executeQuery();

  • 数据库操作 :查询 deptno=30 的部门名称(dname)和位置(loc)。

⑸动态生成 HTML 响应

  • 若查询到结果

    if (rs.next()) {
    String dname = rs.getString("dname"); // 部门名称
    String loc = rs.getString("loc"); // 部门位置
    out.print("部门编号:" + deptno + "
    ");
    out.print("部门名称:" + dname + "
    ");
    out.print("部门位置:" + loc + "
    ");
    }

输出示例

  • 若未查询到结果

    • 当前代码未处理空结果,页面会显示空白(需改进)。

⑹释放数据库资源

  • 关闭连接

    DBUtil.close(conn, ps, rs); // 释放数据库连接、语句和结果集

⑺总结流程

  1. 用户点击链接 → 2. 发送 GET 请求 → 3. Servlet 处理参数 → 4. 查询数据库 → 5. 生成动态页面 → 6. 返回响应 → 7. 用户查看详情
  • 关键点 :所有逻辑由 DeptDetailServletdoGet() 方法驱动,数据库查询结果直接决定页面内容。

5.为什么写在Doget方法里

在Servlet中,将逻辑写在 doGet() 方法中的原因主要有以下几点:


⑴. HTTP请求方法的语义匹配

  • GET请求的特点

    • 用于获取数据(如加载页面、查询信息)。

    • 对服务器资源是只读操作,不会修改数据。

    • 请求参数通过URL传递(如 /dept/list?page=1)。

  • POST请求的特点

    • 用于提交数据(如表单提交、删除操作)。

    • 可能修改服务器资源。

    • 请求参数通过请求体传递,不可见且更安全。

示例代码分析

  • DeptListServletdoGet() 用于生成部门列表页面(数据展示),符合GET的语义。

  • DeptEditServletdoGet() 用于加载编辑表单页面(展示待修改的数据)。

  • DeptDelServletdoGet() 处理删除操作,这是不安全的(应使用POST或DELETE方法)。


⑵. 前端操作触发的方式

  • 通过超链接或直接访问URL

    例如,点击 <a href="/dept/list">部门列表</a> 会发送GET请求,触发 doGet()

  • 通过表单提交(默认GET)

    如果表单未指定 method="post",提交时会发送GET请求。

代码中的体现

  • 部门列表、编辑页面、详情页面均通过链接访问,因此使用 doGet()

  • 新增部门(DeptSaveServlet)和修改部门(DeptModifyServlet)通过表单提交,使用 doPost()


⑶. 代码结构与规范

  • 职责分离

    • doGet() 处理页面渲染和数据查询。

    • doPost() 处理数据修改(增删改)。

  • 安全性

    • 将敏感操作(如删除)放在 doPost() 中,可减少CSRF攻击风险。

    • 用户提供的代码中,删除操作错误地使用了 doGet()(见 DeptDelServlet),需改进为 doPost()


⑷. 用户实际场景举例

  • 场景1:查看部门列表

    用户访问 /dept/list → 浏览器发送GET请求 → DeptListServletdoGet() 执行 → 返回HTML页面。

  • 场景2:删除部门

    用户点击删除链接 → 触发JavaScript跳转 → 发送GET请求到 /dept/delete?deptno=30DeptDelServletdoGet() 执行 → 删除数据。
    问题:删除操作本应通过POST请求发送,此处设计不安全。


⑸总结

  • 写在 doGet() 中的原因

    • 匹配GET请求的语义(数据展示、无副作用)。

    • 响应前端通过链接或URL直接触发的操作。

  • 需注意的问题

    • 避免在 doGet() 中执行敏感操作(如删除),应改用 doPost() 或其他安全方法。

    • 遵循RESTful设计原则,使代码更规范、安全。

三、在一个web应用中应该如何完成资源的跳转

1.在一个web应用中通过两种方式,可以完成资源的跳转:

  • 第一种方式:转发

  • 第二种方式:重定向

2.转发和重定向有什么区别?

⑴代码上有什么区别?

转发

重定向

⑵形式上有什么区别?

  • 转发和重定向的本质区别?

    • 转发:是由WEB服务器来控制的。A资源跳转到B资源,这个跳转动作是Tomcat服务器内部完成的。

    • 重定向:是浏览器完成的。具体跳转到哪个资源,是浏览器说了算。

  • 使用一个例子去描述这个转发和重定向

    • 借钱(转发:发送了一次请求)

      • 杜老师没钱了,找张三借钱,其实张三没有钱,但是张三够义气,张三自己找李四借了钱,然后张三把这个钱给了杜老师,杜老师不知道这个钱是李四的,杜老师只求了一个人。杜老师以为这个钱就是张三的。
    • 借钱(重定向:发送了两次请求)

      • 杜老师没钱了,找张三借钱,张三没有钱,张三有一个好哥们,叫李四,李四是个富二代,于是张三将李四的家庭住址告诉了杜老师,杜老师按照这个地址去找到李四,然后从李四那里借了钱。显然杜老师在这个过程中,求了两个人。并且杜老师知道最终这个钱是李四借给俺的。


  • 转发和重定向应该如何选择?什么时候使用转发,什么时候使用重定向?

    • 如果在上一个Servlet当中向request域当中绑定了数据,希望从下一个Servlet当中把request域里面的数据取出来,使用转发机制。

    • 剩下所有的请求均使用重定向。(重定向使用较多。)

  • 跳转的下一个资源有没有要求呢?必须是一个Servlet吗?

    • 不一定,跳转的资源只要是服务器内部合法的资源即可。包括:Servlet、JSP、HTML.....
  • 转发会存在浏览器的刷新问题。


相关推荐
雷渊1 分钟前
深入分析Spring的事务隔离级别及实现原理
java·后端·面试
rebel13 分钟前
Java获取excel附件并解析解决方案
java·后端
并不会24 分钟前
多线程案例-单例模式
java·学习·单例模式·单线程·多线程·重要知识
数据攻城小狮子25 分钟前
Java Spring Boot 与前端结合打造图书管理系统:技术剖析与实现
java·前端·spring boot·后端·maven·intellij-idea
m0_5557629026 分钟前
struct 中在c++ 和c中用法区别
java·c语言·c++
HongXuan-Yuan36 分钟前
系统设计:高并发策略与缓存设计
java·分布式·高并发
Alt.939 分钟前
MyBatis基础五(动态SQL,缓存)
java·sql·mybatis
Yang-Never41 分钟前
Open GL ES ->纹理贴图,顶点坐标和纹理坐标组合到同一个顶点缓冲对象中进行解析
android·java·开发语言·android studio·贴图
呦呦鹿鸣Rzh1 小时前
Spring MVC
java·spring·mvc
计算机程序设计开发1 小时前
宠物医院管理系统基于Spring Boot SSM
java·spring boot·后端·毕业设计·计算机毕业设计