书城项目第八阶段:使用Filter过滤器实现后台的权限管理

书城项目第八阶段:使用Filter过滤器实现后台的权限管理

7、书城第八阶段

1、使用Filter过滤器拦截/pages/manager/所有内容,实现权限检查

新建com.atguigu/filter/MangerFilter

package com.atguigu.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class ManageFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @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 destroy() {

    }
}

配置web.xml

<filter>
        <filter-name>ManageFilter</filter-name>
        <filter-class>com.atguigu.filter.ManageFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ManageFilter</filter-name>
        <url-pattern>/pages/manager/*</url-pattern>
        <url-pattern>/manager/bookServlet</url-pattern>
    </filter-mapping>

2、ThreadLocal的使用

ThreadLocal的作用,它可以解决多线程的数据安全问题。
ThreadLocal它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
Threadlocal的特点:
1、Threadlocal可以为当前线程关联一个数据。(它可以像Map一样存取数据,key为当前线程)
2、每一个Threadlocal对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个 Threadlocal对象实例。
3、每个Threadlocal对象实例定义的时候,一般都是static类型
4、Threadlocal中保存数据,在线程销毁后。会由JVM虚拟自动释放。

新建tmp/src/threadlocal/ThreadLocalTest

package threadlocal;

import java.util.Random;

public class ThreadLocalTest {
//    public final static Map<String,Object> data=new ConcurrentHashMap<>();线程安全
//    public final static Map<String,Object> data=new Hashtable<>();
    public static ThreadLocal<Object> threadLocal=new ThreadLocal<>();
    private static Random random=new Random();
    public static class Task implements Runnable{

        @Override
        public void run() {

//            threadLocal.set("abc");
//            threadLocal.set("bbj");
//            System.out.println(threadLocal.get());//bbj 覆盖
            


            //在run方法中 ,随机生成一个变量(线程要关联的数据),然后一当前线程名为key保存到map中
            Integer i = random.nextInt(1000);
            //获取当前线程名
            String name = Thread.currentThread().getName();
            System.out.println("线程["+name+"]生成的随机数是:"+i);
//            data.put(name,i);
            threadLocal.set(i);
            //模拟操作
//            try {
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }

            new OrderService().createOrder();

            //在Run方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作
//            Object o = data.get(name);
            Object o=threadLocal.get();
            System.out.println("线程["+name+"]快结束时取出关联的数据是:"+o);

        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Task()).start();
        }
    }
}

新建threadlocal/OrderService

package threadlocal;

public class OrderService {
    public void createOrder(){
        String name = Thread.currentThread().getName();
        System.out.println("OrderService 当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.threadLocal.get());
        new OrderDao().saveOrder();

    }
}

新建threadlocal/OrderDao

package threadlocal;

public class OrderDao {
    public void saveOrder(){
        String name = Thread.currentThread().getName();
//        System.out.println("OrderDao 当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.data.get(name));
        System.out.println("OrderDao 当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.threadLocal.get());

    }
}

结果

书城项目第八阶段:使用Filter过滤器实现后台的权限管理

3、使用Filter和ThreadLocal组合管理事务

3.1 使用ThreadLocal确保所有操作都使用同一个Connection来实现

验证是否为同一线程

书城项目第八阶段:使用Filter过滤器实现后台的权限管理

修改 OrderServiceImpl 模拟错误

package com.atguigu.service.impl;

import com.atguigu.dao.BookDao;
import com.atguigu.dao.OrderDao;
import com.atguigu.dao.OrderItemDao;
import com.atguigu.dao.impl.BookDaoImpl;
import com.atguigu.dao.impl.OrderDaoImpl;
import com.atguigu.dao.impl.OrderItemDaoImpl;
import com.atguigu.pojo.*;
import com.atguigu.service.OrderService;

import java.util.Date;
import java.util.List;
import java.util.Map;

public class OrderServiceImpl implements OrderService {
    private OrderDao orderDao =new OrderDaoImpl();
    private OrderItemDao orderItemDao=new OrderItemDaoImpl();
    private BookDao bookDao=new BookDaoImpl();
    @Override
    public String createOrder(Cart cart, Integer userId) {

        System.out.println("OrderServiceImpl 程序在["+Thread.currentThread().getName()+"]中");


        //订单号==唯一性
        String orderId=System.currentTimeMillis()+""+userId;
        //创建一个订单对象
        Order order=new Order(orderId,new Date(),cart.getTotalPrice(),0,userId);
        //保存订单
        orderDao.saveOrder(order);

        //模拟错误
        int i=12/0;

        //遍历购物车中每一个商品项转换为订单保存到数据库
        for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()) {
            //获取购物车每一个商品项
            CartItem cartItem=entry.getValue();
            //转换为订单
            OrderItem orderItem=new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId);
            //保存到数据库
            orderItemDao.saveOrderItem(orderItem);

            //更新库存和销量
            Book book = bookDao.queryBookById(cartItem.getId());
            book.setSales(book.getSales()+cartItem.getCount());
            book.setStock(book.getStock()-cartItem.getCount());
            bookDao.updateBook(book);
        }
        //清空购物车
        cart.clear();

        return orderId;
    }

    @Override
    public List<Order> showAllOrders() {
        return orderDao.queryOrders();
    }

    @Override
    public int sendOrder(String orderId) {
        return orderDao.changeOrderStatus(orderId,1);
    }

    @Override
    public List<OrderItem> showOrderDetail(String orderId) {
        return orderItemDao.queryOrderItemByOrderId(orderId);
    }

    @Override
    public List<Order> showMyOrders(int userId) {
        return orderDao.queryByUserId(userId);
    }

    @Override
    public int receiverOrder(String orderId) {
        return orderDao.changeOrderStatus(orderId,2);
    }

}

结果 网页,添加购物车去结账

t_order表无记录
t_order_item有记录

原理

书城项目第八阶段:使用Filter过滤器实现后台的权限管理

修改 JdbcUtils

package com.atguigu.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;

public class JdbcUtils {

    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns=new ThreadLocal<>();
    static {

        try {
            Properties properties=new Properties();
            //读取jdbc.properties属性配置文件
            InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //从流中加载数据
            properties.load(inputStream);
            //创建数据连接池
            dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);//Ctrl+ALT+T

        } 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 (SQLException throwables) {
                throwables.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 (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
         */

}

修改 JdbcUtilsTest

package com.atguigu.test;

import org.junit.Test;

public class JdbcUtilsTest {
    @Test
    public void testJdbcUtils(){
//        for (int i = 0; i < 100; i++) {
//            Connection connection = JdbcUtils.getConnection();
//            System.out.println(connection);
//            JdbcUtils.close(connection);
//        }
    }

}

修改 BaseDao

package com.atguigu.dao;

import com.atguigu.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;

public abstract class BaseDao {

    //使用DbUtils操作数据库
    private QueryRunner queryRunner=new QueryRunner();

    /**
     * update() 方法用来执行,Insert\Update\Delete语句
     * @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
     */
    public int update(String sql,Object... args){
        System.out.println("BaseDao 程序在["+Thread.currentThread().getName()+"]中");

        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 (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }



}

修改 OrderServlet

package com.atguigu.web;

import com.atguigu.pojo.Cart;
import com.atguigu.pojo.User;
import com.atguigu.service.OrderService;
import com.atguigu.service.impl.OrderServiceImpl;
import com.atguigu.utils.JdbcUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class OrderServlet extends BaseServlet {

    private OrderService orderService = new OrderServiceImpl();

    /**
     * 生成订单
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //先获取Cart购物车对象
        Cart cart = (Cart) req.getSession().getAttribute("cart");  //获取Userid
        User loginUser = (User) req.getSession().getAttribute("user");
        if (loginUser == null) {
            req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
            return;
        }

        System.out.println("OrderServlet 程序在["+Thread.currentThread().getName()+"]中");

        Integer userId = loginUser.getId();
        //调用orderservice.createorder(cart,userid);生成订单

        String orderId = null;
        try {
            orderId = orderService.createOrder(cart, userId);
            JdbcUtils.commitAndClose();//提交事务
        } catch (Exception e) {
            JdbcUtils.rollbackAndClose();//回滚事务
            e.printStackTrace();
        }

//        req.setAttribute("orderId",orderId);
        //请求转发至pages/cart/checkout.jsp
//        req.getRequestDispatcher("/pages/cart/checkout.jsp").forward(req,resp);

        req.getSession().setAttribute("orderId", orderId);
        resp.sendRedirect(req.getContextPath() + "/pages/cart/checkout.jsp");
    }

    /**
     * 查看所有订单
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void showAllOrders(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 发货
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void sendOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 查看订单详情
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void showOrderDetail(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 查看我的订单
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void showMyOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 签收订单/确认收货
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void receiverOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }




}

结果 网页,添加购物车去结账

书城项目第八阶段:使用Filter过滤器实现后台的权限管理

3.2、使用Filter统一给所有Service方法都加上try-catch来实现管理事务

书城项目第八阶段:使用Filter过滤器实现后台的权限管理

新建 filter/TransactionFilter

package com.atguigu.web;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

public abstract class BaseServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action=req.getParameter("action");
//        System.out.println(action);


        //action的value和调用的方法名是统一的
//        if ("login".equals(action)){
            System.out.println("处理登录的需求");
//           login(req,resp);
//        }else if ("regist".equals(action)){
            System.out.println("处理注册的需求");
//            regist(req,resp);
//        }

        //反射
        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过滤器
        }


    }



}

配置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>

修改 OrderServlet

package com.atguigu.web;

import com.atguigu.pojo.Cart;
import com.atguigu.pojo.User;
import com.atguigu.service.OrderService;
import com.atguigu.service.impl.OrderServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class OrderServlet extends BaseServlet {

    private OrderService orderService = new OrderServiceImpl();

    /**
     * 生成订单
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //先获取Cart购物车对象
        Cart cart = (Cart) req.getSession().getAttribute("cart");  //获取Userid
        User loginUser = (User) req.getSession().getAttribute("user");
        if (loginUser == null) {
            req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
            return;
        }

        System.out.println("OrderServlet 程序在["+Thread.currentThread().getName()+"]中");

        Integer userId = loginUser.getId();
        //调用orderservice.createorder(cart,userid);生成订单

        String orderId = orderService.createOrder(cart, userId);


//        req.setAttribute("orderId",orderId);
        //请求转发至pages/cart/checkout.jsp
//        req.getRequestDispatcher("/pages/cart/checkout.jsp").forward(req,resp);

        req.getSession().setAttribute("orderId", orderId);
        resp.sendRedirect(req.getContextPath() + "/pages/cart/checkout.jsp");
    }

    /**
     * 查看所有订单
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void showAllOrders(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 发货
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void sendOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 查看订单详情
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void showOrderDetail(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 查看我的订单
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void showMyOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    /**
     * 签收订单/确认收货
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void receiverOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }




}

修改 BaseServlet

package com.atguigu.web;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

public abstract class BaseServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action=req.getParameter("action");
//        System.out.println(action);


        //action的value和调用的方法名是统一的
//        if ("login".equals(action)){
            System.out.println("处理登录的需求");
//           login(req,resp);
//        }else if ("regist".equals(action)){
            System.out.println("处理注册的需求");
//            regist(req,resp);
//        }

        //反射
        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过滤器
        }


    }



}

3.3、将所有异常都交给Tomcat,让Tomcat显示友好的错误信息页面

在web.xml 中我们可以通过错误页面配置来进行管理。

新建 pages/error/error404.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>纺织用品</title>

    <%--静态包含 base标签,css样式,jquery文件 --%>
    <%@ include file="/pages/common/head.jsp"%>

</head>
<body>
很抱歉,您访问的页面不存在,或已经被删除!!!<br>
<a href="index.jsp">返回首页</a>
</body>
</html>

并配置 web.xml

 <!-- error-page标签配置,服务器出错之后,自动跳转的页面   -->
    <error-page>
        <!--  error-code是错误类型      -->
        <error-code>500</error-code>
        <!-- location标签表示,要跳转的页面路径-->
        <location>/pages/error/error500.jsp</location>
    </error-page>

修改 TransactionFilter

package com.atguigu.filter;

import com.atguigu.utils.JdbcUtils;

import javax.servlet.*;
import java.io.IOException;

public class TransactionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @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();
            throw new RuntimeException(e);//把异常抛给Tomcat管理展示友好的错误页面
        }
    }

    @Override
    public void destroy() {

    }
}

新建error/error404.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>纺织用品</title>

    <%--静态包含 base标签,css样式,jquery文件 --%>
    <%@ include file="/pages/common/head.jsp"%>

</head>
<body>
很抱歉,您访问的页面不存在,或已经被删除!!!<br>
<a href="index.jsp">返回首页</a>
</body>
</html>

配置web.xml

<!-- error-page标签配置,服务器出错之后,自动跳转的页面   -->
    <error-page>
        <!--  error-code是错误类型      -->
        <error-code>404</error-code>
        <!-- location标签表示,要跳转的页面路径-->
        <location>/pages/error/error404.jsp</location>
    </error-page>
上一篇:java的throw和throws异常区别


下一篇:springboot2.x玩转quartz