1.Servlet/JSP单独使用的弊端
当我们用Servlet或者JSP单独处理请求的时候
- Servlet:拼接大量的html字符串 造成可读性差、难以维护
- JSP:使得html和Java代码互相交织 也造成了可读性差、难以维护的后果
最合适的做法就是两者结合使用
2.Servlet+JSP处理请求的常见过程
- 以上过程就是典型的MVC开发模式 即Model(数据)-View(页面展示)-Controller(控制器)模式 分别对应着上图中的数据库-JSP-Servlet
3.实现
- 有了前面这层铺垫 再加上Servlet_JSP中介绍的EL和JSTL 那么我们就可以开始实现以下我们的crm项目了 即customer relationship management(客户关系管理系统) 我们采用的方式就是Servlet和JSP结合使用
- 我们首先先来实现一下Servlet中不拼接html字符串、JSP中不内嵌Java代码的需求 即Servlet转发到JSP这一步
Customer.java
ListServlet.javapackage com.mj.bean; public class Customer { // 定义成员变量 // 姓名 private String name; // 年龄 private Integer age; // 身高 private Double height; public Customer(String name, Integer age, Double height) { this.name = name; this.age = age; this.height = height; } public Customer() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getHeight() { return height; } public void setHeight(Double height) { this.height = height; } @Override public String toString() { return "Customer{" + "name='" + name + '\'' + ", age=" + age + ", height=" + height + '}'; } }
List.jspimport com.mj.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; import java.util.ArrayList; import java.util.List; @WebServlet("/list") public class ListServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 将用户信息集合以键值对的形式储存到request中 request.setAttribute("customers", getCustomers()); // 然后将请求发送给JSP 并且跳转到JSP页面 request.getRequestDispatcher("page/list.jsp").forward(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 定义一个获取用户信息的方法 private List getCustomers(){ // 首先我们创建一个List集合 用于储存用户信息 List<Customer> customers = new ArrayList<>(); // 我们还是假设有10个用户 for(int i = 0; i < 10; ++i){ customers.add(new Customer("张三" + i, 10 + i, 1.70 + i)); } // 返回用户信息集合 return customers; } }
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <title>展示用户信息</title> <!-- 设置表格单元格的样式 --> <style> th td { border: 1px solid black; } </style> </head> <body> <!-- 我们要以表格的形式来展示用户信息 --> <table> <thead> <tr> <th>姓名</th> <th>年龄</th> <th>身高</th> </tr> </thead> <tbody> <c:forEach items="${customers}" var="customer"> <tr> <td>${customer.name}</td> <td>${customer.age}</td> <td>${customer.height}</td> </tr> </c:forEach> </tbody> </table> </body> </html>
- 一些小细节:
- Customer.java中的成员变量统统设置为包装类的原因在于方便判空 我举个例子 比如我设置了一个price成员变量 如果设置为int基本类型 那么就无法判空 相反 如果设置为了包装类Integer的话 那么判空操作就十分容易
- getRequestDispacher() 可以接收一个参数 表示的是转换的目的地 由于转发操作只能在同一个context中进行 所以你只需要写明Servlet/JSP在当前context中的路径即可
- 我们在jsp中导入标签库的时候 语句为
<%@taglib prefix="c" uri="xxx"%>
其中uri指的是导入的标签库路径 而prefix则为使用标签库中的标签时所加的前缀 设置前缀的意义在于可以区分不同标签库中的相同标签 避免产生歧义 - 我们利用el获取了customers集合 他的本质为request.getAttribute(“customers”) 但是获取集合中每一个元素的属性时 我们也利用了el进行获取 其本质为obj.getProperty(但是如果遍历的元素类型是Map类型并且要获取的是其中的value 那么el表达式通常写为obj[“property”]/obj[propertyVar] 其本质相当于Java中的obj.get 即Map中的键找值)
- 一些小细节:
4.转发
- 所谓转发 其实就是将请求发送给指定的接收方 并且跳转到接收方的界面
- 转换操作只能在同一个context(项目 context就是用来定位部署到服务器软件上的项目的) 即只允许你在同一个项目中的不同Servlet/JSP(JSP的本质就是Servlet)进行转发操作
1.转发链条
-
在同一个请求中 可以转发多次 形成一个转发链条
- 在一个转发链条上 可以通过request.setAttribute、request.getAttribute进行数据共享(不同的context之间肯定是不可能发生数据共享的 原因在于转发操作只允许在同一个context中进行)
- 每一次转发都会创建一个新的request对象 用成员变量request指向前一个request对象
ListServlet
package mj.servlet; import mj.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; import java.util.ArrayList; import java.util.List; @WebServlet("/list") public class ListServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 将用户信息集合以键值对的形式储存到request中 request.setAttribute("customers", getCustomers()); // 然后将请求发送给JSP 并且跳转到JSP页面 request.getRequestDispatcher("/test1").forward(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 定义一个获取用户信息的方法 private List getCustomers(){ // 首先我们创建一个List集合 用于储存用户信息 List<Customer> customers = new ArrayList<>(); // 我们还是假设有10个用户 for(int i = 0; i < 10; ++i){ customers.add(new Customer("张三" + i, 10 + i, 1.70 + i)); } // 返回用户信息集合 return customers; } }
Test1Servlet
package mj.servlet; 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("/test1") public class Test1Servlet 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 { System.out.println(request); request.getRequestDispatcher("/test2").forward(request, response); } }
Test2Servlet
package mj.servlet; 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("/test2") public class Test2Servlet 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 { System.out.println(request); request.getRequestDispatcher("/test3").forward(request, response); } }
Test3Servlet
package mj.servlet; 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("/test3") public class Test3Servlet 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 { System.out.println(request); } }
对于以上案例 我们在地址栏中输入test1 然后就可以看到三次转发相应的request类型以及哈希值 从结果来看 三次转发的request对象都各不相同 因此 可以证明每一次转发都创建了一个新的request对象
而在同一个转发链条中进行数据共享 则依靠的是每一次新的request对象中request成员变量会指向上一次的request对象 相当于通过两两之间的指针进行数据共享
我们可以通过断点调试证明一下上述图的合理性
我们分别在三个TestServlet中request打印语句打断点 然后依次执行到三处断点位置 分别查看三个request对象 以下图片展示了转发链条上request对象之间的指向关系
- 在一个转发链条上 可以通过request.setAttribute、request.getAttribute进行数据共享(不同的context之间肯定是不可能发生数据共享的 原因在于转发操作只允许在同一个context中进行)
-
在转发链条上 所有的attribute都会储存到头部的Request对象中
- 实现原理:第一次转发的request对象是RequestFacade类型 我们查找该类中的setAttribute 可以发现 他其实是调用本类中的request成员的setAttribute 而本类中的request成员指向前一个request对象 那么我们就可以知道 原来他是一路往上调用setAttribute 直到调用头部request对象的setAttribute
- 同理 attribute的获取也是一路往上调用getAttribute 直到调用头部request对象的getAttribute为止
5.再次实现
这次的实现我们要完成添加和保存功能 其中 具体的思路为:list.jsp页面点击添加按钮 跳转到添加界面 输入新用户 保存 表单发送给SaveServlet处理 然后添加到ListServlet中的Customers集合 但是呢 有更好的解决方案 即设置一个模型(bean) 封装获取/添加集合的功能 只不过 里头的话 初始化集合需要放置在静态初始化块(保证程序运行过程中只会执行一次) 集合变量需私有静态修饰 并且以final加以修饰(保证程序运行过程中只有一份内存 且由于只进行了一次赋值操作 所以建议加上final修饰) 至于说获取/添加集合的功能 需公有化 提供外界去访问
list.jsp 其中 a的href属性值不要加/ 否则无法跳转
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>客户列表</title>
<style>
th {
border: 1px solid black;
}
td {
border: 1px solid red
}
</style>
</head>
<body>
<a href="page/add.html">添加</a>
<table>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>身高</th>
</tr>
</thead>
<tbody>
<!-- 遍历集合中的元素 并且获取每一个元素的属性 -->
<c:forEach items="${customers}" var="customer" varStatus="s">
<tr>
<td>${customer.name}_${s.index}</td>
<td>${customer.age}_${s.index}</td>
<td>${customer.height}_${s.index}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
add.html 其中 form的action属性值会拼接到IP:端口号之后 可以加/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/crm3/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>
SaveServlet.java 其中 getRequestDispatcher参数为相对于项目路径的路径 可以加/
而且 字符串转换为Integer/Double有两种方法 分别为:valueOf/parsexxx 返回值分别为Integer/int 建议使用一步到位的valueOf方法
package com.mj.servlet;
import com.mj.bean.Customer;
import com.mj.bean.Data;
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 {
// 设置请求类型
request.setCharacterEncoding("UTF-8");
// 接收请求参数
String name = request.getParameter("name");
String age = request.getParameter("age");
String height = request.getParameter("height");
// 接着我们要将请求参数封装为一个bean类
Customer customer = new Customer(name, Integer.valueOf(age), Double.valueOf(height));
Data.add(customer);
request.getRequestDispatcher("/list").forward(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
ListServlet.java 其中 getRequestDispatcher的参数仍然是相对于项目路径的路径 可以加/
package com.mj.servlet;
import com.mj.bean.Customer;
import com.mj.bean.Data;
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;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/list")
public class ListServlet extends HttpServlet {
private List<Customer> getCustomers(){
// 定义一个集合 用于存放customer
List<Customer> customers = Data.getCustomers();
return customers;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 将获取到的用户信息储存到集合中
request.setAttribute("customers", getCustomers());
// 然后在转发到list.jsp
request.getRequestDispatcher("/page/list.jsp").forward(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
Customer.java 用户信息类
package com.mj.bean;
public class Customer {
// 姓名
private String name;
// 年龄
private Integer age;
// 身高
private Double height;
public Customer(String name, Integer age, Double height) {
this.name = name;
this.age = age;
this.height = height;
}
public Customer() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
Data.java 封装用户获取/添加功能的类
package com.mj.bean;
import java.util.ArrayList;
import java.util.List;
public class Data {
private static final List<Customer> customers = new ArrayList<>();
static{
for(int i = 0; i < 10; ++i){
customers.add(new Customer("张三" + i, i, 1.7 + i));
}
}
public static List<Customer> getCustomers(){
return customers;
}
public static void add(Customer customer){
customers.add(customer);
}
}
但是 我们实现的这个项目有一个明显的缺点 即所展示的页面和地址栏没有互相对应 比如:以下界面展示的为list地址对应的画面 地址栏却为save
重定向就可以很好的解决这个问题
6.重定向
所谓重定向 其实就是服务器响应客户端提醒其再次发送一个新的任意的url
并且重定向操作的响应码为302
- 响应由响应码(表示不同的响应操作 302表示重定向操作 而200表示正常响应)+响应头(包含content-type、location(用于指定客户端再次发送请求时新的url))
- 重定向流程
- 我们就可以利用重定向来规避页面、地址栏不对应的弊端
SaveServlet 其中 利用sendRedirect完成了重定向操作 而且参数包含了context 原因在于这个参数指定了客户端重新发送的请求 而客户端发送的请求会拼接到IP:端口号之后
package com.mj.servlet;
import com.mj.bean.Customer;
import com.mj.bean.Data;
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 {
// 设置请求类型
request.setCharacterEncoding("UTF-8");
// 接收请求参数
String name = request.getParameter("name");
String age = request.getParameter("age");
String height = request.getParameter("height");
// 接着我们要将请求参数封装为一个bean类
Customer customer = new Customer(name, Integer.valueOf(age), Double.valueOf(height));
Data.add(customer);
response.sendRedirect("/crm3/list");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
7.转发vs重定向
- 转发:
request.getRequestDispatcher("/路径").forward(request, response);
- 只能转发到同一个Context下 路径中不用包含ContextPath
- 客户端只发送了一次请求
- 浏览器地址栏url不会改变
- 转发的操作只有服务器完成
- 重定向:
response.sendRedirect("/路径");
- 可以重定向到任意的url 如果重定向到同一个Context下 路径需要包含ContextPath
- 浏览器发送了两次请求
- 浏览器地址栏url会改变
- 重定向操作由服务器+客户端配合完成