老杜JavaWeb学习笔记:下

JSP技术

 

一、为什么需要JSP技术

【存在问题】

原先仅使用Servlet开发的项目,需要利用Servlet的request对象的out.print方法将html标签输出到浏览器上,导致Servlet类中存在众多html代码。

简而言之,java程序中编写html代码存在以下问题:

  • 编写难度大,麻烦
    • 无报错提示
    • 每次调试都需要编译java,生成class文件
  • 代码耦合度过高
  • 代码不美观
  • 维护成本高

1682243578640

【解决方案】

  • 思考:

    能否让程序员只需编写Servlet程序中的“前端代码“,然后让机器将我们写的“前端代码”自动翻译生成“Servlet”这种“java”程序。再自动将“java程序”编译成“class”程序,再使用JVM调用这个class中的方法。

    ---jsp技术!

二、底层原理

(1)浏览器中访问index.jsp文件

实际上Tomcat会先将index.jsp文件翻译生成index_jsp.java文件,然后进行编译生成index_jsp.class文件,最后执行class文件,显示浏览器页面

(2)JSP实际上是一个Servlet

当index.jsp访问时,会自动翻译生成index_jsp.java,会自动编译生成index_jsp.class,那么index.jsp就是一个类。

inde_jsp继承HttpJspBase,而HttpJspBase类继承HttpSerlvet,所以inde_jsp就是一个Servlet类

1682245141334

1682245171553

(3)jsp生命周期与Servlet生命周期完全相同

  • 两者是一个东西,无区别

  • 两者都是单例的 (假单例)

  • 【jsp生命周期】

    • Servlet创建调用无参构造方法
    • 调用init方法初始化
    • 用户请求一次访问一次service方法
    • 最后销毁时调用destory方法

(3)jsp第一次访问效率较低

  • 为什么运维人员给客户演示时,都提前访问一遍jsp文件?
  • 第一次:
    • 要把jsp文件翻译成java源文件
    • java源文件要编译生成class字节码文件
    • 然后通过class创建servlet对象
    • 调用servlet对象的init方法
    • 调用servlet对象的service方法
  • 第二次:
    • 直接调用单例Servlet对象的service方法

三、JSP概述

3.1 什么是jsp

jsp是一种动态网页开发技术

①JSP是一套规范,所有的web容器/web服务器都遵循该规范进行的翻译。

  • Servlet是javaEE的13个规范之一,JSP也是规范之一

  • 遵循不同规范时,jsp会按照不同的符号输出在jsp文件的不同位置

  • 每个Web容器,所有的服务器都内置一个JSP翻译引擎

②同时JSP是java程序,本质仍是一个Servlet

Tomcat会将jsp文件变为对应的xx_jsp.java文件,编译为xx_jsp.class文件

  • JSP是JavaServer Pages的缩写。
    • 【基于java语言实现的服务器端的页面】
  • 所以对jsp进行错误调试时,仍要打开jsp对应的java文件。检查java代码

③由jsp生成的java文件在哪?

可在Tomcat启动时,打开Tomcat控制台找到CATALINA_BASE,复制其后路径,在本机中访问找到

1682249155834

(开发jsp,最高境界:眼前是jsp,脑里中java代码)

3.2jsp基础语法
3.2.1 jsp输出原理

jsp文件中编写的内容,都会被自动翻译到对应的Java文件,从而编译生成.class文件

  • jsp文件中编写的文字

  • ---翻译到servlet类的service方法的out.write("");中

  • 类似于Servlet中的out.print("")

    • 同时会被当做普通的字符串处理
    • 同时jsp将html/css/js以字符串的形式输出到浏览器后,浏览器会对其进行解释执行,展现页面效果
  • 不同的标签会生成在jsp(生成的java程序)的不同位置

3.2.2 jsp中文乱码

【存在问题】

当在jsp页面中输出中文,会发现页面出现中文乱码问题

1682248601002

【解决方案】

可利用jsp的page指令解决,响应的乱码问题

  • 步骤:在jsp文件上方,添加以下代码设置响应内容类型为UTF-8
<%@page contentType="text/html;charset=UTF-8" %>
<%--表示响应的内容是text/html,采用的字符集为UTF-8--%>
  • 原理:

    添加该指令后,jsp对应的java文件中代码就会相应更改

3.2.3编写java代码
(1)<%%>--翻译到service方法里

①<%xxx%>翻译到哪?

  • xxx会被翻译到其jsp对应的java类的service方法中

②<%xxx%>翻译成了什么?

  • 翻译成: service方法中的一条条语句

③<%%>什么时候用?

用于在jsp页面中编写java语句

  • 当jsp中不添加特殊标记的内容,默认输出为字符串到service方法的out.write("")中

  • 当想在jsp页面中编写java语句,应采用<%%>

<%java语句;%>
  • 该符号中编写的被视作java程序,会被翻译到Servlet类的service内部,而非out.print("")当中

④注意:

  • ❗<%%>写代码,就类似于在service方法体写代码!!
  • service方法不能写静态代码块、不能写方法、不能定义成员变量
  • 同时编写的代码要符合编译顺序,因为就相当于将jsp<%%>中的语句,写入其对应java的service方法里
  • eg: private int i=0;因为方法中只允许有私有化变量/可以写int i =0

1682251722478

(2)<%!%>--翻译到service方法外

①<%!%>翻译到哪里?

  • 翻译到jsp文件生成的java文件的service方法外面

②<%!%>翻译成什么?

  • 翻译成service方法外的一条条语句

③<%!%>什么时候用?

  • 当想将java代码输出jsp对应java程序的service方法外,可以使用<%!%>
    • 使用较少
      • 因为在service方法中写静态变量和实例变量,都会存在线程安全问题,因为jsp就是servlet,而servlet是单例的,多线程并发的环境下,该静态变量和实例变量一旦有修改操作,必然会存在线程安全问题
<%!java代码; %>

1682251729846

【注意】同时使用<%①%>和<%!②%>时,java程序中②在上service方法外,①在下service方法里

(3)<%=%>--翻译到service方法out.print()里

①<%=xxxx%>会被翻译到哪?

  • 其中的xxx会被翻译到jsp其对应java代码的service方法的out.print()中

②<%=xxx%>会被翻译成什么?

  • 翻译成: out.print()
<%=100+200%>
<%--被翻译为out.print(100+200) --%>

③<%=xx%>什么时候用?

  • 当输出的是一个动态变化的内容/含有java变量,使用<%=%>输出
<%  int a = 5;
    int b = 4;
    int i =a+b;
%>
<%=i%>
3.2.5 jsp的注释
  • jsp中的专业注释为:

    <%-- jsp专业注释,不会被翻译到java源代码中 --%>
  • html注释为

    <!--html注释不专业,仍会被翻译到java源代码中-->
3.2.6 jsp输出语句

要向浏览器输出一个java变量,可使用内置对象out的write("")方法

  • jsp代码如下:
    • 此处的out为JSP的九大内置对象之一,可直接在service内部使用。
      • 内置对象,只能在Servlet生成的java程序的service方法中使用哦!
      • 九大内置对象:pageContext、session、application、config、out、page、request、reponse、Exception
<% String name = "jack";out.write("name="+name);%>  
  • 注意:

①固定的内容输出,直接写在jsp文件中即可,不必写到<%%>中,因为jsp文件的内容,本身就会变为ou.write,然后输出到浏览中

②输出的内容若有变量,则利用<%java代码%>的形式展现

③输出内容有java代码,使用以下语法格式:

  • <%=%> 在=号后编写要输出的内容

始终记得:jsp就是servlet,只是职责不同。

3.3jsp和Servlet的区别

jsp的本质就是一个Servlet,但两者职责不同

  • Servlet职责是:收集数据
    • 其强项在于逻辑处理,业务处理,连接数据库,获取/收集数据
  • jsp职责是:展示数据
    • 其强项在于做数据的展示
3.4总结

jsp语法总结:

(1)jsp中直接写代码
  • 翻译到哪?

翻译到jsp生成的java文件serice()方法的out.write("")中

  • 翻译成什么?

翻译成:out.write("");

  • 何时使用?

当输出静态的字符串到浏览器时

如:html标签,不变的标题文字等等

(2)<%%>
  • 翻译到哪?

翻译到jsp生成的java文件service()方法体中

  • 翻译成什么?

一条条语句,写什么翻译为什么,只是放在service()方法体中

  • 何时使用?

要在service()方法体中定义java代码时

(3)<%!%>
  • 翻译到哪?

翻译到jsp生成的java文件service方法体外

  • 翻译成什么?

一条条代码语句,写什么翻译为什么,只是放在service()方法体外

  • 何时使用?

要在service()方法体外定义java代码时

(4)<%=%>
  • 翻译到哪?

翻译到jsp生成的java文件的service方法中的out.print()中

  • 翻译成什么?

翻译成: out.print();

  • 何时使用?

要输出动态的内容

(5)<%@ %>
<%@page contentType="text/html;charset=UTF-8"%>
  • 翻译成什么?

为page指令,通过contentType属性用来设置响应的内容类型

主要用于解决防止乱码

四、利用Servlet+JSP改造纯Servlet的oa项目

4.1未改造前代码

原先未改造的代码,主要利用纯servlet编写,弊端是:

  • 在java后端代码中需要使用很多out.print语句去输出html标签
  • 前后端代码耦合性差
  • 代码繁琐
  • 等....
4.2改造后代码

采用jsp技术,去代替servlet展示html代码,好处是:

  • 前后端代码编写分离
    • jsp会帮助我们将jsp页面中的html代码响应给浏览器
  • 简化代码
    • 省去了许多out.print语句

①创建一个新java工程,右键工程->Add Framework support添加web框架支持

②File->Project Structure->Modules ->选中项目->Dependencies添加servlet,jsp的jar

1683958863658

③Add configurations->添加Tomcat,并进行配置->Deployment 设置项目访问路径

1683958964819

④在工程的web->WEB-INF目录下,新建lib包用于存放数据库连接的jar包

⑤在src中创建utils包,放置DBUtils工具类,用于连接数据库

1683959377031

⑥正式开始编码

  • 首先将所有html文件,变为.jsp文件

  • 同时为防止中文乱码,在文件首行添加<%@page contentType="text/html;charset=UTF-8"%>语句

  • 更改原先所有的html静态路径,变为jsp静态路径

    • 注意页面的路径,需要携带/项目名

    • 因为,当前端发送请求路径是绝对路径时,要以"/"开始,加项目名

      • 同时项目名最好不要写死,在此jsp提供了<%=requet.getContextPat()%>解决这个问题

      • <a href="<%=request.getContextPath()%>/list.jsp">查看</a>
  • 实现页面的正常流转

⑦编码思想,由servlet获取处理数据,jsp去展示

(1)action包下: 编写servlet代码,用于获取数据

  • 1️⃣为简化servlet请求路径配置,采用@WebServlet的servlet注解式开发
@WebServlet({"请求路径,不带项目名"}) 
-写在类名上,表示该类中所有方法的请求路径前缀为/xx/xx
  • 2️⃣为防止一个表增删改查要写多个servlet类,采用将每个Servlet类写成方法的模板方法设计模式开发
servlet中直接重写service方法
-request.getSerlvetPath();//得到servlet请求路径
-if结合"".equals(path);//判断请求路径为xx
-调用对应的doXxx方法
  • 3️⃣利用实体类的思想,存储查询的各字段数据

⑧编写serlvet代码

  • 获取数据,进行处理,跳转至页面
DeptServlet.java代码
  • 将数据库获取的数据存储在javabean中,然后形成数据链表,放入请求域
  • 最后跳转至jsp页面
@WebServlet({"/dept/list","/dept/detail","/dept/delete","/dept/add","/dept/update"})
public class DeptServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
     String servletPath = request.getServletPath();
     if("/dept/list".equals(servletPath)){
         doList(request,response);
     }else if("/dept/detail".equals(servletPath)){
         doDetail(request,response);
     }else if("/dept/delete".equals(servletPath)){
         doDel(request,response);
     }else if("/dept/add".equals(servletPath)){
         doAdd(request,response);
     }else if("/dept/update".equals(servletPath)){
         doUpdate(request,response);
     }
    }

    /**
     * 连接数据库,查询所有部门信息,再跳转至jsp做页面展示
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void  doList(HttpServletRequest request,HttpServletResponse response)
            throws ServletException,IOException{
         //1.连接数据库
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<Dept> deptList = new ArrayList<>();
        try {
            //2.获取连接
            conn = DBUtils.getConnection();
            //3.执行查询语句
            String sql = "select deptno,dname,loc from dept";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while(rs.next()){
                String deptno = rs.getString("deptno");
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");
                //创建一个表实体类对象,将数据放入
                Dept dept = new Dept();
                dept.setDeptno(deptno);
                dept.setDname(dname);
                dept.setLoc(loc);
                //将存放数据的实体类,存入链表
                deptList.add(dept);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //4.释放资源
            DBUtils.close(conn,ps,rs);
        }
        //将最后的链表数据存入请求域中
        request.setAttribute("deptList",deptList);
        //转发,注意不可重定向,否则无法共享请求域数据
        request.getRequestDispatcher("/list.jsp").forward(request,response);
    }
    private void doDetail(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{
        //从请求域中获取deptno
        String deptno = request.getParameter("deptno");
        //获取数据库连接
        Connection conn = null;
        ResultSet rs = null;
        PreparedStatement ps= null;
        Dept dept = new Dept();
        try {
            conn = DBUtils.getConnection();
            String sql = "select dname,loc,principal from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,deptno);
            rs = ps.executeQuery();
            //结果集只有一条数据,不必循环
            if(rs.next()){
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");
                String principal = rs.getString("principal");
                dept.setDeptno(deptno);
                dept.setDname(dname);
                dept.setLoc(loc);
                dept.setPrincipal(principal);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        request.setAttribute("dept",dept);
        String f = request.getParameter("f");
        if(f.equals("m")){//转发到修改页面
            request.getRequestDispatcher("/edit.jsp").forward(request,response);
        }else if(f.equals("d")){//转发到详情页面
            request.getRequestDispatcher("/detail.jsp").forward(request,response);
        }

    }
    private void doDel(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException {
        String deptno = request.getParameter("deptno");
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        int count = 0;
        try {
            conn = DBUtils.getConnection();
            String sql = "delete from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            count = ps.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn, ps, rs);
        }
        if (count == 1) {//删除成功,重定向到列表页面
            //要加项目名,因为此时是重新发起一次请求
            response.sendRedirect(request.getContextPath() + "/dept/list");
        } else {//删除失败
            response.sendRedirect(request.getContextPath() + "/error.jsp");
        }
    }
    private void doAdd(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{
        request.setCharacterEncoding("UTF-8");
        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");
        String principal = request.getParameter("principal");
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        int i = 0;
        try {
            conn = DBUtils.getConnection();
             String sql = "insert into dept(deptno,dname,loc,principal) values (?,?,?,?)";
             ps = conn.prepareStatement(sql);
             ps.setString(1,deptno);
             ps.setString(2,dname);
             ps.setString(3,loc);
             ps.setString(4,principal);
             i = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        if(i == 1){//添加成功
            response.sendRedirect(request.getContextPath() +"/dept/list");
        }else {
            response.sendRedirect(request.getContextPath() +"/error.jsp");
        }

    }

    private void doUpdate(HttpServletRequest request,HttpServletResponse response)throws IOException,ServletException{
        request.setCharacterEncoding("UTF-8");
        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");
        String principal = request.getParameter("principal");
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        int i = 0;
        try {
            conn = DBUtils.getConnection();
            String sql = "update  dept set dname=?,loc=?,principal=? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(4,deptno);
            ps.setString(1,dname);
            ps.setString(2,loc);
            ps.setString(3,principal);
            i = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        if(i == 1){//添加成功
            response.sendRedirect(request.getContextPath() +"/dept/list");
        }else {
            response.sendRedirect(request.getContextPath() +"/error.jsp");
        }

    }
}

⑨在页面中通过请求域,获取动态展示的数据

  • 利用<%%>即编写java语句至service方法,通过srequest请求域,获取存储的List数据
  • 利用<%=%>即out.write,循环将数据输出到浏览器中
    • 获取servlet发给页面的数据,request.getAttribute、request.setAttribute
    • 获取页面发给servlet的数据,request.getParameter
<table border="1px" align="center" width="50%">
    <tr>
        <th>序号</th>
        <th>部门编号</th>
        <th>部门名字</th>
        <th>部门地址</th>
        <th>操作</th>
    </tr>
<%
        int index = 1;
        List<Dept> deptList = (List<Dept>) request.getAttribute("deptList");
        for(Dept dept :deptList){
%>
    <tr>
        <td><%=index++%></td>
        <td><%=dept.getDeptno()%></td>
        <td><%=dept.getDname()%></td>
        <td><%=dept.getLoc()%>></td>
        <td>
            <a href="javascript:void(0)" οnclick="window.confirm('确认删除该部门信息吗?')">删除部门</a>
            <a href="<%=request.getContextPath()%>/edit.jsp">修改部门</a>
            <a href="<%=request.getContextPath()%>/detail.jsp">查看详情</a>
        </td>
    </tr>
  <%}%>

【拓展】:

  • 如何清除服务器缓存

    ctrl+shift+delete

  • 可否只用jsp独立开发web应用?

仅用jsp也可以独立开发web应用,因其本质就是servlet,但是不推荐,会使代码解耦性变差

因为servlet用于数据获取及处理,jsp用于数据展示,jsp中的java代码越少越好

  • jsp文件的扩展名必须是.jsp么?

并非一定是jsp,可通过在Tomcat目录/conf/web.xml文件中进行配置即可

因为对于Tomcat而言,jsp就是一个普通的文本文件,web容器会将生成jsp_serlvet.java文件,再编译成class,最后运行调用java对象相关方法,真正执行与jsp文件无关。

1683965382674

  • 之后还可以学到什么?
    • Serlvet+jsp
      • session
      • cookie
      • EL表达式
      • jstl标签
      • Filter
      • Listener
    • Ajax
    • jQuery
    • Vue
    • mvc架构模式
    • 连接池
    • SSM
    • maven
    • git
页面代码如下:
  • 在此以list.jsp为例,代码如下:
    • 注意使用的Dept是自定义的实体类,使用时要导包
<%@ page import="java.util.List" %>
<%@ page import="com.bean.Dept" %>
<%@page contentType="text/html;charset=UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表页面</title>
</head>
<body>
<h1 align="center">部门列表</h1>
<hr>
<table border="1px" align="center" width="50%">
    <tr>
        <th>序号</th>
        <th>部门编号</th>
        <th>部门名字</th>
        <th>部门地址</th>
        <th>操作</th>
    </tr>
    <%
        int index = 1;
        List<Dept> deptList = (List<Dept>) request.getAttribute("deptList");
        for(Dept dept :deptList){
    %>
    <tr>
        <td><%=index++%></td>
        <td><%=dept.getDeptno()%></td>
        <td><%=dept.getDname()%></td>
        <td><%=dept.getLoc()%></td>
        <td>
            <a href="javascript:void(0)" οnclick="del(<%=dept.getDeptno()%>)">删除部门</a>
            <a href="<%=request.getContextPath()%>/dept/detail?f=m&deptno=<%=dept.getDeptno()%>">修改部门</a>
            <a href="<%=request.getContextPath()%>/dept/detail?f=d&deptno=<%=dept.getDeptno()%>">查看详情</a>
        </td>
    </tr>
    <%}%>

</table>
<hr>
<a href="<%=request.getContextPath()%>/add.jsp">新增部门</a>
<a href="javascript:void(0)" οnclick="del(no)" >删除</a>
<script type="text/javascript">
    function del(no){
        if(window.confirm("亲,删了不可恢复哦!")){
            document.location.href = "<%=request.getContextPath()%>/dept/delete?deptno=" + no;
        }
    }
</script>
</body>
</html>

五、实现登录功能

5.1不带Session

①步骤一:创建user表

  • user表包括登录用户名+登录密码
  • 同时密码采用密文存储(MD5加密)

②步骤二:编写登录页面

  • 页面含有用户名和密码的输入框
  • 点击登录,提交表单,提交用户名和密码,利用post提交方式

③步骤三:编写对应的Servlet处理登录请求

  • 登录成功:跳转部门列表
  • 登录失败:跳转失败页面

④步骤四:编写提供一个失败页面

【存在问题】:即便用户不登录,但若用户知晓请求路径,即可通过路径跨过登录页面,直接访问部门列表页

【解决方法】:利用session判断其是否登录

5.2带session

要解决用户不登录就可通过部门列表请求路径访问这个问题,可以将用户登录时的信息存储到session中,即当session中存在用户信息,则表示已登录。未存在用户信息则表示未登录

①登录成功时,获取Session对象,同时将登录信息存储在其中。

  • 此时必须要获取到session用于存储登录信息
     if(flag){
            //获取session对象,没有则创建
            HttpSession session = request.getSession();
            session.setAttribute("username",username);
            //跳转到部门列表页面
            response.sendRedirect(request.getContextPath()+"/dept/list");
        }else {
            //跳转失败页面
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }

②在servlet中(跳转部门列表的请求中),判断是否取得当前session,获取到表示已登录,获取不到表示未登录

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //此处不一定要获取到Session对象,有表示曾登录过,无表示未登录过
        HttpSession session = request.getSession(false);
        //获取session对象中的username属性
        String username = (String)session.getAttribute("username");
        if(session!=null && username!=null){
            String servletPath = request.getServletPath();
            if("/dept/list".equals(servletPath)){
                doList(request,response);
            }else if("/dept/detail".equals(servletPath)){
                doDetail(request,response);
            }else if("/dept/delete".equals(servletPath)){
                doDel(request,response);
            }else if("/dept/add".equals(servletPath)){
                doAdd(request,response);
            }else if("/dept/update".equals(servletPath)){
                doUpdate(request,response);
            }
        }else {
            //跳转到登录页面,request.getContextPath()为项目路径
            response.sendRedirect(request.getContextPath());
        }
    }
❗访问jsp也会生成session对象

在访问了jsp页面,就会创建session对象,因为jsp的九大内置对象之一,就有session。如果访问jsp时,不想升生成session,只需在jsp页面中添加以下配置指令:

<%@page session="false"%>

六、登录后显示用户名

登录后,如果要在jsp页面中显示用户名,只需要利用jsp内置对象的session对象,取出username属性即可

  • 注意使用jsp的session对象时,要保证其存在
<h1>欢迎<%=session.getAttribute("username")%></h1>

七、安全退出功能

安全退出指将当前的Sesiion对象,进行手动销毁

核心servlet代码如下:

//获取session对象,不需要创建新的,因而为false
HttpSession session = request.getSession(false);
//如果存在session
if(session != null){
  //将其手动销毁
  session.invalidate();
  //跳转至登录页面
    response.sendRedirect(request.getContextPath()+'/login.jsp');
}

八、jsp的指令

8.1 指令的作用

指令的作用:指导当前jsp的翻译引擎如何进行工作

  • 即如何翻译
8.2 常用指令

①include指令

  • 包含指令,用于在jsp中完成静态包含,目前很少被使用

②taglib指令

  • 引入标签库指令,多在JSTL标签库中使用

③page指令

  • 页面指令,用于定义JSP页面的全局属性
8.3指令的使用语法
<%@指令名 属性名=属性值 属性名=属性值....%>
8.4常用的page指令
①session属性
  • 是否启用session对象
<%@page session="true|false"%>
true表示启用jsp的内置对象Session,若无session对象,则创建
false表示不启用jsp的内置对象Session,则当前jsp页面中无法使用内置对象session
若未设置,默认true
②contentType属性
  • 用于设置响应的内容类型
<%@page contentType="text/json"%>
<!--contentType用于设置内容的响应类型,相当于response.setContentType("text/json");-->

<%@page contentType="text/json;charset=utf-8"%>
<!--在设置响应内容类型的同时,也设置响应时采用的字符集-->
③pageEncoding属性
  • 设置响应时采用的字符集
  • 常和pageEncoding搭配使用,也可直接在contentType中直接写 contentType="text/json;charset=UTF-8"
<%@page contentType="text/json" pageEncoding="UTF-8"%>
<!--相当于response.setConteneType("text/json;charset=UTF-8")-->
④import属性
  • 用于导包,导入后该jsp页面可使用包的方法
  • 导多个包,用逗号( , )分隔
<%@page import="java.util.list, java.util.Date"%>
<!-- import属性用于导包-->
⑤errorPage属性
  • 用于配置出错页面
  • 当前jsp页面出错后,就会跳转到配置的出错页面上
    • 路径地址从web根目录开始
<%@page errorPage="/路径地址" %>

【存在问题】:

当设置出错页面时,页面出错会跳转至错误页面,但此刻浏览器和控制台并不会输出错误信息,不利于程序员维护

【解决方法】:

可以启用页面的exception内置对象,其表示刚刚发生的异常对象

⑥isErrorPage属性
  • 设置该页面exception对象
    • exception为jsp的九大内置对象之一
  • true表示启用
  • false表示不启用
  • 默认为false
    • 常与errorPage属性一起搭配,用于设置的出错页面
<%@page isErrorPage="true"%>
<!--启用exception对象-->

<%
  //利用以下语句,打印异常堆栈信息,输出到后台控制台
  exception.printStackTrace();
%>

九、九大内置对象

[注:我使用的是jdk17,因而javax.servlet更名为jakar.servlet]

9.1 pageContext
  • 【页面作用域】

  • 包名 jakarta.servlet.jsp.PageContext pageContext

9.2 request
  • 【请求作用域】
  • 包名 jakarta.servlet.http.HttpServletRequest request
9.3 session
  • 【会话作用域】
  • 包名 jakarta.servlet.http.HttpSession session
9.4 application
  • 【应用作用域】
  • 包名 jakarta.servlet.ServletContext application

-----------------------------以上是四个作用域对象-----------------------------

1️⃣作用域的大小: pageContext<request<session<application

2️⃣四个作用域都有 :setAttribute、getAttribute、removeAttribute方法

3️⃣使用原则:尽可能使用小的域

----------------------------------------结束-----------------------------------------------

9.5 exception
  • 【异常对象】
  • 包名 java.lang.Throwable exception
9.6 config
  • 【配置对象】
    • 即ServletConfig ,表示web.xml文件的配置
  • 包名 jakarta.servlet.ServletConfig config
9.7 page
  • 【当前页面对象=this】

    • 其实是this,表示当前的servlet对象
  • 包名 java.lang.Object page

9.8 response
  • 【响应对象】

    • 负责响应
  • 包名 jakarta.servlet.http.HttpServletResponse response

9.9 out
  • 【输出对象】

    • 负责输出
  • 包名 jakarta.servlet.jsp.JspWriter out

Session机制和Cookie

关于B/S结构的会话机制称为session机制

一、什么是会话session?

  • 会话对应的英文是:session

    • 而session机制其实是一种规范,而不同的语言会对其进行不同的实现
  • 一次会话:用户打开浏览器,进行一系列操作,最终关闭浏览器,整个过程称为“一次会话”。

    • 请求的服务器端的java对象是:session
    • 一个会话会包含多次请求(一次会话对应N次请求)
    • 一个会话对应一个Session对象
  • 【回顾】**一次请求:**用户在浏览器进行一次点击(超链接/按钮),然后跳转,可以粗略认为是“一次请求”。

    • 请求的服务器端的java对象是:request
    • 一次请求对应一个request对象

    java和session都是服务器端的java对象,都在JVM中

二、Session作用

①session主要作用:保存会话信息

  • 如:用户登录成功,该如何将登陆成功的状态一直保存下来,就应该使用session对象保存

②为什么需要session对象保存会话状态?

  • 因为Http是一种无状态协议

    • 无状态:请求时,B和S是连接的,但请求结束后,连接就会中断
      • 即请求瞬间是连接,请求结束后连接断开,大大减轻服务器压力
    • 因而就需要一个对象去存储状态,即session会话对象
  • java的servlet规范中,session对应的类名是:

    • jarkata.servlet.http.HttpSession【jdk17】
    • javax.servlet.http.HttpSession【jdk17以下】

三、为什么用Session保存会话状态?

request请求域(HttpServletRequest)< session会话域(HttpSession) < application应用域(ServletContext)

以上三者都具有getAttribute()【取】和setAttribute()【存】方法

但request作用域和ServletContext应用域,

  • reuquest是一次请求一个对象
  • ServletContext是服务器启动时创建对象,服务器关闭时销毁对象,该ServletContext对象只有一个

因而request域太小,ServletContext域太大,最适合的还是session

四、session对象的使用

①获取session对象代码如下:

//从WEB服务器中获取当前session对象,如果未获取session对象,则新建
HttpSession session = request.getSession();
//从Web服务中获取当前session对象,如果未获取到session对象,不新建则返回null
HttpSession sesssion = request.getSession(false);

②存储数据,获取数据

session.setAttribute("存储名",存储值名);
session.getAttribute("设置取到值的名",取值名);

五、session对象的实现原理

5.1实现原理

1️⃣在web服务器中有一个session列表。

类似于map集合:

该map集合的key存储的是sessionid

该map集合的value存储的是session对象

**2️⃣当用户打开浏览器,第一次发起请求时:**服务器会创建一个新的session对象,同时为session对象生成一个session的id,然后web服务器会将session的id发送到浏览器,浏览器再将session的id保存到浏览器缓存中。

**3️⃣用户第二次发起请求时:**会自动将浏览器缓存中的sessionid自动发送给web服务器,服务器获取sessionid,根据sessionid查找其对应的session对象。

用户第N次发起请求,同上.....

4️⃣当用户关闭浏览器后,浏览器缓存中保存的sessionid会消失,下次重新打开浏览器后,缓存中找不到sessionid,就自然找不到web服务器中sessionid对应的session对象,而找不到session对象等同于会话结束,web服务器就会重新再创建一个session对象,生成其对应的sessionid,然后存到浏览器缓存中....

6️⃣同时当会话结束,但其对应的session对象并非立刻销毁,因为Http是无状态协议,所以Tomcat会根据会话超时机制,在某个时间后,若用户仍未请求Tomcat中的该session会话对象时,就将为该用户创建的该session对象给销毁。

而其中的sessionid是以cookie的形式存储

其存储在浏览器的内存中

1684042801676

7️⃣浏览器关闭会话一定结束么?

不一定,通常情况下,关闭浏览器后,缓存中的sessionid会消失,即访问web服务器就找不到原先对应的session对象。但有时当浏览器打开一定时间,超过一定时间没有进行操作,虽然并没有关闭浏览器,但根据session的超时机制,它也会销毁其对应的session对象。同时还有手动配置超时时长的情况。

8️⃣手动设置超时时长

如果想要手动设置session超时机制的时长,可在web.xml添加以下代码:

<!--在web.xml添加以下配置,表示设置session的超时机制中,超时时长为30分钟-->
<session-config>
    <session-timeout>30</session-timeout>
</session-config>
5.2注意

①session对象存储在服务器端,由服务器创建,并销毁

②一个session对象一个会话,浏览器打开->关闭

③一次会话内,包含多个请求

如:南京网友多次请求服务器,获取的都是同一个session1对象,前提是在一次会话内

若:第一次请求服务器,不存在session对象,服务器会为其创建一个新的session对象

而:原先的旧session对象并不会马上销毁,而是根据超时机制:当一定时间内,用户仍未访问该session对象,则销毁

④获取当前session对象时,有两种情况

  • 未获取到,则新建

    HttpSession session = request.getSession();

  • 未获取到,则返回null

    HttpSession session = request.getSession(false);

5.3流程图解【一个会话中】

①当用户第一次发起请求时,不存在sessionid,因而服务器为其创建一个session对象,将sessionid响应给浏览器,放入缓存

  • 其中的Jsessionid是以cookie的形式存储

1684043556601

②当用户第N次发起请求时,浏览器缓存以存在sessionid,会请求服务器,获取sessionid对应的session对象

1684043454434

六、Cookie

6.1 Cookie概念

在session的实现原理中,每个session对象都会关联一个sessionid

不同的路径所提交的cookie不同哦!

  • 如:JSESSION=41C481F208EF1234578
  • 而以上的键值对数据,就是cookie
  • 对于session关联的cookie而言,该cookie保存在浏览器的“运行内存”中
  • 只要浏览器不关闭,用户再次发起请求时,就会自动将运行内存的cookie发送给服务器
  • 而服务器再根据cookie去找到对应的session对象
6.2 cookie组成

http协议中规定,任何一个cookie都是由name和value组成的,且都是字符串类型。

  • java的servlet中,对cookie提供了哪些支持?
    • 一个Cookie类专门表示cookie数据
      • jarkata.servlet.Cookie;【jdk17】
      • javax.servlet.Cookie;【jdk17以下】
  • java怎么把cookie发送给浏览器呢?
    • 利用response.addCookie(cookie)方法
6.3Cookie保存在何处

Cookie最终保存在浏览器客户端上

  • 也可保存在运行内存中(浏览器关闭则消失)
  • 也可保存在硬盘文件中(永久保存)
6.4Cookie的作用

cookie和session机制都是为了保存会话状态

  • cookie是将会话的状态保存在浏览器客户端上 (cookie数据存储在浏览器客户端上)
  • session是将会话的状态保存在服务器端上 (session数据存储在服务器上)

cookie和session存在的意义?因为http协议是无状态 无连接协议。

【经典案例】:京东商城,在未登录情况下,仍可以添加商品至购物车,而下次访问购物车商品仍存在

1️⃣做法:

  • 将购物车中的商品编号放到cookie中,cookie保存在硬盘文件中,即使关闭浏览器。硬盘上cookie还在,再次打开京东商城时,查看购物车,会自动读取本地硬盘中存储的cookie,拿到商品编号,并动态显示。
    • 京东存储购物车的cookie可能如下:productIds = xxx.yyy.zzz 其中为一个个商品编号
    • 注意:如果在浏览器cookie清除掉,是清除对应硬盘的cookie数据,购物车的商品就会消失

【经典案例】:126邮箱的,十天内免登陆

1️⃣做法:

  • 当勾选[十天内免登陆] 登陆成功后,浏览器客户端会保存一个cookie,该cookie会保存将登陆时的用户名和密码信息,保存到本地的硬盘文件中,十天有效,当第二次访问该地址,浏览器自动直接从硬盘文件中取得对应cookie发送给浏览器,浏览器接收到cookie后,获取用户名和密码,自动进行登录
6.5cookie属于谁

cookie机制和session机制,属于http协议的一部分,php开发中也有cookie和session机制,只要做web开发,都会使用到。

6.5浏览器何时发送Cookie

http协议中规定:当发送请求时,会自动携带该path下的cookie数据给服务器

6.6发送Cookie给服务器

利用response对象的addCookie()方法,设置不同的关联路径(URL),选择在哪些路径中携带cookie数据,最后发送给服务器

①关于cookie的关联路径:

【案例】将cookie从服务器端发送到客户端上
  • 主要实现服务器生成cookie,然后将cookie响应给浏览器,浏览器接收cookie,将cookie展示在客户端上

①编写jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <a href="<%=request.getContextPath()%>/cookie/generate">服务器生成cookie,然后cookie响应给浏览器,浏览器接受cookie,再将cookie放到客户端中</a>
  </body>
</html>

②编写对应的servlet代码

@WebServlet({"/cookie/generate"})
public class GenerateCookie extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建Cookie对象
        Cookie cookie = new Cookie("productid","1234567891245");
        //设置cookie的失效时间
        cookie.setMaxAge(30);
        //设置cookie的关联路径
        cookie.setPat(request.getContextPath());
        //将Cookie响应到浏览器上
        response.addCookie(cookie);
    }
}

③访问编写好的servlet请求路径即可

1684128269676

6.7服务器接收Cookie

**接收时,利用request对象的getCookie方法接收**该路径携带的cookie数据,返回类型为String[]

①接收cookie

  • 返回值为string[],若浏览器未提交cookie,返回null
//通过request对象,接收浏览器发来的cookie
Cookie[] cookies = request.getCookies();
【案例】通过java程序接收浏览器发送过来的cookie

①编写页面,跳转至servlet

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <a href="<%=request.getContextPath()%>/cookie/send">接收浏览器发来的cookie,在java中接收,同时遍历cookie数据</a>
  </body>
</html>

②编写servlet,通过request对象的方法接收发来的cookie,同时进行遍历

@WebServlet({"/cookie/send"})
public class SendCookie extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取浏览器发送到服务器的cookie数据
        //注意:如果浏览器没有提交cookie,则返回null
        Cookie[] cookies = request.getCookies();
         //如果不是null,则遍历取出cookie
        if(cookies != null){
             for (Cookie cookie : cookies){
                 String name = cookie.getName();
                 String value = cookie.getValue();
                 System.out.println(name+"+"+value );
             }
        }
    }
}

③访问路径,再查看控制台信息

1684133354765

6.8Cookie有效时间

①设置cookie的有效时间

cookie.setMaxAge()
  • 未设置cookie的有效时间:默认保存在浏览器的运行内存中(浏览器关闭则消失)

  • 若设置cookie的有效时间>0

    • 表示一定会保存在硬盘文件中
  • 若设置cookie的有效时间=0

    • 表示该cookie被删除

    • 使用场景:多用于删除同名cookie

  • 若设置cookie的有效时间<0

    • 表示该cookie不会被存储
      • 仅不会被存储到硬盘文件中,会放在浏览器运行内存中
      • 和不调研serMaxAge()一个效果

②创建cookie

Cookie cookie = new Cookie("存储在cookie的数据名",存储的数据);
6.9实现十天内免登陆功能
6.9.1效果图

类似于126邮箱的30天免登陆

6.9.2思路

①首先实现基本的登录功能

  • 登录成功
    • 跳转部门列表页面
  • 登录失败
    • 跳转登录失败页面

②修改登录页面,添加勾选复选框[十天内免登陆]

    <form action="<%=request.getContextPath()%>/user/login" method="post">
        用户名<input type="text" name="username" value="" >
        <br>
        密码<input type="text" name="password" value="">
        <br>
        <input type="checkbox" name="remember" value="1">十天内免登陆<br> 
        <button type="submit" >登录</button>
    </form>

③修改Servlet中的login方法

  • 若勾选
    • 则在登录时,创建cookie
    • 同时将用户名和密码进行存储,同时设置过期时间为10天
    • 同时设置cookie的关联路径,使其一访问该路径,就自动携带设置的cookie数据
    • 最后将cookie响应给浏览器
      • 浏览器就会将其自动保存到硬盘文件当中10天
  • 若未勾选,则正常登录跳转
      if(flag){
            //获取Session
            HttpSession session = request.getSession();
            session.setAttribute("username",username);
            String remember = request.getParameter("remember");
            if("1".equals(remember) ){//如果用户点击十天免登陆
             //创建Cookie对象存储用户名,密码
                Cookie cookie1 = new Cookie("username",username);
                Cookie cookie2 = new Cookie("password",password);//真实情况需要加密
            // 设置失效时间 十天
                cookie1.setMaxAge(60*60*24*10);
                cookie2.setMaxAge(60*60*24*10);
            //设置关联路径
                cookie1.setPath(request.getContextPath());
                cookie2.setPath(request.getContextPath());
             //响应cookie给浏览器
                response.addCookie(cookie1);
                response.addCookie(cookie2);
            }
            //跳转到部门列表页面
            response.sendRedirect(request.getContextPath()+"/dept/list");
        }else {
            //跳转失败页面
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }

④在web.xml配置文件中,设置欢迎页,跳转到servlet请求中

<!--    会访问welcome,不用添加/,直接从根路径开始-->
    <welcome-file-list>
        <welcome-file>welcome</welcome-file>
    </welcome-file-list>

⑤编写WelcomeServlet

  • 用户再次访问时:

    • 获取cookie,遍历得到其中的用户名username和密码passwrod两个cookie值
    • 若都取到了,进行登录的操作,若成功->跳转部门列表页面
    • 若未取到,说明之前并未勾选[十天免登陆]/cookie有效期到,跳转登录页面
  • 要么跳转部门列表页面

  • 要么跳转登录页面

@WebServlet({"/welcome"})
public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Cookie,不可能size=0,只可能size>0,或为null
        Cookie[] cookies = request.getCookies();
        String username = null;
        String password = null;
//如果获取的cookie不为空
        if(cookies != null){
            //循环遍历cookie,得到username和password
            for(Cookie cookie : cookies){
                String name = cookie.getName();
                if("username".equals(name)){
                    username = cookie.getValue();
                }else if("password".equals(name)){
                    password = cookie.getValue();
                }
            }
            //如果获取的username和password都不为空,跳转至部门列表页面
            if(username !=null && password != null){
                //进行登录的操作
                Connection conn = null;
                PreparedStatement ps = null;
                ResultSet rs = null;
                Boolean flag = false;
                try {
                    conn = DBUtils.getConnection();
                   String sql = "select * from user where username = ? and password = ?";
                    ps = conn.prepareStatement(sql);
                    ps.setString(1, username);
                    ps.setString(2, password);
                    System.out.println(sql);
                    System.out.println(ps);
                    rs = ps.executeQuery();
                    System.out.println(rs);
                    if(rs.next()){
                        flag = true;
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }finally {
                    DBUtils.close(conn,ps,rs);
                }
                if(flag) {
                    //获取Session
                    HttpSession session = request.getSession();
                    session.setAttribute("username", username);
                    response.sendRedirect(request.getContextPath()+"/dept/list");
                }
            }else {
                //否则跳转登录页面
                response.sendRedirect(request.getContextPath()+"/index.jsp");
            }
        }

    }

⑥到此,十天免登录功能完成

  • 同时:只有修改密码,或删除cookie的硬盘文件,十天免登陆才会失效

可访问浏览器,测试点击十天免登陆后成功登录时,再次访问该路径,是否直接跳过登录页面,进入到部门列表

1684139887952

1684139874141

七、cookie禁用

cookie禁用是指,服务器正常发送cookie给浏览器,但浏览器拒收。

  • cookie禁用了,sessionid就会一直找不到,因此每次请求都获取到新的session对象
  • 同时每次请求生成的session,只能利用超时机制去销毁

当cookie禁用,该怎样实现session机制?

  • 利用URL重写机制

    • 在路径后添加分号 (;),然后将sessionid的值拼接过去

    • 但使用该机制,会大大提高开发者成本,开发者在编写所有请求路径时,都要添加一个Sessionid

      因而正常若禁用cookie,就干脆不让用户使用

localhost:8080/项目名/访问资源;jsessionid=xxxxxxxxxxxxxxx

八、Javaweb域对象

①一共有以下三个域对象:

  • request(对应类名: HttpServletRequest)
    • 请求域 [ 请求级别 ]
  • session(对应类名: HttpSession)
    • 会话域 [ 会话级别 ]
  • applicaion(对应类名: ServletContext)
    • 应用域 [ 项目级别 ]

②三个域的大小关系:

  • request<session<application

③三者都有以下的公共方法:

  • setAttribute(向域当中绑定数据)
  • getAttribute(向域当中获取数据)
  • removeAttribute(移除域中的数据)

④使用原则:尽量使用小的域

EL表达式

一、EL表达式的作用

EL(Expression Language)表达式语言,可以算是jsp语法的一部分,它有以下功能:

  • EL可以代替jsp中的java代码,使jsp文件的程序更加整洁、美观
    • 原先的jsp夹杂了许多<% java代码 %>、<%= java代码%>,导致jsp中既有html又有java,十分混乱
主要作用:进行页面展示

(从域中取+输出到浏览器)从某个域中取数据,然后转换为字符串,输出到浏览器

因而要利用EL表达式取数据,首先必须得存储在域中

①作用一:从某个域中取数据

  • 四个域
    • pageContext
    • request
    • session
    • application

②作用二:将取出的数据转为字符串

  • 如果是一个java对象,也会自动调用java对象的toString()方法将其转换为字符串

③作用三:将字符串输出到浏览器

  • 同jsp的:<%=%>一致,将其输出到浏览器

二、EL表达式的基本语法

  • 注意:不能加双引号 “ ”,会被看做字符串

  • 面试题: abc和���和{"abc"}有什么区别?

${abc}表示从某个域中取数据,并且被取的数据name为“abc”,之前一定有该语句:域.setAttribute("abc",对象)

${“abc”}表示将“abc”作为字符串输出到浏览器,而并非从某个域中取到

①第一步:声明当前页面的字符集编码形式

<%page contentType="text/html;charset=UTF-8"%>

②第二步:通过作用域.setAttribute存数据

request.setAttribute("存的值",存储的名)

③第三步:通过EL表达式取数据,并输出浏览器

${xx.xx}
2.1从域中取数据并展示

直接在${}中写“存储的数据名“,servlet中利用request.setAttribute("存储的数据名",存储的数据)

【示例】

2.1.1取出单个的值

1️⃣语法

 ${取值的名}

2️⃣示例

<%
request.setAttribute("username","zhangsan")
%>
<!--取出userename的值-->
${username}
等同于<%=request.getAttribute("username")%>
2.1.2取出实体类对象的属性值

1️⃣语法

  • 不带引号,会将其看做变量。

  • 带双引号,则会去找 对象的对应属性

    • 常用于要取的属性name含有特殊字符:xxx.xx的情况

      eg:${requestScope["adb.de"]}可取出属性名为 adb.de的值

 ${ 对象名.属性名 }
 ${ 对象名[" 属性名 "] }

2️⃣底层原理

  • 先创建一个符合javabean规范的User类

  • 底层上:是调用了其属性名对应的getter方法

    • 注意:若没有get方法,则报异常
      • 没有按驼峰命名法编写的get方法,不报异常,但编码规范不建议这样写
  • 但是EL省略get同时小写 ≈ 属性名

    • 相当于一个username---》EL会将其➕get,再将u->U-》getUsername()

3️⃣示例

<%
 User user = new User();
 user.setUsername("jackjson");
 user.setPassword("1234");
 user.setAge(50);
//数据必须存储在某个域的范围内,因为EL表达式只能从某个域中取数据
 request.setAttribute("userObj",user);
%>
<!--利用EL表达式取-->
<!--输出内容:EL表达式会调用对应的toString()方法-->
${userObj}
<!-- ${userObj}底层是从域中取出userObj对象,然后调用其toString()方法-->


<!--输出内容:实体类对应的属性名username的值-->
${userObj.username} 
${userObj["username"]}
使用这些语法的前提是:userObj对象有getUsername()方法
2.1.3取出两个关联实体类对象的属性

1️⃣语法

${实体类.对象名.属性值}

2️⃣示例

  • 如:User类中含有Address类型属性,怎样取出user其中所在的城市呢?
    • ${user.addr.city}
<%                                           class User{
 //创建User对象                                 private String username;
 User user = new User();                       private String password;
 user.setUsername("jackjson");                 private int age;
 user.setPassword("1234");                     private Address addr;
 user.setAge(50);                              .......对应的get、set方法
                                             }
 //创建Address对象	
 Address a = new Address();                 class Address{
 a.setCity("北京");                           private String city;
 a.setStreet("大兴区");                       private String street;
 a.setZipcode("1111");                       private String zipcode;
                                            ....对应的get、set方法
 //设置User对象的address                     }
 user.setAddr(a);
 
//将user对象存入域中
 request.setAttribute("userObj",user);
%>
<!-- 利用EL表达式 -->
<!--取出user对象是哪个城市的-->
${userObj.addr.city}   相当于user对象.getAddr().getCity()
     
     
     
<!--取出user对象是哪个街道的-->
${userObj,addr.street}   相当于user对象.getAddr().getStreet() 
2.1.4从Map集合中取数据

1️⃣语法

  • 通过key来获取
    • 底层仍然调用其getXxx方法
${集合名.对象名.属性名}

2️⃣示例

①先向域中存储一个Map类型的数据
<%

 Map<String,User> userMap = new HashMap<>();
 User user = new User();
 user.setUsername("lisi");
 userMap2.put("user",user);
 request.setAttribute("hhh",userMap2);
 
%>
②将域中的数据取出
<!--取出map的数据-->
${hhh.user.username}
2.1.5从数组中取数据

1️⃣语法

  • 直接输出数组对象
    • 底层调用其toString()方法
${nameArray}
  • 输出数组对象中的某个值
    • 通过下标来获取
    • ❗:即便下标越界,仅取不出数,输出内容为空
      • 因为EL表达式会对空进行处理
${nameArray[下标值]}

2️⃣示例

<%
   //①字符串数组对象
   String[] usernames = {"zhangsan","lisi","wangwu"};
   //向域中存储数组
   request.setAttribute("nameArray",usernams);
   //②实体类数组对象
   User u1 = new User();
   u1.setUsername("zhangsan");
   
   User u2 = new User();
   u2.setUsername("lisi");
   
   User[] user = {u1,u2};
   request.setAttribute("userArray",users);
%>

${nameArray[0]} 
<!--取出nameArray数组的第一个-->

${userArray[1].username} 
<!--取出userArray数组的第二个,其中的username值-->
2.1.6从List集合中取数据

1️⃣语法

  • 下标值从0开始
${list列表名[下标值]}

2️⃣示例

<%
   List<String> list = new ArrayList<>();
   list.add("abc");
   list.add("def");
   request.setAttribute("myList",list)
%>

${myList}
${myList[0]}
${myList[1]}
2.1.7从Set集合中取数据

1️⃣语法

2️⃣示例

<%
  Set<String> set = new HashSet<>();
  set.add("a");
  set.add("b");
  request.setAttribute("set",set);
%>

三、向域中取值的优先级

四个域存储数据的属性名一致的情况,取值的优先级是?

优先从小范围内读取数据

3.1 未指定范围时
  • 默认的取值优先级是:pageContext->reuqest->session->application (从小范围域-->大范围域)
3.2 指定范围时
  • EL表达式有四个隐含的隐式的范围对象:

    • pageScope
    • requestScope
    • sessionScope
    • applicationScope
  • 可利用隐含的范围对象,进行指定

    ${pageScope.data} 从pageContext域中获取data数据

    ${requestScope.data} 从request域中获取data数据

    ${sessionScope.data} 从session域中获取data数据

<%
    //四个域都存储了数据,且name相同
    pageContext.setAttribute("data","pageContext");
    request.setAttribute("data","request");
    session.setAttribute("data","session");
    application.setAttribute("data","application");
%>

${data}
<!--从小范围到大范围中取:pageContext->request->session->application-->
  • 在实际开发中,name都不相同,所以xxScope一般可省略

四、EL表达式对空值处理

EL表达式对空值会进行处理,如果利用request.getAttribute("")取值为空时,会输出null,而EL表达式输出,会将null变为一串空字符串,对应页面的展示更为友好。

即EL表达式表面为${},其实还是转化为jsp代码,再翻译为java去执行

${username}
===================等同于============
<%=request.getAttribute("username) == null ? "":request.getAttribute("username")%>

五、EL表达式指令

5.1 isELIgnored
  • 表示是否忽略该页面中所有的EL表达式
    • true 表示不忽略EL表达式
      • 页面的EL表达式会当做普通字符串输出
    • false 默认为false,表示启用EL表达式
<%@page contentType="text/html;charset=UTF-8" isELIgnored="true"%>
  • 若只想忽略某个EL表达式语句,在前加反斜杠\
\${username}

六、EL表达式常用隐式对象

6.1pageContext

(1)作用

其常用于获取应用的根路径:${pageContext.request.contextPath}

【省流】:EL中需要获取request对象,而非直接得到,用pageContext隐含对象的getRequest()方法得到

jsp中九大内置对象就有pageContext,与EL表达式的隐式对象pageContext是同一个。

①jsp中获取request对象有两种方法:

①request
②pageContext.getRequest()
在jsp中的getRequet()似乎沒用,但其实是为在EL表达式中使用作铺垫的

②EL表达式中获取request对象:

  • 利用pageContext隐式对象的getRequest()方法
${pageContext.request}
<!--别忘了EL表达式中.request就是调用对应的getRequest()方法哦!-->

通过pageContext隐式对象的getRequest()方法得到request对象后,就可以调用.一切request相关的方法,只需注意EL表达式中getXxx()-->xxx即可

③获取应用的根路径

①jsp中可以写成
<!--此处需要强转,因为只有HttpSerlvet才有getContextPath()这个方法-->
<%=(HttpServeltRequest)pageContext.getRequest()).getContextPath() %>
<%=request.getContextPath()%>
②对应的EL表达式为
${pageContext.request.contextPath}
6.2param

(1)作用

用于获取页面路径中,请求参数一位数组的第一个参数

(2)语法

${param.参数名}

(3)示例

若用户在浏览器上提交单值数据:http://localhost:8080/jsp/15.jsp?username=lisi

①jsp中:
用户名:<%=request.getParameter("username")%>
②EL表达式中:
用户名:${param.username}

若用户在浏览器上提交多值数据:http://localhost:8080/jsp/15.jsp?aihao=study&aihao=dance&aihao=read

①jsp中
爱好:<%=request.getParameter("username")%>
②EL表达式中:会返回第一个值,正常应使用paramValues
爱好:${param.aihao}
6.3paramValues

(1)作用

获取页面路径中,请求参数一位数组的所有参数值

(2)语法

${paramValues.参数名}

(3)示例

若用户在浏览器上提交多值数据:http://localhost:8080/jsp/15.jsp?aihao=study&aihao=dance&aihao=read

①jsp中
爱好:<%=request.getParameterValues("aihao")%>
②EL表达式中
<!--获取的是一位数组-->
爱好:${paramValues.aihao}
<!--获取数组中的元素-->
爱好:${paramVlaue.aihao[0]}
6.4 initParam

(1)作用

获取web.xml中配置的ServletContext的初始化参数

ServletContext是Serlvet上下文对象,对应的JSP九大内置对象中的:application

(2)语法

initParam.属性名

(3)示例

web.xml

<context-param>
    <param-name>pageSize</param-name>
    <param-value>20</param-value>
</context-param>
<context-param>
    <param-name>pageNum</param-name>
    <param-value>5</param-value>
</context-param>

①jsp获取ServletContext中的初始化参数

<%
   String pageSize = application.getInitParameter("pageSize");
   String pageNum  = application.getInitParameter("pageNum");
%>
记录条数:<%=pageSize%>
页码: <%=pageNum%>

②EL表达式获取ServletContext中的初始化参数

记录条数:${initParam.pageSize}
页码: ${initParam.pageNum}

七、EL表达式的运算符

7.1算术运算符
+ - * / %

①+号只进行求和,不进行拼接

  • 因而会将+两旁的转为数字,无法转换则报错
${20+10} //30
${20+"10"} //30
${20+"abc"} //报错
${"asd"+"axs"} //报错
7.2关系运算符
== != < <= > >= eq
①==和eq

EL表达式的这两个运算符,都调用了equals方法,用于比较两个变量的引用是否相同

  • 注意String重写了,是比较内容
${"abc"=="abc"}//true
${"abc"}==${"abc"} //acb==abc
<%
   Object o1 = new Object();
   request.setAttribute("o1",o1);
   request.setAttribute("o2",o1);
%>
${o1 == o2} //true
<%
  String s1 = "abc";
  String s2 = "abc";
  request.setAttribute("a",s1);
  request.setAttribute("b",s2);
%>
${a == b}//false

<%
 String s1 = new String();
 String s2 = new String();
 request.setAttribute("a",s1);
 request.setAttibute("b",s2);
%>
${a == b}//true

<%
   Object o1 = new Object();
   Object o2 = new Object();
   request.setAttribute("o1",o1);
   request.setAttribute("o2",o2);
%>
${o1 == o2}//false
②!=和not

也调用equals方法, 表示取反

  • 使用时要注意优先级,加括号
${!(stu1 eq stu2)}
7.3逻辑运算符
! && || not and or
7.4条件运算符
  • 又称三目运算符
? :

示例:

${empty param.username ? "對不起,用戶名爲空" : "成功登录"}
7.5取值运算符
[] 和 .
7.6 empty运算符

判断是否为空,空->true,非空->false

${empty param.user} 
<!--判断获取到user参数是否为空-->
${not empty param.user}
<!--判断获取到user参数是否不为空-->
${!empty param.user}
<!--判断是否获取到user参数是否不为空-->

${empty param.password == null}
<!--前半部分是boolean类型,与null类型不相等,所以结果为false-->

JSTL标签库

一、什么是JSTL标签库?

JSTL全称Java Standard Tag Lib(java标准的标签库)

JSTL标签库通常结合EL表达式一起使用,目的是消除JSP中的java代码

【JST原理解析】

引入标签库的uri后面路径,指向一个xxx.tld文件

而tld文件实际上是一个xml配置文件

在tld文件中,描述了“标签”和“java”类之间的关系

  • 常用的核心标签库对应的tld文件是?

    • c.tld
  • tld文件在哪?

    • 在jakarta.servlet.jsp.jstl-2.0.0.jar里面META-INF目录下,有c.tld文件

    1684725710788

  • 源码解析:配置文件tld

<tag>
   <description>
       对该标签的描述
    </description>
    <name>标签名</name>
    <tag-class>标签对应的java类</tag-class>
    <body-content>标签体中可以出现的内容</body-content>
        <!--如果是jsp,表示该标签体中可以出现所有符合jsp语法的代码,如EL表达式-->
    <!--属性-->
    <attribute>
        <description>对属性的描述</description>
        <name>属性名</name>
        <required>是否为必须,false非必须,true表示必须</required>
        <rtexprvalue>该属性是否支持EL表达式</rtexprvalue>	
    </attribute>
</tag>
<c:forEach items="属性"> 标签
    标签体
</c:forEach>
【旧版jar包下载教程】

jar包下载地址:**http://tomcat.apache.org/taglibs/standard/

①点击download,耐心等待跳转页面

②下载taglibs-standard-impl-1.2.5.jar 和 taglibs-standard-spec-1.2.5.jar

  • 在下载页面中一共有4个jar包:

    Impl: taglibs-standard-impl-1.2.5.jar JSTL实现类库

    Spec: taglibs-standard-spec-1.2.5.jar JSTL标准接口

    EL: taglibs-standard-jstlel-1.2.5.jar JSTL1.0标签-EL相关

    Compat: taglibs-standard-compat-1.2.5.jar 兼容版本

  • 从README得知:

    如果不使用JSTL1.0标签,可以忽略taglibs-standard-jstlel包, README没有介绍taglibs-standard-compat

    包,估计应该是兼容以前版本

1684909765877

【新版jar包下载教程】

jakarta.servlet.jsp.jstl-2.0.0.jar下载地址

jakarta.servlet.jsp.jstl-api-2.0.0.jar下载地址

点击以上两个链接,耐心等待,即可直接下载,注意根据项目使用的jdk版本,需要使用引入对应的jar包,否则运行项目,会出现报错。

1684914472609

二、使用步骤

  • 第一步:引入JSTL标签库对应的jar包

    • 在IDEA中如何引入?

      【IDEA->WEB-INF下新建lib目录,jar包拷贝到lib中,Add lib】

    • ❗引入时,注意保持jar的版本一致,统一为tomcat10之前/之后

    • 什么时候需要将引入的jar放入lib目录下?

      当引入的jar包是Tomcat中没有的,必须放在lib目录下

    • tomcat10之后:

      jakarta.servlet.jsp.jstl-2.0.0.jar

      jakarta.servlet.jsp.jstl-api-2.0.0.jar

    • tomcat10之前:

      javax.servlet.jsp.jstl.jar

      taglibs-standard-impl-1.2.5.jar

      taglibs-standard-spec-1.2.5.jar

  • 第二步:在jsp页面中引入要使用的标签库(采用taglib指令)

    • JSTL提供了很多标签库,所以使用时需指定
<!--核心标签库-->
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!--格式化标签库:负责格式化操作-->
<%@taglib prefix="fmt" uri="http://java.sum.com/jsp/jstl/fmt"%>
<!--sql标签库-->
<%@taglib prefix="sql" uri="http://java.cum.com/jsp/jstl/sql"%>
  • 第三步:在需要使用标签的位置使用即可
    • 标签是jstl标签,实际上是java程序
示例:
<%@page contentType="text.html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sum.com/hsp/jstl/core"%>
<%
  //创建List集合
  List<Student> stuList = new ArrayList<>();
  //创建Stduent对象
  Student s1 = new Student("110","经常");
  Student s2 = new Student("220","获取");
  Student s3 = new Student("330","消防车");
  //添加到List集合中
  stuList.add(s1);
  stuList.add(S2);
  stuList.add(s3);
  //将list集合存储到请求域中
  request.setAttribute("stuList",stuList);
%>

<!--遍历stuList集合的数据,并输出到浏览器-->

<!--①使用java代码-->
<%
  List<Strudent> stuList = (List<Student>)request.getAttribute("stuList");
  for(stu : stuList){
%>
  id:<%=stu.getId()%>,name:<%=stu.getName()%>
<%
  }
%>

<!--②使用jstl标签库:forEach标签-->
<c:forEach items="stuList" var="s">
   id:${s.id}
   name:${s.name}
</c:forEach>

三、常用标签

3.1 forEach标签

1️⃣作用

forEach标签用于循环

2️⃣语法

①遍历集合

  • items:
    • 要迭代的集合
    • 非必须
    • 支持EL表达式
  • var
    • 迭代时的每一项元素
      • 即别名
  • varStatus
    • 表示var的状态对象
      • 是一个java对象
    • 具有count属性值,主要用于编号
      • 从1开始,以1递增
<%@page contentType="text/html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sum.com/jsp/jstl/core"%>
<%
  //创建List集合
  List<Student> stuList = new ArrayList<>();
  //创建Stduent对象
  Student s1 = new Student("110","经常");
  Student s2 = new Student("220","获取");
  Student s3 = new Student("330","消防车");
  //添加到List集合中
  stuList.add(s1);
  stuList.add(S2);
  stuList.add(s3);
  //将list集合存储到请求域中
  request.setAttribute("stuList",stuList);
%>
<c:forEach items="stuList" var="s" varStatus="stuStatus">
    编号:${stuStatus.count}
    id:${s.id}
</c:forEach>

②遍历变量

  • var:

    • 用于指定循环时的变量
  • begin

    • 开始位置
  • end

    • 结束位置
  • step

    • 步长
      • 类同每次+几
<c:forEach  var="i" begin="1" end="10" step="1">
  ${i}
</c:forEach>
输出:1 2 3 4 5 6 7 8 9 10

3️⃣示例

<%@page contentType="text/html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sum.com/jsp/jstl/core"%>
<%
  //创建List集合
  List<Student> stuList = new ArrayList<>();
  //创建Stduent对象
  Student s1 = new Student("110","经常");
  Student s2 = new Student("220","获取");
  Student s3 = new Student("330","消防车");
  //添加到List集合中
  stuList.add(s1);
  stuList.add(S2);
  stuList.add(s3);
  //将list集合存储到请求域中
  request.setAttribute("stuList",stuList);
%>
<c:forEach items="stuList" var="s">
  id:${s.id}
  name:${s.name}
</c:forEach>
3.2 if标签

1️⃣作用

if标签用于判断

2️⃣语法

  • test:

    • 判断的条件,为真执行,为假不执行

    • 是必须的

    • 支持EL表达式

    • 只能是Boolean类型

  • var:

    • 每次判断的结果值 true/false

      • 存储在域中
    • 非必须的

    • 支持EL表达式

  • scope:

    • 用来指定var的存储域
      • 有四个域:page(pageContext域)、request(request域)、session(session域)、application(application域)
<c:if test="" var="" scope="">
</c:if>

3️⃣示例

<%@page contentType="text/html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sum.com/jsp/jstl/core"%>
<c:if test="${empty param.username}">
    如果param中的username为空,则显示该句话
</c:if>
<c:if test="${not empty param.user}">
    如果param中的username不为空,则显示这句话
</c:if>
<!--无else标签时,可用两个if替代-->
3.3 choose标签

1️⃣作用

多分支条件选择,"当....执行....;否则...."

2️⃣语法

  • test
    • 表示条件判断语句
<c:choose>
  <c:when test=""></c:when>
  <c:when test=""></c:when>
  <c:when test=""></c:when>
  <c:otherwise>
  
  </c:otherwise>
</c:choose>

3️⃣示例

<%@page contentType="text/html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sum.com/jsp/jstl/core"%>
<c:choose>
  <c:when test="${param.age < 18}">
   青少年
  </c:when>
  <c:when test="${param.age < 35 }">
   青年
  </c:when>
  <c:when test="${param.age < 50}">
   中年
  </c:when>
  <c:otherwise>
  老年
  </c:otherwise>
</c:choose>

四、利用jstl改造oa系统

4.1使用技术

​ 利用servlet+jsp+EL表达式+JSTL标签库

1️⃣EL表达式

将项目上下文路径由jsp的<%=request.getContextPath()%>变为${pageContext.request.contextPath}

2️⃣JSTL标签库

  • 先向页面引入

  • <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

forEach、if、choose标签,将jsp页面的循环,变为< c:forEach>< /c:forEach>+EL表达式${xx.xx}取值

3️⃣利用EL表达式和JSTL标签库,都是为了将jsp页面中的java代码尽可能的减少

因而只在jsp页面中添加改造的代码

4.2改造系统功能代码
  • 在此以list列表页为例
(1)改造前

1684915462733

(2)改造后

1684915415875

4.3改造页面路径代码(base标签)

(1)base标签

  • 作用:设置整个页面的基础路径

  • 常用写法:

    • 写死的

      http://localhost:8080/oa
    • 动态的

      协议://服务器IP:端口号/项目根路径/
      ${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/
      • 协议 ${pageContext.request.scheme}
      • 服务器IP ${pageContext.request.serverName}
      • 端口号 ${pageContext.request.serverPort}
      • /项目根路径 ${pageContext.request.contextPath}

在HTML代码中,有一个base标签,能设置整个网页的基础路径

  • 默认会将base标签中的路径,添加在没有以"/"开始的路径前
<title>首页</title>
<base href="http://localhost:8080/oa">
<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/"> 
<!--设置整个网页的基础路径写法有以上两种👆-->
  • 之后网页中涉及路径带项目前缀时,都可以去掉
可由以下:
<a href="${pageContext.request.contextPath}/user/list"></a>
变为:
<base href="http://localhost:8080/oa">
<a href="user/list">

【同时可能对js代码不起作用,建议js中仍使用${pageContext.request.contexPath}】

Filter过滤器

一、是什么?

Filter过滤器是一个java程序。

既可以在Servlet目标程序之前添加代码,又可以在Servlet目标程序之后添加代码。

一般在过滤器中编写公共代码执行,同时执行多个过滤器,具有执行顺序规则。

为什么需要?

当具有多个不同相关业务时,都需要编写一次登录拦截,会造成代码冗余。

而采用Filter过滤器可以更快的帮助我们对不同请求,进行过滤

如:

在Tomcat低版本中,需要处理Servlet乱码问题,因而所有servlet中都需要添加以下两句代码:

//POST请求乱码问题
request.setCharacterEncoding("UTF-8");
//响应中文乱码问题
response.setContentType("text/html;charset=UTF-8");

而我们就可以通过Filter过滤器,将其只写一遍,然后在所有servlet中得到复用

二、如何使用?

①第一步:编写一个java类实现一个接口

  • jarkata.servlet.Filter【Tomcat10以上】
  • javax.servlet.Filter【Tomcat10以下】
public class Filter implements Filter{

}

②第二步:实现该接口中doFilter方法

编写过滤规则+chain.doFilter(request,response)方法

  • init方法:在Filter对象第一次被创建时调用,且只调用一次

  • doFilter方法:用户发送一次请求,则执行调用一次【编写过滤规则】

  • destory方法:在Filter对象被释放/销毁前调用,并且只调用一次

    • 【注意】

      Servlet对象默认情况下,服务器启动并不会新建对象(调用时新建)

      Filter对象默认情况下,服务器启动会新建对象

      但两者都是单实例(假单)

  • chain.doFilter(request,response):表示执行下一个过滤器/目标java程序

public class Filter1 implements Filter{
 
    public Filter1(){
        //无参构造方法
    }
    
  @Override
 public void init(FilterConfig filterConfig) throws SerlvetException {
   Filter.super.init(filterConfig);
 }
 
 @Override
 public void doFilter(SerlvetRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletExcetion{
   //编写过滤规则
     //表示执行下一个过滤器,如果下一个不是过滤器,则执行目标程序servlet
     chain.doFilter(request,response);
 }
 
 @Override
 public void destory(){
  Filter.super.destory();
 }
}

③第三步:对Filter进行配置,采用在web.xml文件中对Filter进行配置

表示访问哪些对应的请求路径,会跳转该过滤器

<filter>
    <filter-name>过滤器名称</filter-name>
    <filter-class>过滤器包路径</filter-class>
    <!--可选:过滤器初始化参数名-->
    <init-param>
       <param-name>参数名</param-name>
        <param-value>参数值</param-value>
    </init-param>
</filter>

<!--过滤器映射信息-->
<filter-mapping>
    <filter-name>映射的过滤器名</filter-name>
    <url-pattern>指定的过滤器作用的请求路径</url-pattern>
</filter-mapping>

④第三步:对Filter进行配置,采用@WebFilter注解式

  • 包含以下路径匹配方式:
    • 精确匹配 /dept/save
    • 后缀匹配 *.do
    • 前缀匹配 /dept/*
    • 匹配所有 /*
@WebFilter({"a.do","b.do"})//精确匹配,a.do与b.do都走这个过滤器
@WebFilter("*.do") //扩展匹配,以*开始 --》以.do结尾都走这个过滤器
@WebFilter("/dept/*") //前缀匹配,以/开始 --》以/dept开头都走这个过滤器
@WebFilter("/*")//匹配所有的路径

【注意:】目标Servlet是否执行取决于两个条件

  • 第一:用户发送的请求路径是否与Servlet的请求路径匹配成功
  • 第二:chain.doFilter(request,response)是否写了
    • 表示执行下一个过滤器,若无,执行目标Servlet程序

三、Filter执行顺序

(1)Filter优先级天生比Servlet的高

  • 若一个请求路径,即对应Filter又对应Servlet,一定先执行Filter,再执行Servlet

(2)若多个Filter,先执行哪个?

①若采用web.xml文件配置请求路径

  • xml中的位置决定先执行哪个Filter
<filter-mapping>FilterB</filter-mapping>
<url-patter>/xxxxx</url-patter>

<filter-mapping>FilterA</filter-mapping>
<url-patter>/xxxx</url-patter>

先执行FilterB,后执行FilterA

标签的位置,决定Filter的执行顺序

②若采用@WebFilter注解式配置请求路径

  • 取决于整个类名在字典中的顺序
FilterA和FilterB,先执行FilterA
Filter1和Filter2,先执行Filter1

四、执行原理

  • 过滤器的调用顺序,遵循栈数据结构
    • 先进后出
  • 1684758870383

五、Filter生命周期

  • init 服务器启动时,Filter对象被创建后,执行一次
  • doFilter 用户发送一次请求,执行一次
  • destory 服务器关闭时,Filter对象被释放/销毁前,执行一次
    • 同Servlet的生命周期
    • 唯一不同:Filter默认情况,服务器启动时进行实例化,创建对象

六、Filter责任链设计模式

1️⃣概念

Filter最大优点,在程序编译时,不会确定调用顺序。

因为其调用顺序配置在web.xml文件中,只需修改顺序即可调整Filter的执行顺序。

可见Filter执行顺序,可在程序运行期间动态组合,调用顺序灵活。该设计模式被称为 责任链设计模式

2️⃣作用

在程序运行阶段,动态的组合程序的调用顺序

3️⃣示例代码:

/**
* 该程序缺点:在编译阶段就已经确定了调用关系
* 若要改变调用顺序,就要修改java代码
* java代码修改,就需要重新编译,项目重新测试,项目重新发布
* 此举违背了:OCP(开闭)原则,对修改关闭,对扩展开放
*/
public class Test{
    public class void main(String[] args){
     System.out.println("main begin");
     m1();
     System.out.println("main over");
    }
    private static void m1(){
     System.out.println("m1 begin");
     m2();
     Sysetm.out.println("m1 over");
    }
    private static void m2(){
    System.out.println("m2 begin");
    m3();
    System.out.println("m2 over");
    }
    private static void m3(){
     System.out.println("目标程序正在执行中....");
    }
}

七、Filter过滤器改造oa项目

  • 当前的oa项目存在什么缺陷?

    • DeptServlet、EmpServlet、OrderServlet,每个Servlet都是处理自己的相关业务;

      但执行这些Servlet前都需要判断用户是否登录,若登录,执行操作;未登录,提醒用户登录。

    • 而在所有的Servlet中都添加判断是否登录的这段固定代码,显然代码冗余,未得到复用

  • 解决方法?

    • 使用Servlet规范中的Filter过滤器来解决该问题

①创建LoginCheckFilter,实现Filter

public class LoginCheckFilter implements Filter{

}

②在web.xml配置映射信息

  • 拦截对应的路径,到Filter
<filter>
     <filter-name>loginFilter</filter-name>
    <filter-class>com.oa.filter.LoginCheckFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>loginFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

③重写其doFilter方法

  • 如果想放行路径,怎么办?

    request.getSerlvetPath得到路径

    if("xxx路径".equals(servletPath) ){

    chain.doFilter(request,response);//放行

    }

  • 添加以下代码,设置拦截登录的过滤器

public class LoginCheckFilter implements Filter{
@Override
  public void doFilter(ServletRequest req,ServletResponse res,FilterChain chain)
  throw IOException,ServletException{
      //进行强制类型转换
      HttpServletRequest request = (HttpServletRequest) req;
      HttpServletResponse response = (HttpServletResponse) res;
       //获取session
      HttpSession session = request.getSession(false);
      String servletPath = request.getServletPath();
      //如果session不为空,同时有username
     if("/index.jsp".equals(servletPath) || "/welcome".equals(servletPath) || "/user/login".equals(servletPath) || "/user/exit".equals(servletPath) || (session != null && session.getAttribute("username") !=null)){
        //继续执行下一个过滤器
         chain.doFilter(request,response);
     }else{
         //跳转到登录页面
         response.sendRedirect(request.getContextPath()+"/index.jsp");
     } 
  }
 }

④最后将Servlet中的相关代码,删除即可

  • 因为设置的拦截器已经为我们进行了登录拦截,就不需要在每个Servlet再去拦截一次啦

Listener监听器

一、监听器是什么

  • 监听器是Servlet规范的一员,而Filter也是规范的一员
  • 监听器接口:都以XxxListener结尾

二、监听器的作用

【使用场景】:在特定的情况下,去执行某段代码,就可以使用到监听器,去找到该情况。

三、常用监听器

Servlet规范中提供了哪些监听器?

提供以下监听器的接口,可在这些时间点前后,编写要执行的语句

jakarta.servlet包下:

3.1 ServletContext域

①SerlvetContextLinstener
  • 用于监听SerlvetContext对象的状态
  • 服务器启动+关闭触发
②ServletContextAttributeLinstener
  • 用于监听ServletContext对象域中属性的变化

  • 向域中存储、删除、替换数据触发

3.2 ServletReuqest

①ServletRequestListener
  • 用于监听ServletReuqest对象的状态

  • 发送一次请求开始+结束触发

②ServletReuqestAttributeListener
  • 用于监听ServletRequest对象域中属性的变化

  • 向域中存储、删除、替换数据触发

jakarta.servlet.http包下:

3.3 HttpSession

①HttpSessionListener
  • 用于监听HttpSessionListener对象的状态
  • 第一次请求时触发
@WebListener
public class MyHttpSessionLinstener implements HttpSessionListener{

   @Override
   public void sessionCreated(HttpSessionEvent httpSessionEvent) {
     //session对象创建时,调用该事件代码
   }
   
   @Overrride
   public void sessionDestoryed(HttpSessionEvent httpSessionEvent) {
     //session对象销毁时,调用该事件代码
   }
  
}
②HttpSesssionAttributeLinstener
  • 用于监听session对象域中属性的变化
  • 向域中存储、删除、替换数据触发
@WebListener
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener{
   @Override
   public void attributeAdded(HttpSessionBindingEvent se){
     //向session域中存储数据时,该方法被WEB服务器调用
   }
   @Override
   public void attributeRemoved(HttpSessionBindingEvent se){
     //向session域中移除数据时,该方法被WEB服务器调用
   }
   @Override
   public void attributeReplaced(HttpSessionBindingEvent se){
     //向session域中数据被替换时,该方法被WEB服务器调用
   }
}
③HttpSessionBindingListener
  • 用于监听实现该监听器的对象

    假设User类实现了该监听器:对象放入session触发bind事件,对象从session删除触发unbind事件

    假设User类未实现该监听器:对象放入session/从session删除都不触发事件

  • 与HttpSesssionAttributeLinstener的区别?

    • HttpSesssionAttributeLinstener:监听session域中数据变化,监测点在HttpSession对象上。

      • 只要向HttpSession域中放,就触发

      • 需要@WebListener注解

    • HttpSessionBindingListener:监听session域中某个特定数据变化,实现了该监听器的对象。

      • 只要类实现了该接口的方法,那么对象在被放入监听器时触发

      • 不需要@WebListener注解

//不需要添加@WebListener注解
public class User1 implements HttpSesssionBindingListener{
         @Override
         public void valueBound(HttpSessionBindingEvent event){
            //向该实现类,绑定数据时,触发
         }	
         @Override
         public void valueUnbound(HttpSessionBindingEvenet event){
           //向该实现类,解绑数据时,触发
         }
}
//以上代码表示,只有向session域中的User1类型对象,存取值,才会触发上述对应方法
//而采用HttpSesssionAttributeLinstener并不能精确到某个对象,只能监听整个session是否有变化
  • 常见业务场景

    ①请编写一个功能,记录网站的实时在线用户人数?

    【HttpSesssionAttributeLinstener监听器即可】

    方案一:通过服务器端是否分配session对象,因为一个session对象代表一个用户,即session多少个,在线用户就有多少个。session对象新建,count++,再将count存储到ServletContext域中

    ②请编写一个功能,记录网站已登录的在线用户人数?

    【HttpSessionBindingListener才行】

    方案二:通过服务器端是否分配session对象+曾setAttribute("user",userObj)存储过user对象,count++。

    将User实现HttpSessionBindingListener监听器,当User类型对象存储到session对象中,count++,再将count存储到ServletContext域中,最后页面进行展示

⑤HttpSessionIdListener
  • 监听session的id的变化,当session的id发生改变,接口中唯一一个方法被调用
⑥HttpSessionActivationListener
  • 监听session的钝化和活化,当session钝化/活化时,触发
    • 钝化:session对象从内存存储到硬盘文件
    • 活化:从硬盘文件把session恢复到内存中

四、使用步骤

以ServletContextListener为例:

(1)第一步:创建Listener包,存放监听器类

(2)第二步:编写一个类 implements(实现) ServletContextListener接口

public class MyServletContextLinstener implements ServletContextListener{
  
}

(3)第三步:实现其方法

  • contextInitialized
  • contextDestoryed
public class MyServletContextLinstener implements ServletContextListener{

   @Override
   public void contextInitialized(ServletContextEvent sce) {
   
   }
   
   @Overrride
   public void contextDestoryed(ServletContextEvent sce) {
   
   }
  
}

(4)第四步:进行监听器的配置

①采用在Web.xml中进行配置

<listener>
   <listener-class>监听器类,所在的位置</listener-class>
</listener>

②采用注解式进行配置

  • 在类上,标注@WebListener注解
@WebListener
public class MyServletContextLinstener implements ServletContextListener{

   @Override
   public void contextInitialized(ServletContextEvent sce) {
     //ServletContext对象创建(服务器启动)时,调用该事件代码
   }
   
   @Overrride
   public void contextDestoryed(ServletContextEvent sce) {
     //ServletContext对象销毁(服务器关闭)时,调用该事件代码
   }
  
}

【注意:所有监听器方法都不需要javaweb程序员调用,当特殊情况发生后,由服务器来自行负责调用】

五、Listener改造oa项目

5.1添加【在线人数统计】功能

①用户登录

若执行sesssion.setAttribute("user",userObj)表示User类型对象往session域中存储过,用户登录

②用户退出

  • 若执行sesssion.removeAttribute("user",userObj)表示User类型对象从session域中移除,用户退出,表示用户不在线

  • 或session超时销毁,也表示用户不在线

5.2实现步骤

①创建User类,implements(实现)监听器

  • 重写HttpSessionBindingListener监听器,其中的valueBound和valueunBound方法
public class User implements HttpSessionBindingListener {
    private String username;
    private String password;

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        //一、用户登录,count+1,最终将count存储到application域中,因为统计的是所有人
        //①获取ServletContext对象,即application域
        ServletContext application = event.getSession().getServletContext();
        //②获取在线人数
        Object onlinecount = application.getAttribute("onlinecount");
        if(onlinecount == null){//如果为空,表示第一个人登录
            application.setAttribute("onlinecount",1);
        }else {//如果不为空,让onlinecount+1
            int count = (Integer) onlinecount;
            count++;
            application.setAttribute("onlinecount",count);
        }
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        //二、用户退出,count-1
        //①获取ServletContext对象
        ServletContext application = event.getSession().getServletContext();
        //②得到在线人数
        Integer onlinecount = (Integer)application.getAttribute("onlinecount");
        //③将在线人数--
        onlinecount--;
        //③将值重新存储到域中
        application.setAttribute("onlinecount",onlinecount);
    }

    public User(){

    }
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

②修改UserSerlvet的代码,在登录和退出时,向session域中添加/删除其中对应的USer类型值,实现监听效果

@WebServlet({"/user/login","/user/exit"})
public class UserServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String servletPath = request.getServletPath();
        if("/user/login".equals(servletPath)){
            doLogin(request,response);
        }else if("/user/exit".equals(servletPath)){
            doExit(request,response);
        }
    }

    private void doLogin(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {
        String username = request.getParameter("username");
        System.out.println(username);
        String password = request.getParameter("password");
        System.out.println(password);

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        Boolean flag = false;
        try {
            conn = DBUtils.getConnection();
            String sql = "select * from user where username = ? and password = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, username);
            ps.setString(2, password);
            System.out.println(sql);
            System.out.println(ps);
            rs = ps.executeQuery();
            System.out.println(rs);
            if(rs.next()){
                flag = true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        if(flag){
            //获取Session
            HttpSession session = request.getSession();
            //➕:向session中添加user
            User user = new User(username,password);
            session.setAttribute("user",user);
            String remember = request.getParameter("remember");
            if("1".equals(remember) ){//如果用户点击十天免登陆
             //创建Cookie对象存储用户名,密码
                Cookie cookie1 = new Cookie("username",username);
                Cookie cookie2 = new Cookie("password",password);//真实情况需要加密
            // 设置失效时间 十天
                cookie1.setMaxAge(60*60*24*10);
                cookie2.setMaxAge(60*60*24*10);
            //设置关联路径
                cookie1.setPath(request.getContextPath());
                cookie2.setPath(request.getContextPath());
             //响应cookie给浏览器
                response.addCookie(cookie1);
                response.addCookie(cookie2);
            }
            //跳转到部门列表页面
            response.sendRedirect(request.getContextPath()+"/dept/list");
        }else {
            //跳转失败页面
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }
    }
    private void doExit(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{
        //获取session对象,不需要创建新的,因而为false
        HttpSession session = request.getSession(false);
          //如果存在session
        if(session != null){
            //➕:从Session中删除user
            session.removeAttribute("user");
            //将其手动销毁
            session.invalidate();
            //跳转至登录页面
            response.sendRedirect(request.getContextPath());
        }
    }
}

③打开两个浏览器,登录两个不同的用户访问,可见在线人数相应改变

  • 同时点击安全退出后,人数也相应减少

1684927729764

至此所有javaweb相关知识到此结束!

MVC架构模式

一、MVC是什么?

  • 系统为什么要分层?

    专人干专事,各人分工明确,降低代码耦合度,增强扩展力,增强组建的可复用性

  • MVC架构模式中 M(Model:数据/业务) V(View:视图) C(Controller:控制器)

  • 其中的Controller层并不处理业务,主要职责是接收请求,进行调度

二、Dao

Dao,全称Data Access Object(数据访问对象)

是javaEE的设计模式之一,并非23种设计模式

实际开发中,Dao层只负责数据库表的增删改查,不包含任何业务逻辑代码

在此以银行账户系统为例,进行迭代更新,将MVC架构模式融入其中

  • 命名规则:以XxxDao命名,表示对XXX表进行增删改查

    • 同时其中的方法名,应该与数据库查询相关

      insertxx
      deleteByxxx
      updateByxxx
      selectAll
  • 通常情况,一张表对应一个Dao对象

三、pojo/bean/domain

pojo,全称Plan Ordinary Java Object(简单的java对象),又被称为javabean,也被称为domain(领域模型)对象

不同程序员的习惯不同,但本质都一样,在mvc架构中一般用于数据库操作中存放数据库表数据

一个简单的实体类,常被称为“pojo”,通常用于存放数据

  • 命名规则:以数据库表名命名

  • 实体类中包含:私有属性、无参构造、有参构造、getter和setter方法

  • 设置实体类的属性,建议采用包装类,防止为null的情况

  • 通常情况下,一张表对应一个Pojo对象

四、 Service

service被翻译为业务,用于专门编写业务代码

  • 命名规则:XxxService ,需要体现出针对Xxx进行的业务操作

五、Controller

controller为控制层,主要是Xxxservlet的java代码,用于专门接收数据调用业务方法处理业务,并跳至View进行页面展示

  • 命名规则:XxxServlet,需体现出针对Xxxx进行的调度操作

六、三层架构

三层架构分为表示层(包含view、controller)->业务逻辑层->持久化层->DB数据库

1685024294925

而三层架构和MVC的关系如图:

1685024434605

【MVC案例:银行转账】

创建数据库表

①利用navicat工具,创建一个名为mvc的数据库

创建t_act表,包含id、actno、balance三个字段

1684928399935

为表添加数据:actno001 50000元、actno002 0元

(1)不采用MVC架构模式

①创建工程

②引入所需的依赖的jar包

  • serlvet
  • jsp
  • jstl
  • mysql

③编写页面代码

<%@page contentType="text/html;charset=UTF-8"  %>
<html>
   <head>
     <base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
     <title>银行账户转账</title>
   </head>
    <body>
<%--    此处无需加项目名,因为<base>标签会自动将前缀加在无/开头的路径上--%>
      <form action="transfer" method="post">
         转出账户:<input type="text" name="formActno"><br>
         转入账户:<input type="text" name="toActno"><br>
         转账金额:<input type="text" name="money">
        <input type="submit" value="转账">
      </form>
    </body>
</html>

④配置Tomcat服务器,启动项目

1684929719512

⑤编写账户转账的Servlet类,同时设置异常,对余额不足等情况进行处理

  • AccountTransferServlet.java:
package com.bank.servlet;

import com.bank.exception.AppException;
import com.bank.exception.MoneyNotEnoughException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

@WebServlet("/transfer")
public class AccountTransferServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        System.out.println("跳转了么???");
        //1.获取转账相关信息
        //来自哪个账号
        String formActno = request.getParameter("formActno");
        //转入哪个账号
        String toActno = request.getParameter("toActno");
        //转账金额
        double money = Double.parseDouble(request.getParameter("money"));
        //2.编写转账业务逻辑代码,连接数据库进行转账
        Connection conn = null;
        PreparedStatement ps = null;
        PreparedStatement ps1 = null;
        PreparedStatement ps2 = null;
        ResultSet rs = null;
        try {
            //①注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //②获取连接

            String url = "jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true";;
            String user = "root";
            String password = "d17379703772";
            conn = DriverManager.getConnection(url, user, password);
            //③获取预编译的数据库对象
            String sql = "select blance from act where actno = ?";
            ps= conn.prepareStatement(sql);
            ps.setString(1,formActno);
            //④执行sql语句,返回结果集
            rs = ps.executeQuery();
            //⑤处理结果集
            if(rs.next()){
                //取出结果数据
                double blance = rs.getDouble("blance");
                if(blance< money){
                    //余额不足(使用异常机制处理)
                    throw new MoneyNotEnoughException("对不起,余额不足");
                }

                //余额充足,执行转账
                // 发起转账用户-money;
                String sql2 = "update act set blance = blance -? where actno = ?";
                ps1 = conn.prepareStatement(sql2);
                ps1.setDouble(1,money);
                ps1.setString(2,formActno);
                int count = ps1.executeUpdate();

                // 收到转账用户+money
             String sql3 = "update act set blance = blance + ? where actno = ?";
                ps2 = conn.prepareStatement(sql3);
                ps2.setDouble(1,money);
                ps2.setString(2,toActno);
                count += ps2.executeUpdate();
               //累计两次都成功
                if(count !=2){
                    throw new AppException("转账出现问题,请联系管理员");
                }
                out.print("转账成功");
            }
        } catch (Exception e) {
            //异常处理,在发生异常时,输出异常信息
            //在此如果余额不足,会得到并输出我们自行编写的MoneyNotEnoughException异常
          out.println(e.getMessage());
        } finally {
            //释放资源
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(ps1 != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(ps2 != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn!=null){
                try{
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
  • MoneyNotEnoughException.java:
/**
 *  用于处理余额不足的异常
 * @author 六日
 * @version 1.0
 * @since 1.0
 */
public  class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException(){}
    public MoneyNotEnoughException(String msg){super(msg);}
}
  • AppException.java
public class AppException extends Exception{
        public AppException(){}
        public  AppException(String msg){ super(msg);}
}

⑥同时为了保证转账和收款一致,导致转账途中中断,添加事务一致性

   //(1)开启事务(不再手动提交,当业务完成后自动提交)
            conn.setAutoCommit(false);
            
    //(2)提交事务
                conn.commit();
                
   //(3)发生异常,进行事务回滚
            try {
                if(conn != null){
                    conn.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }                
(2)采用MVC架构模式

采用mvc架构模式编写项目后,工程目录如下:

  • controller层负责调用service层进行业务,同时跳转对应的视图页面展示

  • dao层用于进行数据库的操作

  • service层用于进行程序业务的操作

  • pojo用于存放实体类,便于dao层使用

  • utils用于存放工具类,如:jdbc连接工具类

  • Exception异常类,用于存放自行编写的异常

  • web目录下:

    • 存放静态资源:jsp、html、css页面和图片等

1685025644670

①Util工具类
/**
 * JDBC工具类
 * @author 六日
 * @version 2.0
 * @since 2.0
 */
public class DBUtils {
    // 数据库驱动程序
    private static String driver= "com.mysql.cj.jdbc.Driver";
    // 数据库连接信息
    private static String url = "jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true";
    // 连接数据库的用户名
    private static String user= "root";
    // 连接数据库的密码
    private static String password = "d17379703772";
    //提供一个私有的构造方法
    //避免其创建对象,因为工具类的方法都是静态的,无需创建对象
    private DBUtils(){}

    //DBUtils类加载时执行
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /** 创建连接池对象
     * 未使用数据库连接池,直接创建连接对象,返回每次都会创建一个新的连接对象
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }

    public static void close(Connection conn, Statement stmt, ResultSet rs){

        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
②pojo实体类
public class Account {
    private Long id;
    private String actno;
    private Double balance;
    public Account(){

    }
    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }
}
③dao层
/**
 * AccountDao负责Account数据的增删改查
 * dao?
 * 表示 data access object数据访问对象
 * 是设计模式的一种,属于javaEE的设计模式之一
 * @author 六日
 * @version  2.0
 */
public class AccountDao {
    /**
     * 插入账户信息
     * @param act 账户信息
     * @return 1表示插入成功,0表示失败
     */
    public int insert(Account act){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        int i = 0;
        try {
            conn = DBUtils.getConnection();
            Connection connection = DBUtils.getConnection();
            String sql = "insert into act(actno,blance) value(?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,act.getActno());
            ps.setDouble(2,act.getBalance());
             i  = ps.executeUpdate();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        return i;
    }

    /**
     * 根据主键删除用户
     * @param id
     * @return
     */
    public int deleteById(Long id){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        int i = 0;
        try {
            conn = DBUtils.getConnection();
            Connection connection = DBUtils.getConnection();
            String sql = "delete from act where actno = ?";
            ps = conn.prepareStatement(sql);
            ps.setLong(1,id);
            i  = ps.executeUpdate();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        return i;
    }

    /**
     * 更新账户
     * @param act
     * @return
     */
    public int update(Account act){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        int i = 0;
        try {
            conn = DBUtils.getConnection();
            Connection connection = DBUtils.getConnection();
            String sql = "update act set actno= ?,blance = ? where  id = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,act.getActno());
            ps.setDouble(2,act.getBalance());
            ps.setLong(3,act.getId());
            i  = ps.executeUpdate();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        return i;
    }

    /**
     * 根据账号ID查询账户
     * @param actno
     * @return
     */
    public Account selectByActno(String actno){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        Account account = null;
        try {
            conn = DBUtils.getConnection();
            Connection connection = DBUtils.getConnection();
            String sql = "select id,blance from act where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,actno);
            rs  = ps.executeQuery();
            if (rs.next()){
                Long id = rs.getLong("id");
                Double blance = rs.getDouble("blance");
                //将结果集封装成对象
                 account = new Account();
                 account.setId(id);
                 account.setActno(actno);
                 account.setBalance(blance);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        return account;
    }

    /**
     * 获取所有的账户
     * @return
     */
    public List<Account> selectAll(){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<Account> list = new ArrayList<>();
        try {
            conn = DBUtils.getConnection();
            Connection connection = DBUtils.getConnection();
            String sql = "select id,actno,blance from act ";
            ps = conn.prepareStatement(sql);
            rs  = ps.executeQuery();
            while (rs.next()){
               //取出数据
                Long id = rs.getLong("id");
                String actno = rs.getString("actno");
                Double blance = rs.getDouble("blance");
                //封装数据
                Account account = new Account();
                account.setId(id);
                account.setActno(actno);
                account.setBalance(blance);
                //将其加到List集合
                list.add(account);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtils.close(conn,ps,rs);
        }
        return list;
    }
}
④service业务层
/**
 * 处理账户转账的业务类
 * @author 六日
 * @version 2.0
 * @since 2.0
 */
public class AccountService {
     private AccountDao dao = new AccountDao();

    /**
     *  完成转账的业务逻辑
     * @param formActno 转出账号
     * @param toActno  转入账号
     * @param money  转账金额
     */
    public void transfer(String formActno,String toActno,double money) throws Exception{
        //查询余额是否充足
        Account formAct = dao.selectByActno(formActno);
        if(formAct.getBalance() < money){
            throw new MoneyNotEnoughException("对不起,你的余额不足");
        }
        //修改余额
        Account toAct = dao.selectByActno(toActno);
        toAct.setBalance(toAct.getBalance()+money);
        formAct.setBalance(formAct.getBalance()-money);
        //更新数据库中的余额
        int count = dao.update(toAct);
        count +=dao.update(formAct);
        //判断是否修改成功
        if(count!=2){
            throw new AppException("转账异常,请重试");
        }

    }
}
⑤Exception 异常类
/**
 * App异常类
 */
public class AppException extends Exception{
    public AppException(){}
    public AppException(String msg){super((msg));}
}
/**
 * 余额不足异常类
 */
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException(){}
    public MoneyNotEnoughException(String msg){ super(msg);}
}
⑥Controller 负责调度
import java.io.IOException;
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    //导入AccountService
    AccountService accountService = new AccountService();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       //获取参数
        String formActno = request.getParameter("formActno");
        String toActno = request.getParameter("toActno");
        Double money = Double.parseDouble(request.getParameter("money"));
        //调用service进行账户更新
        try {
            //进行转账
            accountService.transfer(formActno,toActno, money);
            //转账成功,跳转成功页面
            response.sendRedirect(request.getContextPath()+"/sucess.jsp");
        } catch (MoneyNotEnoughException e){
           //跳转余额不足页面
            response.sendRedirect(request.getContextPath()+"/moneynotenough.jsp");
        } catch (Exception e) {
            //转账失败,跳转失败页面
            e.printStackTrace();
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }
    }
}
(3)采用MVC架构模式+事务
①理论

事务在Service层进行添加

  • 一般是一个业务方法对应一个完整的事务

  • 步骤如下:

    • service代码的开始时:开启事务
    • service代码结束时:进行事务的提交
  • 同时为保证service和dao的Connection连接对象是同一个

    • 将Connection对象绑定在线程上,利用一个Map集合的形式,令key存放Thread,value存放Object
  • MyThreadLocal.java类:

    • 自定义一个map集合,将Connection连接对象放入
/**
* 该容器用于存放和当前线程绑定的数据
*/
public class MyThreadLocal<T>{
    //①创建一个Map集合
  private Map<Thread, T> map = new HashMap<>();
    //②向ThreadLocal中绑定值
    public void set(T obj){
        map.put(Thread.currentThread(),obj)
    }
    //③从ThreadLocal中得到值
    public void get(){
        return map.get(Thread.currentThread());
    }
    //④移除ThreadLocal的值
    public void remove(){
        map.remove(Thread.currentThread());
    }
}
  • DBUtils
    • 在getConnection获取连接对象时,获取存放在map集合中的connection对象
public class DBUtils{
    private static 	MyThreadLocal<Connection> local = new MyThreadLocal<>();
    /**
    * 每一次都调用该方法获取Connection对象
    */
    public static Connection getConnection(){
    //获取当前线程中的connection对象
      Connection connection = local.get();
      //如果为空,表示没有
      if(connection == null){
        //创建一个conncetion对象
        connection = new Connection();
        //存入自行创建的线程map集合中
        local.set(connection);
      }
      return connection;
    }
}

【以上皆为理论,其实java为我们提供了java.lang.ThreadLocal类,其中就含有类似于我们自行编写的ThreadLocal】

②具体步骤

利用ThreadLocal去存放连接对象

可保证当前线程中获取的连接对象都为同一个,使其成功开启事务

①在DBUtils工具类中

获取连接对象时,利用ThreadLocal的set方法,放在线程中

  //调用java自带的ThreadLocal类型,创建一个对象,存放每个线程对应的Connection
     private static ThreadLocal<Connection> local = new ThreadLocal<>();
    /** 创建连接池对象
     * 未使用数据库连接池,直接创建连接对象,返回每次都会创建一个新的连接对象
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        //通过ThreadLocal的get方法得到connection
        Connection conn = local.get();
        //如果为空,第一次必定为空
        if(conn==null){
            //根据url,user,password创建connection
            conn = DriverManager.getConnection(url, user, password);
            //并赋给线程中的connection
            local.set(conn);
        }

        return conn;
    }

关闭连接对象时,将连接对象从线程中移除

因为Tomcat内置了线程池,线程对象都是被提前创建好的,一个线程可能存在重复利用的情况

     if(conn!=null){
            try {
                conn.close();
                //移除当前线程的连接对象
                //因为Tomcat内置线程池,线程池有许多线程对象,都是被提前创建好的,每个线程存在重复使用的现象
                local.remove();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
  • 完整代码如下:
/**
 * JDBC工具类 已添加事务
 * @author 六日
 * @version 3.0
 * @since 3.0
 */
public class DBUtils {
    // 数据库驱动程序
    private static String driver= "com.mysql.cj.jdbc.Driver";
    // 数据库连接信息
    private static String url = "jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true";
    // 连接数据库的用户名
    private static String user= "root";
    // 连接数据库的密码
    private static String password = "d17379703772";
    //提供一个私有的构造方法
    //避免其创建对象,因为工具类的方法都是静态的,无需创建对象
    private DBUtils(){}

    //DBUtils类加载时执行
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
     //调用java自带的ThreadLocal类型,创建一个对象,存放每个线程对应的Connection
     private static ThreadLocal<Connection> local = new ThreadLocal<>();
    /** 创建连接池对象
     * 未使用数据库连接池,直接创建连接对象,返回每次都会创建一个新的连接对象
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        //通过ThreadLocal的get方法得到connection
        Connection conn = local.get();
        //如果为空,第一次必定为空
        if(conn==null){
            //根据url,user,password创建connection
            conn = DriverManager.getConnection(url, user, password);
            //并赋给线程中的connection
            local.set(conn);
        }

        return conn;
    }

    public static void close(Connection conn, Statement stmt, ResultSet rs){

        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();
                //移除当前线程的连接对象
                //因为Tomcat内置线程池,线程池有许多线程对象,都是被提前创建好的,每个线程存在重复使用的现象
                local.remove();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

②在service中开启事务提交,将其变为自动提交

 //开启事务
 connection.setAutoCommit(false);
 //提交事务、
  connection.commit();
  • 完整代码如下:
/**
 * 处理账户转账的业务类 已添加事务
 * @author 六日
 * @version 3.0
 * @since 3.0
 */
public class AccountService {
     private AccountDao dao = new AccountDao();

    /**
     *  完成转账的业务逻辑
     * @param formActno 转出账号
     * @param toActno  转入账号
     * @param money  转账金额
     */
    public void transfer(String formActno,String toActno,double money) throws Exception{
        try(Connection connection = DBUtils.getConnection()){
            connection.setAutoCommit(false);
            //查询余额是否充足
            Account formAct = dao.selectByActno(formActno);
            if(formAct.getBalance() < money){
                throw new MoneyNotEnoughException("对不起,你的余额不足");
            }
            //修改余额
            Account toAct = dao.selectByActno(toActno);
            toAct.setBalance(toAct.getBalance()+money);
            formAct.setBalance(formAct.getBalance()-money);
            //更新数据库中的余额
            int count = dao.update(toAct);
            count +=dao.update(formAct);
            //判断是否修改成功
            if(count!=2){
                throw new AppException("转账异常,请重试");
            }
            //提交事务
            connection.commit();
        }catch (SQLException e){
           throw new AppException("账户异常");
        }


    }
}

③运行项目,查看控制台输出的连接对象为同一个,表示成功将当前线程的连接对象统一

1685070143763

④添加以下代码在service转账业务中,可模拟异常,若未出现执行一半的情况,表示添加事务成功

	String s = null;
	s.toString();
(4)采用MVC架构模式+事务+接口编程

在此我们需要为dao层、service层、controller层都提供一个接口,使程序员能面向接口编程,提升程序的扩展力。

①AccountService和AccountServiceImpl

public interface AccountService  {
    void transfer(String formActno,String toActno,double money) throws Exception;
}
public class AccountServiceImpl implements AccountService {
     private AccountDao dao = new AccountDaoImpl();

    /**
     *  完成转账的业务逻辑
     * @param formActno 转出账号
     * @param toActno  转入账号
     * @param money  转账金额
     */
    public void transfer(String formActno,String toActno,double money) throws Exception{
        try(Connection connection = DBUtils.getConnection()){
            connection.setAutoCommit(false);
            System.out.println(connection);
            //查询余额是否充足
            Account formAct = dao.selectByActno(formActno);
            if(formAct.getBalance() < money){
                throw new MoneyNotEnoughException("对不起,你的余额不足");
            }
            //修改余额
            Account toAct = dao.selectByActno(toActno);
            toAct.setBalance(toAct.getBalance()+money);
            formAct.setBalance(formAct.getBalance()-money);
            //更新数据库中的余额
            int count = dao.update(toAct);
            count +=dao.update(formAct);
            //判断是否修改成功
            if(count!=2){
                throw new AppException("转账异常,请重试");
            }
            //提交事务
            connection.commit();
        }catch (SQLException e){
           throw new AppException("账户异常");
        }
    }
}

②AccountDao和AccountDaoImpl,修改方法同上,在此不做演示,感兴趣可以在本文末尾,下载源码查看

③AccountServlet

@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    //导入AccountService
    AccountService accountService = new AccountServiceImpl();//利用多态,等学到spring框架,可交由spring去new创建
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       //获取参数
        String formActno = request.getParameter("formActno");
        String toActno = request.getParameter("toActno");
        Double money = Double.parseDouble(request.getParameter("money"));
        //调用service进行账户更新
        try {
            //进行转账
            accountService.transfer(formActno,toActno, money);
            //转账成功,跳转成功页面
            response.sendRedirect(request.getContextPath()+"/sucess.jsp");
        } catch (MoneyNotEnoughException e){
           //跳转余额不足页面
            response.sendRedirect(request.getContextPath()+"/moneynotenough.jsp");
        } catch (Exception e) {
            //转账失败,跳转失败页面
            e.printStackTrace();
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }
    }
}

Tomcat版本问题

Tomcat随着版本的更新迭代,有了许多改进

省流:Tomcat10以后不会出现响应请求的中文乱码问题

一、乱码问题

(1)请求数据乱码

①get请求

get请求的数据,存放在请求行中

可修改CATALINA_HOME(Tomcat存放位置)下conf/server.xml

<Connector URIEncoding="UTF-8">

【版本问题】

Tomcat7以及之前的版本,设置< Connertor >标签的URIEncoding=“ISO-8859-Ⅰ“,因而会出现中文乱码问题。

Tomcat8以及之后的版本,设置< Connertor >标签的URIEncoding=“UTF-8”,不存在中文乱码问题。

②post请求

post请求的数据,存放在请求体中

可利用request对象的setCharsetEncoding方法

request.setCharsetEncoding("UTF-8");

【版本问题】

Tomcat9以及之前的版本,设置请求体数据默认为GBK,因而会出现中文乱码。

Tomcat10以及之后的版本,设置请求体数据默认为UTF-8,因而不用考虑中文乱码问题。

(2)响应数据乱码

利用response对象的setContentType()方法

reponse.setContentType("text/html;charset=UTF-8");

【版本问题】

Tomcat9以及之前,响应体的字符集默认为GBK会出现乱码问题

Tomcat10以及之后,响应体的字符集设置默认为UTF-8不会出现乱码问题

IDEA配置

一、修改代码自动更新资源

配置IDEA修改文件代码,就更新资源

1681057653718

二、修改快捷键

File->Setting->搜索Keymap->进行相应设置->Apply进行应用

1682045138881

三、进入多行编辑模式

同时按住ctrl++shift+alt,然后鼠标拖选要一键修改的代码行

javaEE中的路径

①在web.xml中配置Servlet时,映射路径前不必+项目名,以“/”开头

<servlet>
   <servlet>userServlet</servlet>
    <servlet-class>全限定包名</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>userServlet</servlet-name>
    <url-pattern>/访问路径</url-pattern>
</servlet-mapping>

②欢迎页面的资源路径,从web根开始,不需要以"/"开头

<welcome-file-list>
  <welcome-file>index.html</welcome-file>
</welcome-file-list>

③利用request对象的getRequestDispatcher()获取转发器时,路径要以“/”开头,同时不加项目名

request.getRequestDispatcher("/b").forward(request,response);
//跳转到Bservlet中,其中web.xml配置BServlet路径为/b
//该路径也可以是静态资源,路径从web根下开始,不加项目名

④前端页面的< a>跳转,其中href要带上项目名,路径以“/”开头

<a href="/项目名/资源路径"></a>

⑤前端发送请求时,路径都需要带上项目名

解决Tomcat乱码

一、CMD中运行Tomcat报错

1681294136237

三、Tomcat控制台报错

(1)原因
  • Tomcat控制台包含server、Tomcat Localhost Log、Tomcat Catalina Log
  • 该处乱码,说明是Tomcat的配置文件中,字符集设置不符合

1682056395772

(2)解决方案

①修改Tomcat文件下conf目录的logging.properties

其中java.util.logging.ConsoleHandler.encoding 设为UTF-8即可

java.util.logging.ConsoleHandler.encoding = UTF-8

1682056746865

②更改web.xml和server,xml文件

②tomcat根目录\conf的web.xml,在100行左右,改成

<servlet>
		<servlet-name>default</servlet-name>
		<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
		<init-param>
			<param-name>debug</param-name>
			<param-value>0</param-value>
		</init-param>
		<init-param>
			<param-name>fileEncoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>listings</param-name>
			<param-value>false</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

③tomcat根目录\conf的server.xml,在70行左右,改成

<Connector port="8080" protocol="HTTP/1.1"
			   connectionTimeout="20000"
			   redirectPort="8443" URIEncoding="UTF-8" />
  • 若此时Server还处于乱码状态,可能是由于windows默认编码是GBK, idea 的默认继承了windows的编码,但是 tomcat 默认是utf-8的,故而要么修改 tomcat 为GBK

    步骤:

    ①File->Settings

    ②找到Editor->Console->设置字符集为UTF-8

    ③再次运行Tomcat服务器,乱码消失

自行输出内容乱码

①File->Setting->Editor->File Encoding全部设置为UTF-8

1682055204608

Localhost Log中文乱码

  • 找到tomcat存放文件夹下的
  • ./conf/logging.properties
  • 将该文件中的 所有 UTF-8 改为 GBK 即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓六日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值