前九个部分我们大致完成了书城项目的所有功能,第十部分我们做一个结尾,主要包括使用Filter过滤器进行权限检查,使用Filter和ThreadLocal组合管理事务,将异常同一交给Tomcat并展示友好的错误页面,使用Aajx请求改进功能。
Filter过滤器实现权限检查
我们要使用 Filter 过滤器拦截/pages/manager/所有内容,实现权限检查。
-
Filter
的工作流程如下
-
Filter
过滤器的使用步骤:
1、 编写一个类去实现 Filter 接口
2、 实现过滤方法 doFilter()
3、 到 web.xml 中去配置 Filter
Filter
的生命周期
Filter 的生命周期包含几个方法
1、构造器方法
2、init 初始化方法
第 1,2 步,在 web 工程启动的时候执行(Filter 已经创建)
3、doFilter 过滤方法
第 3 步,每次拦截到请求,就会执行
4、destroy 销毁
第 4 步,停止 web 工程的时候,就会执行(停止 web 工程,也会销毁 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 结尾才会拦截到
<url-pattern>*.do</url-pattern>
以上配置的路径,表示请求地址必须以.do 结尾才会拦截到
<url-pattern>*.action</url-pattern>
以上配置的路径,表示请求地址必须以.action 结尾才会拦截
- 创建
ManagerFilter
实现类
public class ManagerFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
Object user = httpServletRequest.getSession().getAttribute("user");
if (user == null) {
httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
- 配置
web.xml
文件
<filter>
<filter-name>ManagerFilter</filter-name>
<filter-class>com.atguigu.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>
Filter和ThreadLocal组合管理事务
ThreadLocal
的作用
ThreadLocal 的作用,它可以解决多线程的数据安全问题。
ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
ThreadLocal 的特点:
1、ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)
2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用 多个
ThreadLocal 对象实例。
3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放
- 使用
ThreadLocal
来确保所有dao
操作都在同一个Connection
连接对象中完成。
JdbcUtils
工具类的修改
public class JdbcUtils {
private static DruidDataSource dataSource;
private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
static {
try {
Properties properties = new Properties();
// 读取jdbc.properties文件
InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 从流中加载数据
properties.load(inputStream);
// 创建数据库连接池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e)
{
e.printStackTrace();
}
}
/**
* 获取数据库连接池中的连接
* @return 如果返回null,说明获取连接失败 <br/>有值就是成功
*/
public static Connection getConnection(){
Connection conn = conns.get();
if (conn == null) {
try {
conn = dataSource.getConnection(); // 从数据库连接池中获取连接
conns.set(conn); // 保存到ThreadLocal对象中,供后面的jdbc操作使用
conn.setAutoCommit(false); // 设置为手动提交
} catch (Exception e) {
e.printStackTrace();
}
}
return conn;
}
/**
* 提交事务,并关闭释放连接
*/
public static void commitAndClose() {
Connection connection = conns.get();
if (connection != null) { // 如果不等于null,说明之前使用过连接,操作过数据库
try {
connection.commit(); // 提交事务
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
// 一定要执行remove操作,否则就会出错(因为Tomcat服务器底层使用了线程池技术)
conns.remove();
}
public static void rollbackAndClose(){
Connection connection = conns.get();
if (connection != null) { //如果不等于null,说明之前使用过连接,操作过数据库
try {
connection.rollback(); // 回滚事务
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
connection.close(); //关闭连接释放资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
// 一定要执行remove操作,否则就会出错(因为Tomcat底层使用了线程池技术)
conns.remove();
}
//
// /**
// * 关闭连接,放回数据库连接池
// * @param conn
// */
// public static void close(Connection conn){
// if(conn != null)
// {
// try {
// conn.close();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// }
}
修改BaseDao
public abstract class BaseDao {
// 使用DbUtils操作数据库
private QueryRunner queryRunner = new QueryRunner();
/**
* update() 方法用来执行:Insert\Update\Delete语句
* @return 如果返回-1说明执行失败<br/>返回其它表示影响的行数
*/
public int update(String sql, Object ... args){
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.update(connection, sql, args);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 查询返回一个javaBean的sql语句
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回的类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type, String sql, Object ... args){
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con, sql, new BeanHandler<T>(type), args);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 查询返回多个javaBean的sql语句
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回的类型的泛型
* @return
*/
public <T> List<T> queryForList(Class<T> type, String sql, Object ... args){
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con, sql, new BeanListHandler<T>(type), args);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 执行返回一行一列的sql语句
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @return
*/
public Object queryForSingleValue(String sql, Object ... args){
Connection conn = JdbcUtils.getConnection();
try {
return queryRunner.query(conn, sql, new ScalarHandler(), args);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
- 使用
Filter
过滤器统一给所有的Service
方法都加上try-catch
。来进行实现的管理。
Filter
类代码
public class TransactionFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try{
filterChain.doFilter(servletRequest, servletResponse);
JdbcUtils.commitAndClose(); // 提交事务
} catch (Exception e) {
JdbcUtils.rollbackAndClose(); // 回滚事务
e.printStackTrace();
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
配置web.xml
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.atguigu.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!-- /* 表示当前工程下所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
一定要记得把BaseServlet
中的异常往外抛给Filter
过滤器
public abstract class BaseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 解决post请求中文乱码问题
// 一定要在获取请求参数之前调用才有效
req.setCharacterEncoding("UTF-8");
String action = req.getParameter("action");
try {
// 获取action业务鉴别字符串,获得相应的业务 方法反射对象
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
// 调用目标业务 方法
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e); // 把异常抛给Filter过滤器
}
}
}
Tomcat管理异常
我们将所有异常都统一交给 Tomcat
,让Tomcat
展示友好的错误信息页面,这样用户就不用面对一大堆问题代码了。
- 在
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-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" %>
<html>
<head>
<title>服务器出错啦</title>
<%-- 静态包含 base标签,css样式,jQuery文件 --%>
<%@ include file="/pages/common/head.jsp"%>
</head>
<body>
服务器出错啦,程序员小哥正在加紧抢修。<br/>
<a href="index.jsp">返回首页</a>
</body>
</html>
error404.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>访问资源不存在</title>
<%-- 静态包含 base标签,css样式,jQuery文件 --%>
<%@ include file="/pages/common/head.jsp"%>
</head>
<body>
您访问的资源不存在,或已被删除<br/>
<a href="index.jsp">返回首页</a>
</body>
</html>
TransactionFilter
要把异常抛给Tomcat
使用AJAX验证用户名是否可用
- 图解验证用户名是否可用流程
- 导入相关
jar
包
gson-2.2.4.jar
UserServlet
程序添加ajaxExistsUsername
方法
/**
* ajax请求判断用户名是否存在
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void ajaxExistUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数username
String username = req.getParameter("username");
// 调用userService.existUsername()
boolean existsUsername = userService.existsUsername(username);
// 把返回的结果封装为map对象
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("existsUsername", existsUsername);
Gson gson= new Gson();
String json = gson.toJson(resultMap);
resp.getWriter().write(json);
}
- 修改
regist.jsp
中的代码
// 给用户名绑定失去焦点事件
$("#username").blur(function () {
// 1. 获取用户名
var username = this.value;
//2 创建正则表达式对象
var usernamePatt = /^\w{5,12}$/;
//3 使用test方法验证
if(!usernamePatt.test(username)) { // 用户名不合法
//4 提示用户结果
$("span.errorMsg").text("用户名不合法!");
} else{
// 用户名合法判断用户名是否存在
// alert("用户名合法");
$.getJSON("${pageScope.basePath}userServlet","action=ajaxExistsUsername&username=" + username,
function (data) {
if(data.existsUsername) { // 用户名存在
$("span.errorMsg").text("用户名已存在!");
} else { // 用户名可用
$("span.errorMsg").text("用户名可用!");
}
});
}
});
AJAX添加商品到购物车
我们之前把商品添加到购物车,是刷新了整个页面,这样用户体验不是很好,我们可用使用Ajax
请求来完成局部的更新。
-
图解商品加入购物车
-
CartServlet
程序添加ajaxAddItem
方法
protected void ajaxAddItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数 商品编号
int id = WebUtils.parseInt(req.getParameter("id"), 0);
// 调用 bookService.queryBookById(id):Book 得到图书的信息
Book book = bookService.queryBookById(id);
// 把图书信息,转换为CartItem商品项
CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
// 调用Cart.addItem(CartItem);添加商品项
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (cart == null) {
cart = new Cart();
req.getSession().setAttribute("cart", cart);
}
cart.addItem(cartItem);
System.out.println(cart);
// 最后一个添加的商品名称
req.getSession().setAttribute("lastName", cartItem.getName());
// 返回购物车总的商品数量和最后一个添加的商品名称
Map<String, Object> resultMap = new HashMap<String, Object>();
resultMap.put("totalCount", cart.getTotalCount());
resultMap.put("lastName", cartItem.getName());
Gson gson = new Gson();
String resultMapJsonString = gson.toJson(resultMap);
resp.getWriter().write(resultMapJsonString);
}
- 修改
index.jsp
页面
- 修改
BaseServlet
程序解决中文乱码问题