JavaWeb第八天

该文档是:JavaWeb学习

博客连接:https://www.loveuluo.cn

日期:2020-12-17

1. 什么是 Cookie

1、Cookie 翻译过来是饼干的意思。
2、Cookie 是服务器通知客户端保存键值对的一种技术。
3、客户端有了 Cookie 后,每次请求都发送给服务器。
4、每个 Cookie 的大小不能超过 4kb

2. 如何创建 Cookie

image-20201217160303873

Servlet 程序中的代码:

public void createCookie(HttpServletRequest req,HttpServletResponse res) throws IOException {
    //1.创建Cookie对象
    Cookie cookie=new Cookie("key4","value4");
    //2.通知客户端保存Cookie
    res.addCookie(cookie);
    //1.创建Cookie对象
    Cookie cookie1=new Cookie("key5","value5");
    //2.通知客户端保存Cookie
    res.addCookie(cookie1);

    res.getWriter().write("Cookie创建成功");
}

3. 服务器如何获取Cookie

图解:

image-20201217162333978

Servlet代码:

服务器获取客户端的 Cookie 只需要一行代码:req.getCookies()。

public void getCookie(HttpServletRequest req,HttpServletResponse res) throws IOException {
    //只能得到所有Cookie的数组,不能按值获得单个
    Cookie[] cookies = req.getCookies();

    //因为只能获取cookie数组,可以现在外边定义一个iWantCookie
    Cookie iWantCookie=null;

    //遍历cookies数组 找到想要的cookie然后赋值给iWantCookie
    for (Cookie cookie : cookies) {
        //.getName()可以得到Cookie的key(名字)
        if ("key4".equals(cookie.getName())){
            //找到并成功赋值之后跳出循环
            iWantCookie=cookie;
            break;
        }
    }
    if (iWantCookie!=null){ //这个不是空的说明找到了需要的Cookie
        res.getWriter().write("找到了我需要的Cookie:<br>");
        //.getValue()可以得到Cookie的value(值)
        res.getWriter().write("我要的Cookie的key是:"+iWantCookie.getName()+",value是:"+iWantCookie.getValue());
    }
}

发现这个查找想要的Cookie代码通用,做成一个工具类:

public class CookieUtils {
    /**
     * 查找指定名称的 Cookie 对象
     * @param name
     * @param cookies
     * @return
     */
    public static Cookie findCookie(String name,Cookie[] cookies){
        if (name == null || cookies == null || cookies.length == 0){
            return null;
        }
        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())){
                return cookie;
            }
        }
        return null;
    }
}

4. Cookie 值的修改

方案一:

//1、先创建一个要修改的同名(指的就是 key)的 Cookie 对象
//2、在构造器,同时赋于新的 Cookie 值。
Cookie cookie=new Cookie("key4","newValue4");
//3、调用 response.addCookie( Cookie );
res.addCookie(cookie);

方案二:

//1、先查找到需要修改的 Cookie 对象
Cookie[] cookies = req.getCookies();
Cookie key5 = CookieUtils.findCookie("key5", cookies);
//2、调用 setValue()方法赋于新的 Cookie 值。
if (key5!=null){
    key5.setValue("newValue5");
    //3、调用 response.adCookie()通知客户端保存修改
    res.addCookie(key5);
}

5. 浏览器查看Cookie

谷歌浏览器如何查看 Cookie:

image-20201217165724156

火狐浏览器何查看 Cookie:

6. Cookie 生命控制

Cookie 的生命控制指的是如何管理 Cookie 什么时候被销毁(删除)
setMaxAge();传值:
正数,表示在指定的秒数后过期
负数,表示浏览器一关,Cookie 就会被删除(默认值是-1)
零,表示马上删除 Cookie

/**
 * 设置存活 1 个小时的 Cooie
 * @param req
 * @param res
 * @throws IOException
 */
public void life3600(HttpServletRequest req,HttpServletResponse res) throws IOException {
Cookie cookie=new Cookie("life3600","life3600");
cookie.setMaxAge(60*60); //设置Cookie,一个小时候后被删除
res.addCookie(cookie);
res.getWriter().write("已经创建了存活一个小时的Cookie");
}

/**
 * 马上删除一个 Cookie
 * @param req
 * @param res
 * @throws IOException
 */
public void delteNow(HttpServletRequest req,HttpServletResponse res) throws IOException {
    Cookie[] cookies = req.getCookies();
    Cookie key4 = CookieUtils.findCookie("key4", cookies);
    if (key4!=null) {
        key4.setMaxAge(0); //设置立马删除当前Cookie
        res.addCookie(key4);
        res.getWriter().write("key4的Cookie已被删除");
    }
}

/**
 * 默认的会话级别的 Cookie
 * @param req
 * @param res
 * @throws IOException
 */
public void defaultLife(HttpServletRequest req,HttpServletResponse res) throws IOException {
    Cookie cookie=new Cookie("defaultLife","defaultLife");
    cookie.setMaxAge(-1); //设置Cookie,浏览器一关闭Cookie就删除(不设置默认也是这个)
    res.addCookie(cookie);
    res.getWriter().write("创建一个会话级别的Cookie");
}

7. Cookie 有效路径 Path 的设置

image-20201217185429172

public void testPath(HttpServletRequest req,HttpServletResponse resp) throws IOException {
    Cookie cookie = new Cookie("path1", "path1");
    // getContextPath() =>> 得到工程路径
    cookie.setPath( req.getContextPath() + "/abc" ); // =>> /工程路径/abc
    resp.addCookie(cookie);
    resp.getWriter().write("创建了一个带有 Path 路径的 Cookie");
}

8. Cookie练习--记住用户名

图解:

image-20201217192756998

login.jsp页面:

image-20201217192738680

LoginServlet 程序:

public class LoginServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        if ("wzg168".equals(username) && "123456".equals(password)) {
            //登录 成功
            Cookie cookie = new Cookie("username", username);
            cookie.setMaxAge(60 * 60 * 24 * 7);//当前Cookie一周内有效
            resp.addCookie(cookie);
            System.out.println("登录 成功");
        } else {
            //登录 失败
            System.out.println("登录 失败");
        }

    }
}

9. 什么是 Session

1、Session 就一个接口(HttpSession)。
2、Session 就是会话。它是用来维护一个客户端和服务器之间关联的一种技术。
3、每个客户端都有自己的一个 Session 会话。
4、Session 会话中,我们经常用来保存用户登录之后的信息。

10. 如何创建Session 和获取(id 号,是否为新)

如何创建和获取Session。它们的API 是一样的。
request.getSession()

--第一次调用是:创建Session 会话

--之后调用都是:获取前面创建好的Session 会话对象。
isNew(); 判断到底是不是刚创建出来的(新的)

-- true 表示刚创建

--false 表示获取之前创建
每个会话都有一个身份证号。也就是ID 值。而且这个ID 是唯一的。
getId() 得到Session 的会话id 值。

代码:

public void createOrGetSession(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建和获取Session会话对象
        HttpSession session = req.getSession();
        // 判断 当前Session会话,是否是新创建出来的
        boolean isNew = session.isNew();
        // 获取Session会话的唯一标识 id
        String id = session.getId();

        resp.getWriter().write("得到的Session,它的id是:" + id + " <br /> ");
        resp.getWriter().write("这个Session是否是新创建的:" + isNew + " <br /> ");
}

11. Session 域数据的存取

    /**
     * 往Session中保存数据
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void setAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getSession().setAttribute("key1", "value1");
        resp.getWriter().write("已经往Session中保存了数据");
    }
    
        /**
     * 获取Session域中的数据
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void getAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Object attribute = req.getSession().getAttribute("key1");
        resp.getWriter().write("从Session中获取出key1的数据是:" + attribute);
    }

12. Session 生命周期控制

image-20201217205301030

如果说。你希望你的web 工程,默认的Session 的超时时长为其他时长。你可以在你自己的web.xml 配置文件中做以上相同的配置。就可以修改你的web 工程所有Seession 的默认超时时长

image-20201217205321565

如果你想只修改个别Session 的超时时长。就可以使用上面的API。setMaxInactiveInterval(int interval)来进行单独的设置:

session.setMaxInactiveInterval(int interval)单独设置超时时长。

Session 超时的概念介绍:

image-20201217205436891

示例代码:

protected void life3(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
    // 先获取Session 对象
    HttpSession session = req.getSession();
    // 设置当前Session3 秒后超时
    session.setMaxInactiveInterval(3);
    resp.getWriter().write("当前Session 已经设置为3 秒后超时");
}

Session 马上被超时示例:

protected void deleteNow(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
    // 先获取Session 对象
    HttpSession session = req.getSession();
    // 让Session 会话马上超时
    session.invalidate();
    resp.getWriter().write("Session 已经设置为超时(无效)");
}

13. 浏览器和Session 之间关联的技术内幕

Session 技术,底层其实是基于Cookie 技术来实现的。

服务器内存中存储的多个Session是对应的不同的客户端(浏览器),而每个客户端(浏览器)只对应一个Session的ID。所以通过Set-Cookie创建一个Cookie对象,而key永远是JSESSIONID,而值是新创建出来的Session值(通过响应把新创建出来的存放Session的Cookie返回给客户端)。当客户端有了这个Cookie之后,因为客户端(浏览器)访问服务器的时候会自动把Cookie发给服务器,所以当服务器收到了Cookie,调用GetSession方法,他发现Cookie里有一个JSESSIONID就知道存在了Session,此时获取这个id为JSESSIONID的Cookie的值,也就是得到了Session对象。

如果把这个存放Session对象的Cookie在浏览器删除掉的话,那么客户端再次访问服务器的时候,服务器收到的Cookie里发现没有id为JSESSIONID的Cookie,就会认为没有Session对象。那么此时创建一个新的Cookie对象,key是JSESSIONID,值是新创建出来的Session的值。

而为什么一关闭浏览器Session就会消失,那是因为存放这个Session的Cookie的生命默认就是-1(浏览器一关闭就会自动销毁)。

image-20201218095114460

14. 什么是Filter-过滤器

image-20201221113445891

15. Filter 的初体验

要求:在你的web 工程下,有一个admin 目录。这个admin 目录下的所有资源(html 页面、jpg 图片、jsp 文件、等等)都必
须是用户登录之后才允许访问。
思考:根据之前我们学过内容。我们知道,用户登录之后都会把用户登录的信息保存到Session 域中。所以要检查用户是否
登录,可以判断Session 中否包含有用户登录的信息即可!!!

这是原先在jsp里的做法,不过html或者图片呢?不能这么做,只能使用过滤器:

image-20201221120708006

路径:

image-20201221120959516

Filter 的工作流程图:

image-20201221120747661

Filter 的代码:

public class AdminFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    /**
     * doFilter 方法,专门用于拦截请求。可以做权限检查
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //将servletRequest强转为HttpServletRequest
        HttpServletRequest req= (HttpServletRequest) servletRequest;
        //从Session中获取user对象
        Object user = req.getSession().getAttribute("user");
        //如果是空的代表没登录
        if (user==null){
            req.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
            return;
        }else{
            //让程序继续往下访问用户的目标资源
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }

    @Override
    public void destroy() {
    }
}

web.xml 中的配置:

<!--filter 标签用于配置一个Filter 过滤器-->
<filter>
    <!--给filter 起一个别名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置filter 的全类名-->
    <filter-class>com.Luo.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 目录
    /admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
    -->
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

Filter 过滤器的使用步骤:
1、编写一个类去实现Filter 接口
2、实现过滤方法doFilter()
3、到web.xml 中去配置Filter 的拦截路径

16. Filter 的生命周期

image-20201221143056563

17. FilterConfig 类

FilterConfig 类见名知义,它是Filter 过滤器的配置文件类。
Tomcat 每次创建Filter 的时候,也会同时创建一个FilterConfig 类,这里包含了Filter 配置文件的配置信息。

FilterConfig 类的作用是获取filter 过滤器的配置内容:
1、获取Filter 的名称filter-name 的内容
2、获取在Filter 中配置的init-param 初始化参数
3、获取ServletContext 对象

java 代码:
image-20201221143450472

web.xml 配置:

image-20201221143502646

18. FilterChain 过滤器链

Filter 过滤器
Chain 链,链条
FilterChain 就是过滤器链(多个过滤器如何一起工作)

image-20201221145343141

19. Filter 的拦截路径

image-20201221145824841

20. ThreadLocal的使用

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

测试类:

public class OrderDao {
    public void saveOrder(){
        String name = Thread.currentThread().getName();
        System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是:" + ThreadLocalTest.threadLocal.get());
    }
}
public class OrderService {
    public void createOrder(){
        String name = Thread.currentThread().getName();
        System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" + ThreadLocalTest.threadLocal.get());
        new OrderDao().saveOrder();
    }
}
public class ThreadLocalTest {
    // public static Map<String,Object> data = new Hashtable<String,Object>();
    public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
    private static Random random = new Random();
    public static class Task implements Runnable {
        @Override
        public void run() {
            // 在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();
        }
    }
}

结果是:

image-20201221154532490

21. 使用Filter 和ThreadLocal 组合管理事务

21.1 使用ThreadLocal 来确保所有dao 操作都在同一个Connection 连接对象中完成

原理分析图:

image-20201221163611366

JdbcUtils 工具类:

把连接放入ThreadLocal中,key是当前线程,值就是con连接。

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 (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }

    /**
     * 提交事务,并关闭释放连接
     */
    public static void commitAndClose() {
        Connection connection = conns.get();
        if (connection != null) { // 如果不等于null,说明之前使用过连接,操作过数据库
            try {
                connection.commit(); // 提交事务
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    connection.close(); // 关闭连接,资源资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        // 一定要执行remove 操作,否则就会出错。(因为Tomcat 服务器底层使用了线程池技术)
        conns.remove();
    }

    /**
     * 回滚事务,并关闭释放连接
     */
    public static void rollbackAndClose() {
        Connection connection = conns.get();
        if (connection != null) { // 如果不等于null,说明之前使用过连接,操作过数据库
            try {
                connection.rollback();//回滚事务
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    connection.close(); // 关闭连接,资源资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        // 一定要执行remove 操作,否则就会出错。(因为Tomcat 服务器底层使用了线程池技术)
        conns.remove();
    }
}

BaseDao:

Dao中异常必须往外抛出去,不然别的无法捕获到异常,无法捕获到就不能做出遇到异常时要回滚的操作!!例如将整个Service操作进行try-catch,而当dao有报错时,那么try-catch就能捕获到Dao的出错,此时就能做出事务回滚操作!!!(可以在下边的OrderServlet中查看详细操作)

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);
        }
    }
}

这里以书城项目中的OrderService为例讲解:

此时例如OrderService中执行创建订单的功能,他这里同时调用了BookDao和OrderDao和OrderItemDao,如何确保他们调用的是同一个连接对象?这里的BookDao和OrderDao和OrderItemDao都继承了BaseDao,所以此时都是调用了BaseDao中的方法。而例如第一次orderDao.saveOrder(order);执行了,那么此时获取数据库连接,BaseDao中调用了JDBCUtils获取连接,在JDBCUtils的获取连接的方法中发现因为此时还没有获取过连接,连接是null,所以创建连接并把连接保存到ThreadLocal 对象中。完成操作后继续执行,此时orderItemDao.saveOrderItem(orderItem);执行了,同样进入JDBCUtils获取连接,但是此时因为之前已经把连接保存到ThreadLocal 对象中,所以可以直接get到之前用的连接,那么就是同一个。

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;
    }
}

OrderServlet:

对应BaseDao中的异常抛出,把整个service操作try-catch,当有异常从Dao层抛出时,这里就能抓取到,那么就进行回滚事务并且归还连接的操作。

public class OrderServlet extends BaseServlet {

    private OrderService orderService = new OrderServiceImpl();

    /**
     * 生成订单
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected 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();
        String orderId = null;
        try {
            //        调用orderService.createOrder(Cart,Userid);生成订单
            orderId = orderService.createOrder(cart, userId);

            JDBCUtils.commitAndClose();//提交事务

        } catch (Exception e) {
            JDBCUtils.rollbackAndClose();//回滚事务
            e.printStackTrace();
        }

        req.getSession().setAttribute("orderId",orderId);

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

}

21.2 使用Filter 过滤器统一给所有的Service 方法都加上try-catch。来进行实现的管理

如果每个Servlet中每一个方法调用Service都要加上try-catch就太麻烦了。

原理分析图:

image-20201221200616761

Filter 类代码:

public class TransactionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChainfilterChain) throws IOException, ServletException {
        try {
        filterChain.doFilter(servletRequest,servletResponse);
        JdbcUtils.commitAndClose();// 提交事务
        } catch (Exception e) {
        JdbcUtils.rollbackAndClose();//回滚事务
        e.printStackTrace();
        }
    }
}

在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 过滤器:

因为dao抛出的异常会被BaseServlet抓住,所以在BaseServlet中也必须抛出异常。

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 {
        // 解决post 请求中文乱码问题
        // 一定要在获取请求参数之前调用才有效
        req.setCharacterEncoding("UTF-8");
        String action = req.getParameter("action");
        try {
            // 获取action 业务鉴别字符串,获取相应的业务方法反射对象
            Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class,
                    HttpServletResponse.class);
            // System.out.println(method);
            // 调用目标业务方法
            method.invoke(this, req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);// 把异常抛给Filter 过滤器
        }
    }
}
最后修改:2020 年 12 月 21 日 08 : 34 PM
如果觉得我的文章对你有用,请随意赞赏