JavaEE开发-第一节 Servlet与JSP
Servlet基本使用
- Servlet是Server Applet的简称,译为“小型的服务程序”,用于响应客户端的请求
- 一般的使用要素
- 继承java.servlet.http.HttpServlet,实现doGet、doPost或者service方法
- 通过request对象获取客户端的请求数据:
request.getParameter()
- 通过response对象给客户端返回响应:
response.getWriter().write()
- 通过注解@WebServlet设置Servlet对应的请求路径
- 乱码问题解决
-
客户端的请求数据乱码
//按照UTF8编码解析客户达发送过来的数据 request.setCharacterEncoding("UTF-8");
-
服务器的响应数据乱码
//设置响应数据的编码 response.setContentType("text/plain;charset=UTF-8");
- 其中
text/plain
是数据的MINEType,根据实际情况而定 - 更多的MINEType可以参考Tomcat安装目录下的conf文件夹下/web.xml文件中可以查看
- 其中
-
- 一些细节
- 通过response拿到的输出流对象(比如getWriter),不需要程序员调用close去关闭
- 在语法基础IO流中讲过,流一般创建了,需要用close销毁,这里是获取,不是创建所以不用销毁
- 默认情况下,一个Servlet类,只会被服务器创建一个实例对象,而且是在第一次处理客户端请求才创建实例
- 注意:Servlet并没有设计成单例模式!!!
- 建议:不要在Servlet中定义可写(writable)的成员变量,会引发线程安全问题
- HTTP请求的默认端口号是80,所以,如果Tomcat服务器的端口号设置为80,那么
- localhost/path等价于localhost:80/path(仅仅是HTTP请求才会自动加上80)
- 也就是说如果Tomcat服务器的端口号设置的不是80,那么HTTP请求的时候就必须是
域名(IP地址):+端口号
进行访问 - 注意:这里的80不等于8080
- HTTPS请求的默认添加端口号是443
- 通过response拿到的输出流对象(比如getWriter),不需要程序员调用close去关闭
-
代码举例:
//TestServlet类 package com.example.HelloWorld; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; //设置请求路径 @WebServlet("/login") //设置多个请求路径 //@WebServlet({"/login","/test"}) public class TestServlet extends HttpServlet { //可以从构造方法中看出,n个请求发送过来,TestServlet对象仅仅被创建一次 public TestServlet(){ //java中打印一个对象时,输出:类名@哈希值,每个对象都有唯一的哈希值,如果2个对象打印的值一样,说明2个对象是同一个对象 System.out.println(this + "server"); } /** * 客户端发送一个http请求,首先会调用这个service方法 * */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //父类的service方法内部已经做好了分发,会判断当前客户端的请求是get还是post,然后分别调用doGet、doPost super.service(req, resp); System.out.println("------service-----------"); //java中打印一个对象时,输出:类名@哈希值,每个对象都有唯一的哈希值,如果2个对象打印的值一样,说明2个对象是同一个对象 System.out.println(this + "server"); } //处理get请求 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("------doget-----------"); //统一函数处理 doPost(req,resp); } //处理post请求 /** * HttpServletRequest req: 请求,用来获取客户端发送的数据 * HttpServletResponse resp:响应,用来给客户端返回数据 * */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("-------dopost---------"); //获取客户端发送的数据(请求参数) //按照UTF8编码解析客户达发送过来的数据 req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println(username + "---" + password); //设置响应数据的MINE Type和数据编码 resp.setContentType("text/plain;charset=UTF-8"); //2. 判断 if("123".equals(username) && "456".equals(password)){ System.out.println("登录成功"); resp.getWriter().write("Login Success!"); }else { System.out.println("登录失败"); resp.getWriter().write("Login Error!"); } } }
用Servlet模拟一个前后端项目
-
web文件下新建一个login.html、login.css2个文件,login.html代码如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CRM-登录</title> </head> <body> <!-- context path --> <!--<form action="/crm/test.jsp" method="post">--> <form action="/crm/login" method="post"> <div>用户名 <input type="text" name="username"></div> <div>密码 <input type="text" name="password"></div> <div><button type="submit">登录</button></div> </form> </body> </html>
-
java文件夹下,com.zh.crm包名下
- 新建一个bean文件夹,用于存放数据的模型,新建一个Customer模型类
- 新建一个servlet,用来存放HttpServlet的子类对象,新建一个LoginServlet类
-
LoginServlet代码如下:
package com.zh.crm.servlet; import com.zh.crm.bean.Customer; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("------doget-----------"); //统一函数处理 doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("-------dopost---------"); //获取客户端发送的数据(请求参数) //按照UTF8编码解析客户达发送过来的数据 request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); System.out.println(username + "---" + password); //设置响应数据的MINE Type和数据编码 response.setContentType("text/html;charset=UTF-8"); // 4.拿到输出流 PrintWriter out = response.getWriter(); // 5.判断 if ("123".equals(username) && "123".equals(password)) { success(out); } else { failed(out); } } //获取客户,模拟数据库 private List<Customer> getCustomers() { List<Customer> customers = new ArrayList<>(); for (int i = 0; i< 5; i++) { customers.add(new Customer("张三" + i, "432423" + i, ((i & 1) == 1) ? "男" : "女")); } return customers; } //手动拼接字符串 private void success(PrintWriter out) { out.write("<html>"); out.write("<head>"); //引用css文件 out.write("<link rel=\"stylesheet\" href=\"http://localhost:8080/crm/login.css\">"); out.write("</head>"); out.write("<body>"); out.write("<h1 style=\"color: blue; border: 1px solid black;\">登录成功</h1>"); out.write("<table>"); out.write("<thead>"); out.write("<tr>"); out.write("<th>姓名</th>"); out.write("<th>电话</th>"); out.write("<th>性别</th>"); out.write("</tr>"); out.write("</thead>"); out.write("<tbody>"); //获取客户信息,拼接标签 List<Customer> customers = getCustomers(); for (Customer customer : customers) { out.write("<tr>"); out.write("<td>" + customer.getName() + "</td>"); out.write("<td>" + customer.getPhone() + "</td>"); out.write("<td>" + customer.getSex() + "</td>"); out.write("</tr>"); } out.write("</tbody>"); out.write("</table>"); out.write("</body>"); out.write("</html>"); } private void failed(PrintWriter out) { out.write("<h1 style=\"color: red; border: 1px solid black;\">登录失败</h1>"); out.write("<ul>"); out.write("<a href=\"http://localhost:8080/crm/login.html\">重新登录</a>"); out.write("</ul>"); } }
-
Customer代码如下:
package com.zh.crm.bean; //客户 public class Customer { private String name; private String phone; private String sex; public Customer() {} public Customer(String name, String phone, String sex) { this.name = name; this.phone = phone; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } // Alt + Insert }
问题总结
- 登录之后显是的网页是用后端代码一句一句拼接的,比较繁琐
- 那么有没有简单的方法呢? —–JSP
JSP基本使用
- JSP是JavaServer Pages的简称,是一种动态网页技术标准
-
指令
<%@ page%>: 配置当前页面信息 <%@ include%>: 包含其他页面 <%@ taglib%>: 导入标签库
-
输出
<%= 需要输出的内容%> 等价于out.print(需要输出的内容) response.getWriter().print(需要输出的内容)
-
嵌入Java代码
<%Java代码%>
-
注释
<%--注释内容--%> HTML/CSS/JS注释照常使用
-
声明
<%!声明成员变量、方法%>
JSP实现前后端项目
- 在web文件夹下新建一个test.jsp
- 将login.html的form表单的aciton修改成:
action="/crm/test.jsp"
-
test.jsp 代码如下
<%@ page import="java.util.ArrayList" %> <%@ page import="java.util.List" %> <%@ page import="com.zh.crm.bean.Customer" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <meta charset="UTF-8"> <title>CRM-登录</title> <link rel="stylesheet" href="http://localhost:8080/crm/login.css"> </head> <body> <%-- jsp就是一个server类,就是一个继承自HttpServlet类的子类 这里面可以定义成员变量、方法 可以写java代码,java代码就是相当于在doGet、doPost的方法中写代码 jsp中的所有标签都会通过response.getWriter().write()方法,从上到下响应给客户端 --%> <%! //定义成员变量, //private int other = 10; //定义成员方法,模拟数据 private List<Customer> getCustomers() { List<Customer> customers = new ArrayList<>(); for (int i = 0; i< 5; i++) { customers.add(new Customer("张三" + i, "432423" + i, ((i & 1) == 1) ? "男" : "女")); } return customers; } %> <% //这个就相当于在doGet、doPost的方法中写代码 // 1.设置请求数据的编码 request.setCharacterEncoding("UTF-8"); // 2.获取请求参数 String username = request.getParameter("username"); String password = request.getParameter("password"); // 3.先设置响应的内容类型(MIMEType + 数据编码) response.setContentType("text/html;charset=UTF-8"); // 4.拿到输出流 // 5.判断,成功 if ("123".equals(username) && "123".equals(password)) { %> <h1 style="color: blue; border: 1px solid black;">登录成功</h1> <table> <thead> <tr> <th>姓名</th> <th>电话</th> <th>性别</th> </tr> </thead> <tbody> <% List<Customer> customers = getCustomers(); for (Customer customer : customers) { %> <tr> <td><%= customer.getName()%></td> <td><%= customer.getPhone()%></td> <td><%= customer.getSex()%></td> </tr> <% } %> </tbody> </table> <% }else { %> <h1 style="color: #ff0000; border: 1px solid #000000;">登录失败</h1> <ul><a href="http://localhost:8080/crm/login.html">重新登录</a></ul> <% } %> </body> </html>
- 总结
- jsp本质就是一个servlet,就是一个继承自HttpServlet类的子类,就相当于我们手动写的LoginServlet类
- 这里面可以定义成员变量、方法
- 可以写java代码,java代码就是相当于在service的方法中写代码
- jsp中的所有非java代码(H5标签)本质都是通过response.getWriter().write()方法,从上到下响应给客户端
- 查看编译后的代码
- 运行项目后,查看控制台,点击Tomcat Catalina Log
-
找到下面一句:
CATALINA_BASE:[/Users/mac/Library/Caches/JetBrains/IntelliJIdea2020.3/tomcat/c8454ae9-56f7-49f4-af61-eaf70a2a9796]
- 然后进入到该目录下,在
/work/Catalina/localhost/crm/org/apache/jsp/
文件夹下可以看到test_jsp.java
文件 - 打开开文件,可以看到
- test_jsp这个类继承HttpJspBase
-
双击shift搜索HttpJspBase,点击tomcat源码进入HttpJspBase查看
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {}
- HttpJspBase继承自HttpServlet
- jsp所有的代码都是在_jspService方法中执行
- 所有的标签都是通过out.write()响应给客户端
总结
- 尽管JSP简化了java原生拼接标签字符串的麻烦,但是一个jsp文件也有明显的缺点.
- Java代码和HTML代码混合在一起,导致可读性差、难以维护
- 整个JSP文件基本得由Java后台工程师来完成,前端工程师根本无法维护
- 在很久以前,有些公司是需要前端后台一起开发JSP。现在比较流行前后端分离
- 从上面看出,对于有web的项目单独使用Servlet,单独使用JSP都是不行的,那么,可不可以这样呢? Java代码部分用Servlet,HTML部分用JSP呢?
EL表达式、JSTL标签库
-
EL是Expression Launguage的简称
${obj.property}、${obj["property"]}、${obj[propertyVar]} empty、not empty
- JSTL是JSP Standard Tag Library的简称,译为”JSP标准标签库“,由Apache的Jakarta小组维护
- EL表达式、JSTL标签库都可以简化JSP代码
下载和使用JSTL核心标签库
- 下载地址:http://tomcat.apache.org/download-taglibs.cgi
- 如果使用其核心标签库,只需要下载2个jar包即可
taglibs-standard-impl-1.2.5.jar、taglibs-standard-spec-1.2.5.jar
- 如果使用其核心标签库,只需要下载2个jar包即可
- IDEA如何手动导入第三方jar包(javaEE项目):
- 在WEB-INF文件夹下新建一个lib文件夹
- 将上面2个jar包复制到该目录下
- 右击选择”Add das Library”->Level选择Module Library,点击OK
-
在jsp文件中,使用taglib指令导入JSTL核心标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-
常用标签
<c:if test="条件"> <c:for Each items="集合" var="元素" varStatus="循环相关的信息"> <c:choose>、<c:when test="条件">、<c:oth erwise>
- 代码举例
-
将LoginServlet中得doPost代码改造如下
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("-------dopost---------"); //获取客户端发送的数据(请求参数) //按照UTF8编码解析客户达发送过来的数据 request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); System.out.println(username + "---" + password); //设置响应数据的MINE Type和数据编码 response.setContentType("text/html;charset=UTF-8"); // 5.判断 if ("123".equals(username) && "123".equals(password)) { //获取客户信息,拼接标签 List<Customer> customers = getCustomers(); // 将客户数据存储到request中 request.setAttribute("customers", customers); //request.getAttribute("customers"); // 转发到login.jsp页面进行数据展示,即转发给另外一个servlet(jsp的本质是servlet) request.getRequestDispatcher("/login.jsp").forward(request, response); //equestDispatcher dispatcher = request.getRequestDispatcher("/page/list.jsp"); //dispatcher.forward(request, response); } }
-
在web文件夹下新建一个login.jsp(要导入标签库,步骤见2、3)
<%--本质就是response.setContentType("text/html;charset=UTF-8");--%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <meta charset="UTF-8"> <title>CRM-登录</title> <link rel="stylesheet" href="http://localhost:8080/crm/login.css"> </head> <body> <h1 style="color: blue; border: 1px solid black;">登录成功</h1> <table> <thead> <tr> <th>姓名</th> <th>电话</th> <th>性别</th> </tr> </thead> <tbody> <%-- forEach中的 ${customers} :是EL表达式,里面存放的是key值,会到request中取value 等价于:request.getAttribute("customers"); --%> <c:forEach items="${customers}" var="customer" varStatus="s"> <tr> <%-- customer.name:这里的name不是成员变量,而是属性名 拿到属性名,会自动去找该属性名的get方法,本质是customer.getName() s:可以获取遍历中的索引,s.index/s.begin 等等 --%> <%-- <td>${s.index}</td> --%> <td>${customer.name}</td> <td>${customer.phone}</td> <td>${customer.sex}</td> </tr> </c:forEach> </tbody> </table> </body> </html>
-
流程分析
-
form表格,点击登录,发送post请求,调用LoginServlet的doPost
//login.html核心代码 <form action="/crm/login2" method="post"> <div>用户名 <input type="text" name="username"></div> <div>密码 <input type="text" name="password"></div> <div><button type="submit">登录</button></div> </form>
- LoginServlet获取用户数据,将数据转发给jsp(另一个Servlet,jsp的本质是servlet)
- jsp将结果响应给客户端
-
-
Servlet+JSP处理请求的常见过程
- 本质就是一个MVC的开发模式
- C(Controller):控制器(Servlet)
- M(Model):数据
- V(View):页面展示(JSP)
转发(forward)
- 在同一个Context(项目)中进行请求转发
- 一个请求只能在同一个项目的Servlet(JSP)之间转发
转发链条(重点,注意理解!!!)
- 在同一次请求中,可以转发多次,形成一个转发链条。在一个转发链条上
- 可以通过request.setAttribute、request.getAttribute来共享数据
- 每一次转发都会创建一个新的request对象,用成员变量request指向前一个request对象
- 每一个新的request对象,都有一个成员变量request,这个request指向前request一个对象
- 在转发链条上,所有的attribute都存储在头部的request对象中
- 这就是为什么转发链条中的所有request对象获取的attribute值都是一样的原因,因为链条中所有通过attribute存储的数据,都存放在第一个request中
- 代码举例:
-
新建3个Servelt
//Test1Servlet @WebServlet("/test1") public class Test1Servlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // RequestFacade //设置数据 request.setAttribute("name", "jack"); System.out.println("Test1Servlet_" + request); //下句打断点,断点1 //转发到Test2Servlet request.getRequestDispatcher("/test2").forward(request, response); } } //Test2Servlet @WebServlet("/test2") public class Test2Servlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Test2Servlet_" + request); //第二个断点 //转发到Test3Servlet request.getRequestDispatcher("/test3").forward(request, response); } } //Test3Servlet @WebServlet("/test3") public class Test3Servlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //第三个断点处 System.out.println("Test3Servlet_" + request); } }
- 然后浏览器输入:
http://localhost:8080/crm/test1
,点击回车 -
打印结果
Test1Servlet_org.apache.catalina.connector.RequestFacade@6a18878a Test2Servlet_org.apache.catalina.core.ApplicationHttpRequest@57075213 Test3Servlet_org.apache.catalina.core.ApplicationHttpRequest@a178cde
- 可以看到3个request对象不是同一个
-
打断点,可以看到下面的转发链条
-
重定向(redirect)
-
重定向:服务器通知客户端重新发送请求到新的任意URL地址
- 客户端发送网络请求到Context1,Context1响应状态码302,并设置响应头Location字段中的重定向的路径或者url,这里假设路径是Context2的路径
- 客户端发现服务器返回的状态码是302,则会到响应头的Location字段中获取新的地址
- 拿到新的地址向新地址发送服务请求到Context2,Context2返回200和响应数据
- 注意:302表达的意思就是告诉客户端,需要重定向
- 重新向可以到任意Context之间
转发vs重定向
- 转发代码:
request.get RequestDispatcher("/路径").forward(request,response)
- 只能转发到同一个Context(项目), 路径中不用包含ContextPath(项目名称路径)
- 客户端只发了一次请求
- 浏览器地址栏的URL不会发生变化
- 转发的操作只由服务器完成
- 重定向代码:
response.sendRedirect("/路径")
- 可以重定向到任意URL, 如果重定向到同一个Context下, 路径中需要包含ContextPath
- 客户端发了两次请求
- 浏览器地址栏的URL会发生变化
- 重定向的操作由服务器+客户端配合完成
- 使用场景:
- 转发:在一个servlet中从数据库中读取到数据,然后转发给另外一个servlet(JSP)进行数据展示
- 重定向:在一个servlet中向数据库中写入完数据,然后让看客户端重定向到某个列表页面展示数据库更新的数据。
代码举例(用户列表维护)
- 需求:
- 第一个页面是用户列表,顶部有个添加按钮
- 点击添加进入添加页面,输入用户信息,点击保存重定向到用户列表页面
- 监听客户端进入用户列表页面请求ListServlet:
http://localhost:8080/crm/list
-
ListServlet
package com.zh.crm.servlet; import com.zh.crm.Data; import com.zh.crm.bean.Customer; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; import java.util.List; @WebServlet("/list") public class ListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取客户数据 List<Customer> customers = Data.getCustomers(); // 将客户数据存储到request中 request.setAttribute("customers", customers); request.getAttribute("customers"); // 转发到list.jsp页面进行数据展示 request.getRequestDispatcher("/page/list.jsp").forward(request, response); } }
-
list.jsp
<%--本质就是response.setContentType("text/html;charset=UTF-8");--%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>客户列表</title> <style> th, td { border: 1px solid black; } </style> </head> <body> <a href="page/add.html">添加</a> <table> <thead> <tr> <th>姓名</th> <th>年龄</th> <th>身高</th> </tr> </thead> <tbody> <%-- forEach中的 ${customers} :是EL表达式,里面存放的是key值,回到request中取value 等价于:request.getAttribute("customers"); --%> <c:forEach items="${customers}" var="customer" varStatus="s"> <tr> <td>${customer.name}</td> <td>${customer.age}</td> <td>${customer.height}</td> </tr> </c:forEach> </tbody> </table> </body> </html>
-
-
添加用户信息界面add.html
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>添加客户</title> </head> <body> <form action="/crm/save" method="post"> <div>姓名 <input type="text" name="name"></div> <div>年龄 <input type="text" name="age"></div> <div>身高 <input type="text" name="height"></div> <div><button type="submit">保存</button></div> </form> </body> </html>
-
监听form表单的post请求SaveServlet
package com.zh.crm.servlet; import com.zh.crm.Data; import com.zh.crm.bean.Customer; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/save") public class SaveServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 获取请求参数 String name = request.getParameter("name"); String age = request.getParameter("age"); String height = request.getParameter("height"); // 转成Java Bean对象 Customer customer = new Customer(); customer.setName(name); customer.setAge(Integer.valueOf(age)); customer.setHeight(Double.valueOf(height)); Data.add(customer); // 重定向 //重定向是告诉客户端,要重新发送一个url请求到服务器,相当于再次在浏览器地址框中输入了:http://localhost:8080/crm/list,并回车 // 状态码:302,告诉客户端,需要重定向,然后客户端到响应头的Location字段中查找要重定向的新路径 // response.setStatus(302); // 响应头Location:/crm/list // response.setHeader("Location", "/crm/list"); //转发可以在不同项目之间,所以路径必须带上项目名/crm //sendRedirect方法就是上面2句的本质 response.sendRedirect("/crm/list"); //转发的话,浏览器的访问地址栏不会改变,客户端没有重新请求,而是服务端Servlet之间的转发 //客户端并不知道服务端发生了什么事情 //转发只能在同一个项目的Servlet(JSP)之间,因此路径不需要带上项目名称crm // request.getRequestDispatcher("/list").forward(request, response); } }