书城项目第六、七阶段
项目第六阶段:购物车
购物车模块分析:
项目中常用的购物车实现技术:
- Session 版本(将购物车信息存放到 Session 域中)
- 数据库版本(将购物车信息存放到数据库中)
- redis + sql + Cookie(使用 Cookie + redis 缓存,和数据库 )
购物车的功能:
- 加入购物车 addItem
- 删除商品项 deleteItem
- 清空购物车 clear
- 修改商品数量 updateCount
购物车模块实现:
加入购物车 addItem
- CartServlet.java
req.getHeader("Referer")获取请求发起时的浏览器地址location.href= "http..."; 浏览器发起请求
private BookService bs = new BookServiceImpl();
/**
* 加入购物车
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void addItem (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
/**
* 1.获取请求参数,商品编号
* 2.调用 bs.queryBookById(id)获取图书信息
* 3.将图书信息转换为 cartItem商品项
* 4.调用 cart.addItem(); 添加商品项
* 5.重定向回商品列表页面
*/
int id = WebUtils.parseInt(req.getParameter("id"), 0);
Book book = bs.queryBookById(id);
CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (null == cart) {
cart = new Cart();
}
cart.addItem(cartItem);
req.getSession().setAttribute("cart", cart);
System.out.println(cart);
System.out.println("Referer " + req.getHeader("Referer"));
// req.getContextPath(): 项目根目录
// resp.sendRedirect(req.getContextPath() + "/pages/client/index.jsp");
/**
* 在 http协议中有一个请求头,叫 Referer,
* 它保存了请求发起时的浏览器地址,
* 可以使用它作为 重定向的地址
*/
resp.sendRedirect(req.getHeader("Referer"));
}
// index.jsp
<div class="book_add">
<button class="add_cart" bookId="${item.id}">加入购物车</button>
</div>
<script type="text/javascript">
$(function () {
$("button.add_cart").click(function () {
var bookId = $(this).attr("bookId");
location.href = "${pageContext.getAttribute("basePath")}" + "cartServlet?action=addItem&id=" + bookId;
})
})
</script>>
删除商品项 deleteItem
- 使用 sessionScope 获取保存在 Session 中的数据
- Map 在 jsp页面中的遍历方法 <c:forEach var="entry" items="${sessionScope.cart.items}">
- 空值的判断 <c:if test="${not empty sessionScope.cart.items}">
// CartServlet.java
/**
* 删除购物车指定商品
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void deleteItem (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
int id = WebUtils.parseInt(req.getParameter("id"), 0);
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (null != cart) {
cart.deleteItem(id);
}
req.getSession().setAttribute("cart", cart);
resp.sendRedirect(req.getHeader("Referer"));
}
// index.jsp
<div id="main">
<table>
<tr>
<td>商品名称</td>
<td>数量</td>
<td>单价</td>
<td>金额</td>
<td>操作</td>
</tr>
<c:choose>
<c:when test="${empty sessionScope.cart.items}">
<tr>
<td colspan="5">亲,当前购物车为空!<a href="index.jsp">返回首页</a> </td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="entry" items="${sessionScope.cart.items}">
<tr>
<td>${entry.value.name}</td>
<td>${entry.value.count}</td>
<td>${entry.value.price}</td>
<td>${entry.value.totalPrice}</td>
<td><a class="del_item" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</table>
<c:if test="${not empty sessionScope.cart.items}">
<div class="cart_info">
<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span>元</span>
<span class="cart_span"><a href="#">清空购物车</a></span>
<span class="cart_span"><a href="pages/cart/checkout.jsp">去结账</a></span>
</div>
</c:if>
</div>
<script type="text/javascript">
$(function () {
$("a.del_item").click(function () {
return confirm("你确认删除《 " + $(this).parent().parent().find("td:first").text() + " 》图书吗?");
})
})
</script>
清空购物车 clear
<c:if test="${not empty sessionScope.cart.items}">
<div class="cart_info">
<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span>元</span>
// clear
<span class="cart_span"><a class="clear_cart" href="cartServlet?action=clear">清空购物车</a></span>
<span class="cart_span"><a href="pages/cart/checkout.jsp">去结账</a></span>
</div>
</c:if>
// ..............................
<script type="text/javascript">
$(function () {
// 清空购物车
$("a.clear_cart").click(function () {
return confirm("你确认要清空购物车吗?");
});
</script>
protected void clear (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (null != cart) {
cart.clear();
}
req.getSession().setAttribute("cart", cart);
resp.sendRedirect(req.getHeader("Referer"));
}
修改商品数量 updateCount
- DOM 的 defaultValue属性
- attr() 方法,获取自定义属性
<c:otherwise>
<c:forEach var="entry" items="${sessionScope.cart.items}">
<tr>
<td>${entry.value.name}</td>
<td>
<input oldCount="${entry.value.count}" bookId="${entry.value.id}" style="width: 60px;" type="text" class="cart_count" value="${entry.value.count}" />
</td>
<td>${entry.value.price}</td>
<td>${entry.value.totalPrice}</td>
<td><a class="del_item" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>
</tr>
</c:forEach>
</c:otherwise>
// ..............................
<script type="text/javascript">
$(function () {
// 改变数量:change 改变数据并失去焦点后触发此事件
$("input.cart_count").change(function () {
var bookName = $(this).parent().parent().find("td:first").text();
// var count = $(this).val();
var count = this.value;
var id = $(this).attr("bookId");
// var oldCount = $(this).attr("oldCount");
var isAdd = confirm("你确定将《 " + bookName + " 》商品数量修改为:" + count + " 吗?");
if (isAdd) {
// updateCount (Integer id, Integer count)
location.href = "${pageContext.getAttribute("basePath")}" + "cartServlet?action=updateCount&id=" + id + "&count=" + count;
} else {
// $(this).val(oldCount);
// this.value = oldCount;
// defaultValue 表示 DOM 默认的 value 属性值
this.value = this.defaultValue;
}
})
})
</script>
protected void updateCount (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
int id = WebUtils.parseInt(req.getParameter("id"), 0);
int count = WebUtils.parseInt(req.getParameter("count"), 0);
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (null != cart) {
cart.updateCount(id, count);
}
req.getSession().setAttribute("cart", cart);
resp.sendRedirect(req.getHeader("Referer"));
}
首页,购物车信息回显
- 在 Session 中存放最后一次添加的图书名称
- req.getSession().setAttribute("lastName", cartItem.getName());
/**
* 加入购物车
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void addItem (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
/**
* 1.获取请求参数,商品编号
* 2.调用 bs.queryBookById(id)获取图书信息
* 3.将图书信息转换为 cartItem商品项
* 4.调用 cart.addItem(); 添加商品项
* 5.重定向回商品列表页面
*/
int id = WebUtils.parseInt(req.getParameter("id"), 0);
Book book = bs.queryBookById(id);
CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (null == cart) {
cart = new Cart();
}
cart.addItem(cartItem);
req.getSession().setAttribute("cart", cart);
// System.out.println(cart);
// System.out.println("Referer " + req.getHeader("Referer"));
// req.getContextPath(): 项目根目录
// resp.sendRedirect(req.getContextPath() + "/pages/client/index.jsp");
/**
* 在 http协议中有一个请求头,叫 Referer,
* 它保存了请求发起时的浏览器地址,
* 可以使用它作为 重定向的地址
*/
req.getSession().setAttribute("lastName", cartItem.getName());
resp.sendRedirect(req.getHeader("Referer"));
}
<div style="text-align: center">
<c:choose>
<c:when test="${not empty sessionScope.cart.items}">
<span>您的购物车中有 ${sessionScope.cart.totalCount} 件商品</span>
<div>
您刚刚将<span class="book_cart" style="color: red">《 ${sessionScope.lastName} 》</span>加入到了购物车中
</div>
</c:when>
<c:otherwise>
<div class="book_cart">当前购物车暂无商品...</div>
</c:otherwise>
</c:choose>
</div>
项目第六阶段:订单
订单功能分析:
OrderServlet 程序:
-
生成订单:createOrder()
-
查询所有订单(管理员):showAllOrders()
-
发货(管理员):sendOrder()
-
查看订单详情(管理员、用户):showOrderDetail()
-
查看我的订单(用户):showMyOrders()
-
签收订单(用户):receiveOrder()
OrderService 程序:
-
生成订单:createOrder(Cart, userId)
-
查询所有订单(管理员):showAllOrders()
-
发货(管理员):sendOrder(orderId)
-
查看订单详情(管理员、用户):showOrderDetail(orderId)
-
查看我的订单(用户):showMyOrders(userId)
-
签收订单(用户):receiveOrder(orderId)
创建表单数据库:
USE book_project;
CREATE TABLE t_order(
order_id VARCHAR(50) PRIMARY KEY,
create_time datetime,
price DECIMAL(11, 2),
`status` INT,
user_id INT,
FOREIGN KEY(`user_id`) REFERENCES t_user(`id`)
);
CREATE TABLE t_order_item(
`id` INT PRIMARY KEY auto_increment,
`name` VARCHAR(100),
`count` INT,
price DECIMAL(11,2),
total_price DECIMAL(11,2),
order_id VARCHAR(50),
FOREIGN KEY(order_id) REFERENCES t_order(order_id)
);
编写订单模块的 Dao 程序
- OrderDao.java
package com.atjava.dao;
import com.atjava.pojo.Order;
/**
* @author lv
* @create 2021-10-21 22:26
*/
public interface OrderDao {
public int saveOrder (Order order);
}
- OrderDaoImpl.java
package com.atjava.dao.impl;
import com.atjava.dao.BaseDao;
import com.atjava.dao.OrderDao;
import com.atjava.pojo.Order;
/**
* @author lv
* @create 2021-10-21 22:31
*/
public class OrderDaoImpl extends BaseDao implements OrderDao {
@Override
public int saveOrder(Order order) {
String sql = "insert into t_order(order_id, create_time, price, status, user_id) values(?,?,?,?,?)";
int updates = updates(sql, order.getOrderId(), order.getCreateTime(), order.getPrice(), order.getStatus(), order.getUserId());
return updates;
}
}
- OrderItemDao.java
package com.atjava.dao;
import com.atjava.pojo.OrderItem;
/**
* @author lv
* @create 2021-10-21 22:29
*/
public interface OrderItemDao {
public int saveOrderItem (OrderItem orderItem);
}
- OrderItemDaoImpl.java
package com.atjava.dao.impl;
import com.atjava.dao.BaseDao;
import com.atjava.dao.OrderItemDao;
import com.atjava.pojo.OrderItem;
/**
* @author lv
* @create 2021-10-21 22:32
*/
public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
@Override
public int saveOrderItem(OrderItem orderItem) {
String sql = "insert into t_order_item(name, count, price, total_price, order_id) values(?,?,?,?,?)";
return updates(sql, orderItem.getName(), orderItem.getCount(), orderItem.getPrice(), orderItem.getTotalPrice(), orderItem.getOrderId());
}
}
编写订单模块的 Service 和测试
- OrderService.java
package com.atjava.service;
import com.atjava.pojo.Cart;
/**
* @author lv
* @create 2021-10-22 20:53
*/
public interface OrderService {
public String createOrder(Cart cart, Integer userId);
}
- OrderServiceImpl.java
package com.atjava.service.impl;
import com.atjava.dao.OrderDao;
import com.atjava.dao.OrderItemDao;
import com.atjava.dao.impl.OrderDaoImpl;
import com.atjava.dao.impl.OrderItemDaoImpl;
import com.atjava.pojo.*;
import com.atjava.service.OrderService;
import java.util.Date;
import java.util.Map;
/**
* @author lv
* @create 2021-10-22 20:56
*/
public class OrderServiceImpl implements OrderService {
private OrderDao od = new OrderDaoImpl();
private OrderItemDao oid = new OrderItemDaoImpl();
@Override
public String createOrder(Cart cart, Integer userId) {
/**
* 1.先保存订单
* 2.再保存订单项
*/
// 订单唯一:时间戳 + userId
String orderId = System.currentTimeMillis() + "" + userId;
Order order = new Order(orderId, new Date(), cart.getTotalPrice(), 0, userId);
od.saveOrder(order);
for (Map.Entry<Integer, CartItem> entry : cart.getItems().entrySet()) {
/**
* 1.获取购物车中的商品项并转化为订单项
* 2.将订单项保存到数据库中
*/
CartItem cartItem = entry.getValue();
oid.saveOrderItem(new OrderItem(null, cartItem.getName(), cartItem.getCount(), cartItem.getPrice(), cartItem.getTotalPrice(), orderId));
}
/**
* 结账后清空购物车
*/
cart.clear();
return orderId;
}
}
web层,编写 OrderServlet 程序
- OrderServlet.java
package com.atjava.web;
import com.atjava.pojo.Cart;
import com.atjava.pojo.User;
import com.atjava.service.OrderService;
import com.atjava.service.impl.OrderServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author lv
* @create 2021-10-22 22:05
*/
public class OrderServlet extends BaseServlet {
OrderService os = new OrderServiceImpl();
/**
* 生成订单
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void createOrder (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
/**
* 1.获取 Session中的购物车
* 2.调用 OrderServletImpl 生成订单
* 3.获取订单号保存在 域中并跳转到订单页面
*/
Cart cart = (Cart) req.getSession().getAttribute("cart");
User currentUser = (User) req.getSession().getAttribute("currentUser");
if (null == currentUser) {
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
// 停止执行下边的代码
return;
}
Integer userId = currentUser.getId();
String osOrder = os.createOrder(cart, userId);
// 订单号保存到 Session 域中
req.getSession().setAttribute("orderId", osOrder);
// req.setAttribute("orderId", osOrder);
resp.sendRedirect(req.getContextPath() + "/pages/cart/checkout.jsp");
}
}
- cart.jsp
<div class="cart_info">
<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span>元</span>
<span class="cart_span"><a class="clear_cart" href="cartServlet?action=clear">清空购物车</a></span>
<span class="cart_span"><a href="orderServlet?action=createOrder">去结账</a></span>
</div>
Filter 过滤器
Filter 过滤器介绍
- Filter 过滤器它是 JavaWeb的三大组件之一;三大组件是:Servlet 程序、Listener 监听器、Filter 过滤器
- Filter 过滤器是 JavaEE的规范,也就是接口
- Filter 过滤器的作用:
拦截请求、过滤响应
拦截请求 - 常见应用场景:
- 权限检查
- 日记操作
- 事务管理
- ... 等等
Filter 过滤器的初体验
要求:在项目 web工程下,有一个 admin目录,此目录下的所有资源(html、jpg、jsp 等)都必须是用户登录后才能访问。
思考:根据之前所学内容,我们知道,用户登录后一般会将用户登录信息保存到 Session 域中;所以检查用户是否登录,可以判断 Session 域中是否包含用户的登录信息。
- AdminFilter.java
package com.atjava.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author lv
* @create 2021-10-26 22:40
*/
public class AdminFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/**
* 此方法很重要,专门用于拦截请求。
* 1.可以做权限检查
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest hsr = (HttpServletRequest) request;
HttpSession session = hsr.getSession();
Object user = session.getAttribute("user");
if (null == user) {
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
} else {
// 让程序继续访问用户的目标资源
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
- web.xml
<?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">
<!--filter标签,用于配置一个 filter过滤器-->
<filter>
<!--给 filter起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置 filter的全类名-->
<filter-class>com.atjava.filter.AdminFilter</filter-class>
</filter>
<!--filter-mapping 配置 filter过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给那个 filter使用-->
<filter-name>AdminFilter</filter-name>
<!--
url-pattern 配置拦截路径
"/" 表示请求地址为 http://ip:port/工程路径/ 映射到 IDEA 的 web目录
"*" 表示全部资源
-->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
</web-app>
- admin
- a.jsp
- a.html
<%--
Created by IntelliJ IDEA.
User: Weili
Date: 2021/10/26
Time: 22:17
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>JSP</title>
</head>
<body>
<%
/**
* 权限控制:
* 方式一:
*/
Object user = session.getAttribute("user");
/**
* 如果 user 为 null,跳转到登录页面
*/
// if (null == user) {
// request.getRequestDispatcher("/login.jsp").forward(request, response);
// return;
// }
// 方式二:filter 过滤器控制
%>
我是 a.jsp页面
</body>
</html>
Filter 的生命周期
Filter 的生命周期包含的几个方法
- 构造器方法
- init 初始化方法
- 第 1、2 步,在 web工程启动时执行(已创建 Filter 过滤器)
- doFilter 过滤方法
- 每次拦截到请求,就会执行
- destory 销毁
- 停止 web 工程时,就会执行(并销毁 Filter 过滤器)
package com.atjava.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author lv
* @create 2021-10-26 22:40
*/
public class AdminFilter implements Filter {
public AdminFilter () {
System.out.println("1.AdminFilter构造器方法");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2.init初始化方法");
}
/**
* 此方法很重要,专门用于拦截请求。
* 1.可以做权限检查
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("3.doFilter过滤器方法");
HttpServletRequest hsr = (HttpServletRequest) request;
HttpSession session = hsr.getSession();
Object user = session.getAttribute("user");
if (null == user) {
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
} else {
// 让程序继续访问用户的目标资源
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
System.out.println("4.destroy销毁方法");
}
}
FilterConfig 类
FilterConfig 类见名知意,它是 Filter 过滤器的配置文件类。
Tomcat 每次创建 Filter 时,也会同时创建一个 FilterConfig 类,这个包含了 Filter 配置文件的配置信息。
FilterConfig 类的作用是获取 filter 过滤器的配置内容
- 获取 Filter 的名称 filter-name 的内容
- 获取在 web.xml中配置的 Filter 的 init-param
初始化参数 - 获取 ServletContext 对象
- AdminFilter.java
package com.atjava.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author lv
* @create 2021-10-26 22:40
*/
public class AdminFilter implements Filter {
public AdminFilter () {
System.out.println("1.AdminFilter构造器方法");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2.init初始化方法");
// 1. 获取 Filter 的名称 filter-name 的内容
System.out.println("filter-name:" + filterConfig.getFilterName());
// 2. 获取在 Filter 中配置的 init-param 初始化参数
System.out.println("init-param:username - " + filterConfig.getInitParameter("username"));
// 3. 获取 ServletContext 对象
System.out.println("ServletContext:" + filterConfig.getServletContext());
}
/**
* 此方法很重要,专门用于拦截请求。
* 1.可以做权限检查
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("3.doFilter过滤器方法");
HttpServletRequest hsr = (HttpServletRequest) request;
HttpSession session = hsr.getSession();
Object user = session.getAttribute("user");
if (null == user) {
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
} else {
// 让程序继续访问用户的目标资源
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
System.out.println("4.destroy销毁方法");
}
}
- web.xml
<?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">
<!--filter标签,用于配置一个 filter过滤器-->
<filter>
<!--给 filter起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置 filter的全类名-->
<filter-class>com.atjava.filter.AdminFilter</filter-class>
<!-- filter 的初始化参数-->
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
</filter>
<!--filter-mapping 配置 filter过滤器的拦截路径-->
<filter-mapping>
<!--filter-name 表示当前的拦截路径给那个 filter使用-->
<filter-name>AdminFilter</filter-name>
<!--
url-pattern 配置拦截路径
"/" 表示请求地址为 http://ip:port/工程路径/ 映射到 IDEA 的 web目录
"*" 表示全部资源
-->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
<!--... ...-->
</web-app>
FilterChain 过滤器链
- 多个过滤器如何一起工作
FilterChain 的作用:
- 执行下一个 Filter 过滤器(如果有)
- 执行目标资源(没有 Filter 或 有关的 Filter 都执行完毕后)
多个 FilterChain 的执行顺序:
-
对于同一个 目标资源 或 文件夹 的过滤,在 web.xml 中的 Filter的书写顺序,就是程序执行过滤的顺序
-
web.xml
<filter>
<filter-name>Filter1</filter-name>
<filter-class>com.atjava.filter.Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/target.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>com.atjava.filter.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter2</filter-name>
<url-pattern>/target.jsp</url-pattern>
</filter-mapping>
多个 Filter 过滤器执行的特点:
- 所有 Filter 和 目标资源 默认都执行在同一个线程中
- 多个 Filter 和 目标资源 链式执行的时候,它们都使用同一个 Request 对象(request中的数据是共享的)
// Filter1 前置代码
// 线程1:http-apr-8080-exec-9
// Filter2 前置代码
// 线程2:http-apr-8080-exec-9
// target.jsp 页面执行了
// filter1:filter1
// Filter2 后置代码
// Filter1 后置代码
- target.jsp
<%--
Created by IntelliJ IDEA.
User: Weili
Date: 2021/11/1
Time: 21:33
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Target</title>
</head>
<body>
<%
System.out.println("target.jsp 页面执行了");
System.out.println("filter1:" + request.getAttribute("filter1"));
%>
</body>
</html>
- Filter1.java
package com.atjava.filter;
import javax.servlet.*;
import java.io.IOException;
/**
* @author lv
* @create 2021-11-01 21:25
*/
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Filter1 前置代码");
System.out.println("线程1:" + Thread.currentThread().getName());
chain.doFilter(request, response);
System.out.println("Filter1 后置代码");
}
@Override
public void destroy() {
}
}
Filter 的拦截路径
Filter 过滤器,它只关心请求地址是否匹配,不关心请求的资源是否存在
精确匹配
- <url-pattern>/target.jsp</url-pattern>
- 以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/target.jsp
目录匹配
- <url-pattern>/admin/*</url-pattern>
- 以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/*
后缀名匹配
- <url-pattern>*.html</url-pattern>
- 以上配置的路径,表示请求地址必须以 .html 为结尾的资源,才会进行过滤
- 后缀名可以自己定义:*.abc,*.xxx 等
- 注意:后缀名匹配,路径不能以 斜杠
"/"开头
书城第八阶段
使用 Filter 过滤器拦截 /pages/manager/ 内的所有内容,实现权限检查
- ManagerFilter.java
package com.atjava.filter;
import com.atjava.pojo.User;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author lv
* @create 2021-11-02 21:16
*/
public class ManagerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 类型转换
HttpServletRequest hsr = (HttpServletRequest) request;
HttpServletResponse hsrr = (HttpServletResponse) response;
HttpSession session = hsr.getSession();
User user = (User) session.getAttribute("currentUser");
if (null == user) {
hsrr.sendRedirect(hsr.getContextPath() + "/pages/user/login.jsp");
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
- web.xml
<filter>
<filter-name>ManagerFilter</filter-name>
<filter-class>com.atjava.filter.ManagerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManagerFilter</filter-name>
<url-pattern>/pages/manager/*</url-pattern>
<url-pattern>/manager/bookServlet</url-pattern>
</filter-mapping>
ThreadLocal 的使用
在JDK 中,用于解决数据安全问题的工具类。
- ThreadLocal 的作用,它可以解决多线程的数据安全问题
- ThreadLocal 可以给当前线程关联一个数据(可以是 普通变量、数组、对象、集合)
ThreadLocal 的特点:
- ThreadLocal 可以为当前线程关联一个数据(可以像 Map 一样存取数据,key 为当前线程)
- 每个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个 ThreadLocal 对象实例。
- 每个 ThreadLocal 对象实例定义时,一般都是 static 类型
- ThreadLocal 中保存的数据,在线程销毁后,会由 JVM 虚拟机自动释放
- ThreadLocal.java
package com.atjava.threadlocal;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author lv
* @create 2021-11-02 22:21
*/
public class ThreadLocal {
// ConcurrentHashMap 线程安全的 Map,在高并发下使用
public static Map<String,Object> data = new ConcurrentHashMap<>();
private static Random rd = new Random();
public static java.lang.ThreadLocal<Object> threadLocal = new java.lang.ThreadLocal<>();
public static class Task implements Runnable {
@Override
public void run() {
/**
* 1.在 Run 方法中,随机生成一个变量(线程要关联的数据),
* 然后 以当前线程名为 key 保存到 Map 中
*
* 2.在 Run 方法结束之前,以当前线程名获取数据并打印,
* 查看是否可以取出操作
*/
// 获取随机数
Integer i = rd.nextInt(1000);
// 获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("线程 " + name + " 的随机数:" + i);
data.put(name, i);
// 使用 java.lang.ThreadLocal 保存当前变量
threadLocal.set(i);
try {
// 等待 5 秒钟
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new OrderService().createOrder();
Object o = data.get(name);
Object o1 = threadLocal.get();
System.out.println("在本线程 " + name + " 销毁前取出的随机数:" + o.toString() + " - " + o1.toString());
}
}
public static void main (String[] args) {
for (int i = 0; i < 3; ++i) {
new Thread(new Task()).start();
}
}
}
- OrderService.java
package com.atjava.threadlocal;
/**
* @author lv
* @create 2021-11-02 22:51
*/
public class OrderService {
public void createOrder () {
String name = Thread.currentThread().getName();
System.out.println("OrderService 当前线程 " + name + " 中保存的数据是:" + ThreadLocal.data.get(name) + " - " + ThreadLocal.threadLocal.get());
new OrderDao().saveOrder();
}
}
- OrderDao.java
package com.atjava.threadlocal;
/**
* @author lv
* @create 2021-11-02 22:58
*/
public class OrderDao {
public void saveOrder () {
String name = Thread.currentThread().getName();
System.out.println("OrderDao 当前线程 " + name + " 中保存的数据是:" + ThreadLocal.data.get(name) + " - " + ThreadLocal.threadLocal.get());
}
}
- 程序执行的结果
线程 Thread-0 的随机数:121
线程 Thread-1 的随机数:414
线程 Thread-2 的随机数:709
OrderService 当前线程 Thread-1 中保存的数据是:414 - 414
OrderService 当前线程 Thread-0 中保存的数据是:121 - 121
OrderService 当前线程 Thread-2 中保存的数据是:709 - 709
OrderDao 当前线程 Thread-0 中保存的数据是:121 - 121
OrderDao 当前线程 Thread-2 中保存的数据是:709 - 709
OrderDao 当前线程 Thread-1 中保存的数据是:414 - 414
在本线程 Thread-2 销毁前取出的随机数:709 - 709
在本线程 Thread-0 销毁前取出的随机数:121 - 121
在本线程 Thread-1 销毁前取出的随机数:414 - 414
使用 Filter 和 ThreadLocal 组合管理事务
- 问题:谁能确保所有操作都使用同一个 Connection 连接对象? -
ThreadLocal对象可以 - 前提条件:ThreadLocal 要确保所有操作都使用同一个 Connection 连接对象的前提条件是,
所有操作都必须在同一个线程中完成!!!
回顾 jdbc 的数据库事务管理
- 要确保所有操作
要么都成功、要么都失败,就必须要使用数据库的事务 - 要确保所有操作都在一个事务内,就必须确保,所有操作都使用同一个 Connection 连接对象
// 回顾 jdbc 的数据库事务管理
Connection conn = JdbcUtils.getConnection();
try {
conn.setAutoCommit(false); // 设置为手动管理事务
// 执行一系列的 jdbc 操作
conn.commit(); // 手动提交事务
} catch (Exception err) {
conn.rollback(); // 回滚事务
} finally {
JdbcUtils.close(conn);
}
使用 Filter 和 ThreadLocal 组合管理事务
- JDBCUtils.java
- 添加 ThreadLocal 对象 conns
package com.atjava.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author lv
* @create 2021-08-02 22:33
*/
public class JDBCUtils {
private static DruidDataSource dataSource;
private static ThreadLocal<Connection> conns = new ThreadLocal<>();
static {
try {
Properties properties = new Properties();
// 读取 jabc.properties属性配置文件
InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 加载流数据
properties.load(inputStream);
// 创建 数据库连接池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接池中的连接
* @return
*/
public static Connection getConnection () {
Connection conn = conns.get();
if (null == conn) {
try {
// 从数据库连接池中获取连接
conn = dataSource.getConnection();
// 设置为手动管理事务
conn.setAutoCommit(false);
// 将数据源中的连接保存到 ThreadLocal 中,供后面的 jdbc 操作使用
conns.set(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
}
/**
* 提交事务,并关闭释放连接
*/
public static void commitAndClose () {
Connection connect = conns.get();
if (null != connect) {
try {
connect.commit(); // 提交事务
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connect.close(); // 关闭连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 一定要执行 remove操作,否则会出错(因为 Tomcat服务器底层使用了线程池技术)
conns.remove();
}
/**
* 回滚事务,并关闭释放连接
*/
public static void rollbackAndClose () {
Connection connect = conns.get();
if (null != connect) {
try {
connect.rollback(); // 回滚事务
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connect.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 一定要执行 remove操作,否则会出错(因为 Tomcat服务器底层使用了线程池技术)
conns.remove();
}
}
- BaseDao.java
- 去除自动关闭 连接
- 抛出所有异常,供使用者判断是 提交或回滚
package com.atjava.dao;
import com.atjava.utils.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author lv
* @create 2021-08-04 21:56
*/
public abstract class BaseDao {
// 使用 DbUtils操作数据
private QueryRunner qr = new QueryRunner();
/**
* 用来操作 insert、update、delete方法
* @return
*/
// public int updates (String sql, Object ...args) {
// Connection conn = JDBCUtils.getConnection();
// try {
// return qr.update(conn, sql, args);
// } catch (SQLException e) {
// e.printStackTrace();
// } finally {
// JDBCUtils.close(conn);
// }
// return -1;
// }
public int updates (String sql, Object ...args) {
Connection conn = JDBCUtils.getConnection();
try {
return qr.update(conn, sql, args);
} catch (SQLException e) {
e.printStackTrace();
/**
* 抛出异常,使外面捕获到可以进行事务回滚
*/
throw new RuntimeException(e);
}
}
/**
* 返回结果为 单个数据
* @param type
* @param sql
* @param args
* @param <T>
* @return
*/
// public <T> T queryForOne (Class<T> type, String sql, Object ...args) {
// Connection conn = JDBCUtils.getConnection();
// try {
// return qr.query(conn, sql, new BeanHandler<T>(type), args);
// } catch (SQLException e) {
// e.printStackTrace();
// } finally {
// // JDBCUtils.close(conn);
// }
// return null;
// }
public <T> T queryForOne (Class<T> type, String sql, Object ...args) {
Connection conn = JDBCUtils.getConnection();
try {
return qr.query(conn, sql, new BeanHandler<T>(type), args);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 返回结果为 list的数据
* @param type
* @param sql
* @param args
* @param <T>
* @return
*/
// public <T> List<T> queryForList (Class<T> type, String sql, Object ...args) {
// Connection conn = JDBCUtils.getConnection();
// try {
// return qr.query(conn, sql, new BeanListHandler<T>(type), args);
// } catch (SQLException e) {
// e.printStackTrace();
// } finally {
// // JDBCUtils.close(conn);
// }
// return null;
// }
public <T> List<T> queryForList (Class<T> type, String sql, Object ...args) {
Connection conn = JDBCUtils.getConnection();
try {
return qr.query(conn, sql, new BeanListHandler<T>(type), args);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 返回一行一列的 sql数据
* @param sql
* @param args
* @return
*/
// public Object queryForSingleValue (String sql, Object ...args) {
// Connection conn = JDBCUtils.getConnection();
// try {
// return qr.query(conn, sql, new ScalarHandler(), args);
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// // JDBCUtils.close(conn);
// }
// return null;
// }
public Object queryForSingleValue (String sql, Object ...args) {
Connection conn = JDBCUtils.getConnection();
try {
return qr.query(conn, sql, new ScalarHandler(), args);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
- OrderServlet.java
- 使用 ThreadLocal
package com.atjava.web;
import com.atjava.pojo.Cart;
import com.atjava.pojo.User;
import com.atjava.service.OrderService;
import com.atjava.service.impl.OrderServiceImpl;
import com.atjava.utils.JDBCUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author lv
* @create 2021-10-22 22:05
*/
public class OrderServlet extends BaseServlet {
OrderService os = new OrderServiceImpl();
/**
* 生成订单
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void createOrder (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
/**
* 1.获取 Session中的购物车
* 2.调用 OrderServletImpl 生成订单
* 3.获取订单号保存在 域中并跳转到订单页面
*/
Cart cart = (Cart) req.getSession().getAttribute("cart");
User currentUser = (User) req.getSession().getAttribute("currentUser");
if (null == currentUser) {
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
// 停止执行下边的代码
return;
}
Integer userId = currentUser.getId();
String osOrder = null;
/**
* ThreadLocal 的管理事务
*/
try {
osOrder = os.createOrder(cart, userId);
JDBCUtils.commitAndClose(); // 提交事务
} catch (Exception e) {
JDBCUtils.rollbackAndClose(); // 回滚事务
e.printStackTrace();
}
// 订单号保存到 Session 域中
req.getSession().setAttribute("orderId", osOrder);
// req.setAttribute("orderId", osOrder);
resp.sendRedirect(req.getContextPath() + "/pages/cart/checkout.jsp");
}
}
使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch,来进行事务的管理
- web.xml
- "/*" 表示过滤当前工程下的所有请求
<!--
filter 与 sql事务 组合,统一进行 try-catch
-->
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.atjava.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!--
"/*":表示当前工程下所有请求
-->
<url-pattern>/*</url-pattern>
</filter-mapping>
- TransactionFilter.java
- 使用 try-catch 包裹 chain.doFilter()方法
- 从而利用是否报错来触发 sql事务的 提交或回滚
package com.atjava.filter;
import com.atjava.utils.JDBCUtils;
import javax.servlet.*;
import java.io.IOException;
/**
* @author lv
* @create 2021-11-04 21:43
*/
public class TransactionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
JDBCUtils.commitAndClose(); // 提交事务
} catch (Exception e) {
JDBCUtils.rollbackAndClose(); // 回滚事务
e.printStackTrace();
}
}
@Override
public void destroy() {
}
}
将所有的异常都统一交给 Tomcat,让 Tomcat 展示友好的错误信息页面
-
在 web.xml 中可以通过 错误页面配置来管理工程中出现的错误
-
web.xml
<!--
error-page:工程中出现错误的配置
注意:工程中出现的错误需要抛出后,此配置才能生效
-->
<error-page>
<!-- error-code 错误类型 -->
<error-code>500</error-code>
<!-- location 标签表示,要跳转去的页面路径 -->
<location>/pages/error/error500.jsp</location>
</error-page>
<error-page>
<!-- error-code 错误类型 -->
<error-code>404</error-code>
<!-- location 标签表示,要跳转去的页面路径 -->
<location>/pages/error/error404.jsp</location>
</error-page>
- error500.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>我的订单</title>
<%-- 使用静态包含替换,包含 base标签、css样式、jQuery文件 --%>
<%@ include file="/pages/common/head.jsp" %>
<%--<link type="text/css" rel="stylesheet" href="../../static/css/style.css" >--%>
<style type="text/css">
h1 {
text-align: center;
margin-top: 200px;
}
</style>
</head>
<body>
抱歉,后台服务错误,程序管理人员正在修复!!!<a href="index.jsp">返回首页</a>
</body>
</html>