三、Javaweb之JSP、MVC和三层架构、案例、会话技术

8. JSP

8.1 JSP概述

JSP(全称:Java Server Pages):Java 服务端页面
是一种动态的网页技术,其中既可以定义 HTML、JS、CSS等静态内容,还可以定义 Java代码的动态内容,也就是 JSP = HTML + Java

示例:hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>JSP, Hello World</h1>
    <%
        System.out.println("hello.jsp");
    %>
</body>
</html>

作用:jsp主要作用是代替Servlet程序回传html页面数据

应用场景:假设需要通过登录界面跳转到主页,主页上需要显示目前登录的用户名。此时就需要用response.getWriter().write写一大堆html界面,显然很不方便。可以将html界面的内容和Servlet里面接收参数的java代码写在jsp中,设置表单提交地址为jsp文件来实现

8.2 JSP入门

  1. 导入 JSP 依赖

    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
    </dependency>
    

    该依赖的 scope 必须设置为 provided,因为 tomcat 中有这个jar包了,所以在打包时不希望将该依赖打进到工程的war包中

  2. 创建JSP文件:右键 --> New --> JSP/JSPX

  3. 编写HTML标签和Java代码

8.3 JSP原理

JSP被称为页面,可以写html标签,但为什么可以写Java代码呢?这是因为 JSP 本质上就是一个 Servlet

在这里插入图片描述

  1. 浏览器第一次访问 hello.jsp 页面
  2. tomcat 会将 hello.jsp 转换为名为 hello_jsp.java 的一个 Servlet
  3. tomcat 再将转换的 servlet 编译成字节码文件 hello_jsp.class
  4. tomcat 会执行该字节码文件,向外提供服务

在目录 tomcat-demo2\target\tomcat\work\Tomcat\localhost\tomcat-demo2\org\apache\jsp 即可看到转换后的servlet,即hello_jsp.java 和 hello_jsp.class

在这里插入图片描述

查看hello_jsp.java,可以看到该类为

public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {
    
    ...}

查看tomcat源码,可以看到HttpJspBase又继承自HttpServlet

public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
    
    ...}

hello_jsp.java中,有一个名为 _jspService() 的方法,该方法就是每次访问 jsp 时自动执行的方法,和 servlet 中的 service 方法一样 ,在里面可以看到大量的 out.writer(...) 代码,用于写html标签

8.4 JSP脚本

JSP脚本书写规则

JSP脚本用于在 JSP页面内定义 Java代码

JSP 脚本分类

  • <%…%>:内容会直接放到 _jspService() 方法之中

    • JSP脚本

      <%
          System.out.println("hello.jsp");
      	int i = 3;
      %>
      
    • 对应的hello_jsp.java内容(在 _jspService() 方法里面)

      System.out.println("hello.jsp");
      int i = 3;
      
  • <%=…%>:内容会放到out.print()中,作为out.print()的参数

    • JSP脚本

      <%="mouse"%>
      <%=i%>
      

      "mouse"会直接出现在页面中

    • 对应的hello_jsp.java内容(在 _jspService() 方法里面)

      out.print("mouse");
      out.print(i);
      
  • <%!…%>:内容会放到_jspService()方法之外,被类直接包含

    • JSP脚本

      <%!
          void show(){}
      	String name = "zhangsan";
      %>
      

      "mouse"会直接出现在页面中

    • 对应的hello_jsp.java内容(在hello_jsp类中,但在 _jspService() 方法外面)

      void show(){
              
              }
      String name = "zhangsan";
      

案例

  1. 新建Brand.java实体类

    package org.example.pojo;
    
    public class Brand {
          
          
        private Integer id;//id主键
        private String BrandName;//品牌名称
        private String companyName;//企业名称
        private Integer ordered;//排序字段
        private String description;//描述字段
        private Integer status;//状态:0为禁用,1为启用
        //这里省略了getter, setter, toString, 构造器方法
    }
    

    注意,创建好Brand.java后,要重新启动服务器,从而在target对应的classes里面生成class文件
    仅修改jsp文件,不用重启Tomcat

  2. 创建brand.jsp,并将brand.html复制进去

  3. 修改brand.jsp,插入java代码

    <%@ page import="java.util.List" %>
    <%@ page import="org.example.pojo.Brand" %>
    <%@ page import="java.util.ArrayList" %>
    
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    <%
        //查询数据库,这里为简单仅用一个List模拟数据库
        List<Brand> brands = new ArrayList<Brand>();
        brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1));
        brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0));
        brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1));
    %>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <input type="button" value="新增"><br>
    <hr>
    <table border="1" cellspacing="0" width="800">
        <tr>
            <th>序号</th>
            <th>品牌名称</th>
            <th>企业名称</th>
            <th>排序</th>
            <th>品牌介绍</th>
            <th>状态</th>
            <th>操作</th>
        </tr>
        <%--  通过for循环代码展示  --%>
        <%
            for(int i=0; i<brands.size(); ++i){
                Brand brand = brands.get(i);
        %>
        <tr align="center">
            <td><%=brand.getId()%></td>
            <td><%=brand.getBrandName()%></td>
            <td><%=brand.getCompanyName()%></td>
            <td><%=brand.getOrdered()%></td>
            <td><%=brand.getDescription()%></td>
    
            <%
                if(brand.getStatus() == 1){
            %>
            <td><%="启用"%></td>
            <%
                }else{
            %>
            <td><%="禁用"%></td>
            <%
                }
            %>
    
            <td><a href="#">修改</a> <a href="#">删除</a></td>
        </tr>
        <%
            }
        %>
    
    </table>
    </body>
    </html>
    
    

JSP的缺点

  • 书写麻烦:特别是复杂的页面

  • 阅读麻烦

  • 复杂度高:运行需要依赖于各种环境,JRE,JSP容器,JavaEE…

  • 占内存和磁盘:JSP会自动生成.java和.class文件占磁盘,运行的是.class文件占内存

  • 调试困难:出错后,需要找到自动生成的.java文件进行调试

  • 不利于团队协作:前端人员不会 Java,后端人员不精 HTML

    如果页面布局发生变化,前端工程师对静态页面进行修改,然后再交给后端工程师,由后端工程师再将该页面改为 JSP 页面

JSP 已逐渐退出历史舞台,以后开发更多的是使用 HTML + Ajax 来替代

发展历程

在这里插入图片描述

  • Servlet + JSP:Servlet负责逻辑处理,封装数据;JSP获取数据,遍历展示
  • Servlet + html + ajax:ajax 负责 Servlet 和 html 之间的数据沟通

8.5 EL 表达式

EL(全称Expression Language )表达式语言,用于简化 JSP 页面内的 Java 代码
EL 表达式的主要作用是获取数据
EL 表达式语法:${expression}。例如:${brands} 就是获取域中存储的 key 为 brands 的数据

使用方式

  • 编写Servlet代码:获取数据,并转发给jsp

    package org.example.web
    
    @WebServlet("/demo")
    public class ServletDemo extends HttpServlet {
          
          
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          
          
            //1. 准备数据(实际上从数据库中获取,这里用一个List模拟数据库)
            List<Brand> brands = new ArrayList<Brand>();
            brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1));
            brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0));
            brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1));
            //2. 存储到request域中
            request.setAttribute("brands", brands);
            //3. 转发
            request.getRequestDispatcher("/brand.jsp").forward(request, response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          
          
            this.doGet(request, response);
        }
    }
    
  • 编写brand.jsp代码:接收数据并展示

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        ${brands}
    </body>
    </html>
    
  • 访问页面和结果展示
    在这里插入图片描述

域对象

  • page:当前页面有效
  • request:当前请求有效
  • session:当前会话有效
  • application:当前应用有效

el 表达式获取数据,会依次从这4个域中寻找,直到找到为止。而这四个域对象的作用范围如下图所示
在这里插入图片描述

例如: ${brands},el 表达式获取数据,会先从page域对象中获取数据,如果没有再到 requet 域对象中获取数据,如果再没有再到 session 域对象中获取,如果还没有才会到 application 中获取数据

8.6 JSTL标签

概述

JSP标准标签库(Jsp Standarded Tag Library) ,使用标签取代JSP页面上的Java代码。如下代码就是JSTL标签

<c:if test="${flag == 1}">
    男
</c:if>
<c:if test="${flag == 2}">
    女
</c:if>

在这里插入图片描述

只介绍两个最常用的标签:<c:forEach> 标签和 <c:if> 标签

JSTL使用方法

  • 导入坐标

    <dependency>
        <groupId>jstl</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>1.1.2</version>
    </dependency>
    
  • 在JSP页面上引入JSTL标签库

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

    <c:if>
    

常用标签

  • c:if

    //在demo对应的Servlet中添加下列语句,并转发给brand.jsp
    request.setAttribute("status", 0);
    
    <%--在brand.jsp中判断status值--%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    ...
    <body>
        <%--  c:if  --%>
        <c:if test="${status==1}">
            <h1>启用</h1>
        </c:if>
        <c:if test="${status==0}">
            <h1>禁用</h1>
        </c:if>
    </body>
    
  • c:forEach

    • varStatus:index表示从0开始递增,count表示从1开始递增

    8.4节的案例中,brand.jsp可以改为

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <input type="button" value="新增"><br>
    <hr>
    <table border="1" cellspacing="0" width="800">
        <tr>
            <th>序号</th>
            <th>品牌名称</th>
            <th>企业名称</th>
            <th>排序</th>
            <th>品牌介绍</th>
            <th>状态</th>
            <th>操作</th>
        </tr>
        <c:forEach items="${brands}" var="brand" varStatus="status">
            <tr align="center">
                <%-- 这里brand.id实际上会 转为getId --%>
                <td>${status.count}</td>
                <td>${brand.brandName}</td>
                <td>${brand.companyName}</td>
                <td>${brand.ordered}</td>
                <td>${brand.description}</td>
                <c:if test="${brand.status==1}"><td>启用</td></c:if>
                <c:if test="${brand.status!=1}"><td>禁用</td></c:if>
                <td><a href="#">修改</a> <a href="#">删除</a></td>
            </tr>
        </c:forEach>
    </table>
    </body>
    </html>
    

    forEach第二种写法

    从0循环到10,变量名是 i ,每次自增1

    <hr>
    <c:forEach begin="0" end="10" step="1" var="i">
        <a href="#">${i}</a>
    </c:forEach>
    

    效果:
    在这里插入图片描述

9. MVC模式和三层架构

9.1 MVC模式

MVC 是一种分层开发的模式,其中:

  • M:Model,业务模型,处理业务

  • V:View,视图,界面展示

  • C:Controller,控制器,处理请求,调用模型和视图

在这里插入图片描述

9.2 三层架构

三层架构是将项目分成了三个层面,分别是 表现层业务逻辑层数据访问层

在这里插入图片描述

  • 数据访问层:对数据库的CRUD基本操作
  • 业务逻辑层:对业务逻辑进行封装,组合数据访问层中基本功能,形成复杂的业务逻辑功能。例如 注册业务功能 ,我们会先调用数据访问层的 selectByName()方法判断该用户名是否存在,如果不存在再调用数据访问层的insert()方法进行数据的添加操作
  • 表现层:接收请求,封装数据,调用业务逻辑层,响应数据

而整个流程是,浏览器发送请求,表现层的Servlet接收请求并调用业务逻辑层的方法进行业务逻辑处理,而业务逻辑层方法调用数据访问层方法进行数据的操作,依次返回到serlvet,然后servlet将数据交由 JSP 进行展示

三层架构的每一层都有特有的包名称:

  • 表现层: org.exampe.controller 或者 org.exampe.web
  • 业务逻辑层:org.exampe.service
  • 数据访问层:org.exampe.dao 或者 org.exampe.mapper

不同的框架是对不同层进行封装的

在这里插入图片描述

9.3 MVC 和 三层架构

在这里插入图片描述

三层架构 是对 MVC 模式 实现架构的思想
按照要求将不同层的代码写在不同的包下,每一层里功能职责做到单一,将来如果将表现层的技术换掉,而业务逻辑层和数据访问层的代码不需要发生变化

10. 案例

10.1 环境准备

  1. 创建项目brand_demo,引入依赖坐标和tomcat插件

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <!-- 下面坐标视情况添加 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>
    <!-- 插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
            </plugin>
        </plugins>
    </build>
    
  2. 创建三层架构的包结构
    在这里插入图片描述

  3. 检查数据库表,并创建实体类
    在这里插入图片描述

    package org.example.pojo;
    
    /**
     * 品牌实体类
     */
    public class Brand {
          
          
        private Integer id;
        private String brandName;
        private String companyName;
        private Integer ordered;
        private String description;
        private Integer status;
        //以下省略getter, setter, toString, 构造器方法
    }
    
  4. MyBatis环境:mybatis-config.xml,BrandMapper.xml,BrandMapper接口

    mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <typeAliases>
            <package name="org.example.pojo"/>
        </typeAliases>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///test?useSSL=false&amp;useServerPrepStmts=true"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <package name="org.example.mapper"/>
        </mappers>
    </configuration>
    

    BrandMapper接口

    package org.example.mapper;
    
    public interface BrandMapper {
          
          
    }
    

    BrandMapper.xml:注意位置在resource中,目录要与BrandMapper对应

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.example.mapper.BrandMapper">
    
    </mapper>
    

10.2 前端界面

在这里插入图片描述

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <a href="/brand_demo/selectAllServlet">查询所有</a>
</body>
</html>

brand.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<input type="button" value="新增" id="add"><br>
<hr>
<table border="1" cellspacing="0" width="800">
  <tr>
    <th>序号</th>
    <th>品牌名称</th>
    <th>企业名称</th>
    <th>排序</th>
    <th>品牌介绍</th>
    <th>状态</th>
    <th>操作</th>
  </tr>
  <c:forEach items="${brands}" var="brand" varStatus="status">
    <tr align="center">
        <%-- 这里brand.id实际上会 转为getId --%>
      <td>${status.count}</td>
      <td>${brand.brandName}</td>
      <td>${brand.companyName}</td>
      <td>${brand.ordered}</td>
      <td>${brand.description}</td>
      <c:if test="${brand.status==1}"><td>启用</td></c:if>
      <c:if test="${brand.status!=1}"><td>禁用</td></c:if>
      <td>
        <a href="/brand_demo/selectByIdServlet?id=${brand.id}">修改</a>
        <a href="/brand_demo/deleteServlet?id=${brand.id}">删除</a>
      </td>
    </tr>
  </c:forEach>
</table>
<script>
  document.getElementById("add").onclick = function (){
    location.href = "/brand_demo/addBrand.jsp"
  }
</script>
</body>
</html>

addBrand.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>添加品牌</title>
</head>
<body>
<h3>添加品牌</h3>
<form action="/brand_demo/addServlet" method="post">
    品牌名称:<input name="brandName"><br>
    企业名称:<input name="companyName"><br>
    排序:<input name="ordered"><br>
    描述信息:<textarea rows="5" cols="20" name="description"></textarea><br>
    状态:
    <input type="radio" name="status" value="0">禁用
    <input type="radio" name="status" value="1">启用<br>

    <input type="submit" value="提交">
</form>
</body>
</html>

update.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>修改品牌</title>
</head>
<body>
<h3>修改品牌</h3>
<form action="/brand_demo/updateServlet" method="post">

    <%--隐藏域,提交id--%>
    <input type="hidden" name="id" value="${brand.id}">

    品牌名称:<input name="brandName" value="${brand.brandName}"><br>
    企业名称:<input name="companyName" value="${brand.companyName}"><br>
    排序:<input name="ordered" value="${brand.ordered}"><br>
    描述信息:<textarea rows="5" cols="20" name="description">${brand.description} </textarea><br>
    状态:
    <c:if test="${brand.status == 0}">

        <input type="radio" name="status" value="0" checked>禁用
        <input type="radio" name="status" value="1">启用<br>
    </c:if>

    <c:if test="${brand.status == 1}">

        <input type="radio" name="status" value="0" >禁用
        <input type="radio" name="status" value="1" checked>启用<br>
    </c:if>


    <input type="submit" value="提交">
</form>
</body>
</html>

10.3 后端代码

需求:点击“查询所有”进入主页面,主页面允许增加、修改、删除
在这里插入图片描述

功能:查询所有,增加,修改,删除

实现步骤:

  1. 先在BrandMapper中定义接口和sql语句
  2. 然后完成service层代码
  3. 根据前端界面的提交地址,编写Servlet代码进行数据转发

项目结构

在这里插入图片描述

util

SqlSessionFactoryUtils.java

package org.example.util;

public class SqlSessionFactoryUtils {
    
    
    private static SqlSessionFactory sqlSessionFactory;
    static {
    
    
        try{
    
    
           String resource = "mybatis-config.xml";
           InputStream inputStream = Resources.getResourceAsStream(resource);
           sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
    public static SqlSessionFactory getSqlSessionFactory(){
    
    
        return sqlSessionFactory;
    }
}

mapper

BrandMapper.java

package org.example.mapper;

public interface BrandMapper {
    
    
    /**
     * 查询所有
     * @return
     */
    @Select("select * from tb_brand")
    @ResultMap("brandResultMap")
    List<Brand> selectAll();

    /**
     * 新增数据
     * @param brand
     */
    @Insert("insert into tb_brand values(null, #{brandName}, #{companyName}, #{ordered}, #{description}, #{status})")
    void add(Brand brand);

    /**
     * 根据id查找数据
     * @param id
     * @return
     */
    @Select("select * from tb_brand where id = #{id}")
    @ResultMap("brandResultMap")
    Brand selectById(int id);
    /**
     * 修改数据
     * @param brand
     */
    @Update("update tb_brand set brand_name = #{brandName}, company_name = #{companyName}, ordered = #{ordered}, description = #{description}, status = #{status} where id = #{id}")
    void update(Brand brand);

    /**
     * 删除数据
     * @param id
     */
    @Delete("delete from tb_brand where id = #{id}")
    void delete(int id);
}

mapper.xml

BrandMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.BrandMapper">
    <resultMap id="brandResultMap" type="org.example.pojo.Brand">
        <result column="brand_name" property="brandName"></result>
        <result column="company_name" property="companyName"></result>
    </resultMap>
</mapper>

service

BrandService.java

package org.example.service;

public class BrandService {
    
    
    private SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();//所有方法都会调用,放到成员变量里面
    /**
     * 查询所有
     * @return
     */
    public List<Brand> selectAll(){
    
    
        //1. 建立连接
        SqlSession sqlSession = factory.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        //2. 调用BrandMapper.selectAll()
        List<Brand> brands = mapper.selectAll();
        //3. 要关闭sqlSession
        sqlSession.close();
        return brands;
    }
    /**
     * 新增数据
     * @param brand
     */
    public void add(Brand brand){
    
    
        SqlSession sqlSession = factory.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        mapper.add(brand);
        //格外注意:要提交事务
        sqlSession.commit();
        sqlSession.close();
    }

    /**
     * 根据id查找数据
     * @param id
     * @return
     */
    public Brand selectById(int id){
    
    
        SqlSession sqlSession = factory.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        Brand brand = mapper.selectById(id);
        sqlSession.close();
        return brand;
    }

    /**
     * 修改数据
     * @param brand
     */
    public void update(Brand brand){
    
    
        SqlSession sqlSession = factory.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        mapper.update(brand);
        sqlSession.commit();
        sqlSession.close();
    }

    /**
     * 删除数据
     * @param id
     */
    public void delete(int id){
    
    
        SqlSession sqlSession = factory.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        mapper.delete(id);
        sqlSession.commit();
        sqlSession.close();
    }
}

web

SelectAllServlet.java

//查询所有
package org.example.web;

@WebServlet("/selectAllServlet")
public class SelectAllServlet extends HttpServlet {
    
    
    private BrandService service = new BrandService();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        List<Brand> brands = service.selectAll();
        request.setAttribute("brands", brands);
        request.getRequestDispatcher("/brand.jsp").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

AddServlet.java

//增加数据
package org.example.web;

@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {
    
    
    private BrandService service = new BrandService();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        request.setCharacterEncoding("utf-8");
        //1. 增加数据
        Brand brand = new Brand();
        brand.setBrandName(request.getParameter("brandName"));
        brand.setCompanyName(request.getParameter("companyName"));
        brand.setOrdered(Integer.parseInt(request.getParameter("ordered")));
        brand.setDescription(request.getParameter("description"));
        brand.setStatus(Integer.parseInt(request.getParameter("status")));
        service.add(brand);
        //2. 转发到查询所有Servlet
        request.getRequestDispatcher("/selectAllServlet").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

SelectByIdServlet.java

//修改数据回显时会用到
package org.example.web;

@WebServlet("/selectByIdServlet")
public class SelectByIdServlet extends HttpServlet {
    
    
    private BrandService service = new BrandService();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        String id = request.getParameter("id");
        Brand brand = service.selectById(Integer.parseInt(id));
        request.setAttribute("brand", brand);
        request.getRequestDispatcher("/update.jsp").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

UpdateServlet.java

package org.example.web;

@WebServlet("/updateServlet")
public class UpdateServlet extends HttpServlet {
    
    
    private BrandService service = new BrandService();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        request.setCharacterEncoding("utf-8");
        //1. 接收表单中的数据
        Brand brand = new Brand();
        brand.setId(Integer.parseInt(request.getParameter("id")));
        brand.setBrandName(request.getParameter("brandName"));
        brand.setCompanyName(request.getParameter("companyName"));
        brand.setOrdered(Integer.parseInt(request.getParameter("ordered")));
        brand.setDescription(request.getParameter("description"));
        brand.setStatus(Integer.parseInt(request.getParameter("status")));
        service.update(brand);
        //2. 转发到查询所有Servlet
        request.getRequestDispatcher("/selectAllServlet").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

DeleteServlet.java

package org.example.web;

@WebServlet("/deleteServlet")
public class DeleteServlet extends HttpServlet {
    
    
    private BrandService service = new BrandService();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        int id = Integer.parseInt(request.getParameter("id"));
        service.delete(id);
        request.getRequestDispatcher("/selectAllServlet").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

总结

* 查询所有:index.html --> SelectAllServlet.java --> brand.jsp
* 新增:点击“新增” --> addBrand.jsp --> AddServlet.java --> SelectAllServlet.java --> brand.jsp
* 修改:点击“修改” --> SelectByIdServlet.jsp --> update.jsp --> UpdateServlet.java --> SelectAllServlet.java --> brand.jsp
* 删除:点击“删除” --> DeleteServlet --> SelectAllServlet.java --> brand.jsp

如果出现:Result Maps collection does not contain value for … 报错
检查BrandMapper.xml路径是否正确

代码重点

  1. 实体类和数据库列名不一致时,ResultMap的用法

  2. 修改数据时,要先回显数据,即显示该数据内容
    在这里插入图片描述

  3. 修改数据时,update.jsp里面的隐藏域

11. 会话技术

会话: 用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应

会话跟踪: 一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

  • 浏览器和服务器之间使用的是HTTP请求来进行数据传输
  • HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求
  • HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响
  • 请求与请求之间独立后,就无法实现多次请求之间的数据共享

实现会话跟踪的技术

  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session

Cookie是存储在浏览器端而Session是存储在服务器端

11.1 Cookie

概念

Cookie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问

工作流程

在这里插入图片描述

服务端提供了两个Servlet,分别是ServletA和ServletB
浏览器发送HTTP请求1给服务端,服务端ServletA接收请求并进行业务处理
服务端ServletA在处理的过程中可以创建一个Cookie对象并将name=zs的数据存入Cookie
服务端ServletA在响应数据的时候,会把Cookie对象响应给浏览器
浏览器接收到响应数据,会把Cookie对象中的数据存储在浏览器内存中,此时浏览器和服务端就建立了一次会话
在同一次会话中浏览器再次发送HTTP请求2给服务端ServletB,浏览器会携带Cookie对象中的所有数据
ServletB接收到请求和数据后,就可以获取到存储在Cookie对象中的数据,这样同一个会话中的多次请求之间就实现了数据共享

实际上也是通过request和response来交换数据
但Cookie和Session允许持久化存储。在之前的过程中,例如brand,jsp在收到数据后必须通过请求发送出去,如果不再请求,结束本次服务,那么数据将丢失。通过Cookie和Session,数据会被保存在浏览器本地中,即便本次服务停止,也不会导致数据丢失

Cookie的基本使用

发送Cookie

//AServlet
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    //发送Cookie
    //1. 创建Cookie对象
    //例如当客户端发起一个连接请求时,服务器端将为其创建一个Cookie,并返回给它
    Cookie cookie = new Cookie("username", "zs");
    //2. 发送Cookie
    response.addCookie(cookie);
}

在这里插入图片描述

获取Cookie

以localhost为例,获取Cookie只能获取localhost下的所有Cookie,即获取客户端携带的所有Cookie

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    //获取Cookie
    //1. 获取Cookie数组
    //客户端已经获取到Cookie,再发出请求时,服务器端就可以从它的请求中获取Cookie,判断是否是已连接的客户端,如果不是,则拒绝服务
    Cookie[] cookies = request.getCookies();
    //2. 遍历数组
    for(Cookie cookie:cookies){
    
    
        String name = cookie.getName();
        if("username".equals(name)){
    
    
            String value = cookie.getValue();
            System.out.println(name+":"+value);
            break;
        }
    }
}

先访问AServlet.java,再访问BServlet.java,可以看到Cookie被成功获取

Cookie的原理分析

对于Cookie的实现原理是基于HTTP协议的, 其中涉及到HTTP协议中的两个请求头信息:

  • 响应头:set-cookie
  • 请求头: cookie

在这里插入图片描述

对于AServlet响应数据的时候,Tomcat服务器都是基于HTTP协议来响应数据
当Tomcat发现后端要返回的是一个Cookie对象之后,Tomcat就会在响应头中添加一行数据Set-Cookie:username=zs
浏览器获取到响应结果后,从响应头中就可以获取到Set-Cookie对应值username=zs,并将数据存储在浏览器的内存
浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加Cookie: username=zs发送给服务端BServlet
Request对象会把请求头中cookie对应的值封装成一个个Cookie对象,最终形成一个数组
BServlet通过Request对象获取到Cookie[]后,就可以从中获取自己需要的数据

访问AServlet,可以看到

在这里插入图片描述

再访问BServlet,可以看到

在这里插入图片描述

Cookie的使用细节

  • Cookie的存活时间

    默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁
    Cookie持久化存储:setMaxAge

    setMaxAge(int seconds)
    
    • 正数:将Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除

    • 负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,则Cookie被销毁

    • 零:删除对应Cookie

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          
          
        //发送Cookie
        //1. 创建Cookie对象
        Cookie cookie = new Cookie("username", "zs");
        //2. 设置存活时间
        cookie.setMaxAge(60*60*24*7);//设置为一周,易阅读,需程序计算
        //cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算
        //3. 发送Cookie
        response.addCookie(cookie);
    }
    

    此时访问AServlet后,关闭浏览器,再访问BServlet依然可以获取到Cookie

  • Cookie存储中文

    Cookie不支持中文,如果有中文,报500错误,修改方式如下

    • AServlet发送Cookie:编码

      //发送Cookie
      //1. 创建Cookie对象
      String value = "张三";
      //URL编码
      value = URLEncoder.encode(value, "UTF-8");
      Cookie cookie = new Cookie("username", value);
      //2. 设置存活时间
      cookie.setMaxAge(60*60*24*7);//易阅读,需程序计算
      //cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算
      //3. 发送Cookie
      response.addCookie(cookie);
      
    • bServlet获取Cookie,解码

      @Override
      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              
              
          //获取Cookie
          //1. 获取Cookie数组
          Cookie[] cookies = request.getCookies();
          //2. 遍历数组
          for(Cookie cookie:cookies){
              
              
              String name = cookie.getName();
              if("username".equals(name)){
              
              
                  String value = cookie.getValue();
                  //URL解码
                  value = URLDecoder.decode(value, "UTF-8");
                  System.out.println(name+":"+value);
                  break;
              }
          }
      }
      

    可以使用encode()函数对字符串进行编码,转换成二进制字节数据
    然后用decode()函数将字节解码成字符串

11.2 Session

概念

Session:服务端会话跟踪技术:将数据保存到服务端。

  • Session是存储在服务端而Cookie是存储在客户端
  • 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
  • 存储在服务端的数据相比于客户端来说更安全

工作流程
在这里插入图片描述

在服务端的AServlet获取一个Session对象,把数据存入其中
在服务端的BServlet获取到相同的Session对象,从中取出数据
就可以实现一次会话中多次请求之间的数据共享了
现在最大的问题是如何保证AServlet和BServlet使用的是同一个Session对象?

Session的基本使用

在JavaEE中提供了HttpSession接口,来实现一次会话的多次请求之间数据共享功能

HttpSession session = request.getSession();//获取Session对象,使用的是request对象
void setAttribute(String name, Object o);//存储数据到 session 域中
Object getAttribute(String name);//根据 key,获取值
void removeAttribute(String name);//根据 key,删除该键值对

存储数据到Session中

//SessionDemo1
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    //存储数据到Session中
    //1. 获取Session对象
    HttpSession session = request.getSession();
    //2. 存储数据
    session.setAttribute("username", "zs");
}

获取Session

//SessionDemo2
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    //从Session中获取数据
    //1. 获取Session对象
    HttpSession session = request.getSession();
    //2. 获取数据
    Object username = session.getAttribute("username");
    System.out.println(username);
}

这里两个Servlet类中获取的Session对象是同一个

Session的原理分析

Session是基于Cookie实现的

Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个

原理

在这里插入图片描述

(1)demo1在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10
(2)demo1在session中存入其他数据并处理完成所有业务后,需要通过Tomcat服务器响应结果给浏览器
(3)Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识id:10当做一个cookie,添加Set-Cookie:JESSIONID=10到响应头中,并响应给浏览器
(4)浏览器接收到响应结果后,会把响应头中的coookie数据存储到浏览器的内存中
(5)浏览器在同一会话中访问demo2的时候,会把cookie中的数据按照cookie: JESSIONID=10的格式添加到请求头中并发送给服务器Tomcat
(6)demo2获取到请求后,从请求头中就读取cookie中的JSESSIONID值为10,然后就会到服务器内存中寻找id:10的session对象,如果找到了,就直接返回该对象,如果没有则新创建一个session对象
(7)关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象

展示

SessionDemo1:第一次请求时,在请求中要求获取Session,服务器会响应一个sessionID

在这里插入图片描述

SessionDemo2:第二次请求时,会携带这个ID,告诉服务器要找哪一个session

在这里插入图片描述

Session的使用细节

  • Session钝化与活化

    服务器重启后,Session中的数据是否还在?
    结果是不存在,服务器重新启动后,内存中的数据应该是已经被释放,对象也应该都销毁了。但实际上我们希望做到就算服务器重启了,数据也能保存下来,从而不影响客户端

    注意:这里的关闭和重启必须是正常的关闭和重启,即在项目路径如tomcat-demo2下执行 mvn tomcat7:run 和 ctrl+c

    经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的

    • 钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中

      钝化的数据路径为:项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser
      在这里插入图片描述

    • 活化:再次启动服务器后,从文件中加载数据到Session中

      数据加载到Session中后,路径中的SESSIONS.ser文件会被删除掉

  • Session销毁

    • 默认情况下,无操作,30分钟自动销毁
      这个值是tomcat中写好的,如果要修改,可以在自己项目的web.xml文件中加入以下配置

      <session-config>
          <session-timeout>100</session-timeout>
      </session-config>
      
    • 调用Session对象的invalidate()进行销毁

      //SessionDemo2.java
      @Override
      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              
              
          //从Session中获取数据
          //1. 获取Session对象
          HttpSession session = request.getSession();
          //2. 销毁数据
          session.invalidate();
          //3. 此时再获取数据,报错:HTTP Status 500 - getAttribute: Session already invalidated
          Object username = session.getAttribute("username");
          System.out.println(username);
      }
      

11.3 小结

  • Cookie 和 Session 都是来完成一次会话内多次请求间数据共享的。

  • 区别:

    • 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
    • 安全性:Cookie不安全,Session安全
    • 数据大小:Cookie最大3KB,Session无大小限制
    • 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
    • 服务器性能:Cookie不占服务器资源,Session占用服务器资源
  • 应用场景:

    • 购物车:使用Cookie来存储
    • 以登录用户的名称展示:使用Session来存储
    • 记住我功能:使用Cookie来存储
    • 验证码:使用session来存储
  • 结论

    • Cookie是用来保证用户在未登录情况下的身份识别
    • Session是用来保存用户登录后的数据

具体用Cookie还是Session需要根据具体的业务进行具体分析

11.4 案例:登录注册

需求说明:

  1. 完成用户登录功能,如果用户勾选“记住用户” ,则下次访问登录页面自动填充用户名密码

  2. 完成注册功能,并实现验证码功能

前端界面

login.jsp

在这里插入图片描述

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>login</title>
    <link href="css/login.css" rel="stylesheet">
</head>

<body>
<div id="loginDiv" style="height: 350px">
    <form action="/brand_demo/loginServlet" method="post" id="form">
        <h1 id="loginMsg">LOGIN IN</h1>
        <div id="errorMsg">${login_msg} ${register_msg}</div>
        <p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
        <p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>
        <p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
        <div id="subDiv">
            <input type="submit" class="button" value="login up">
            <input type="reset" class="button" value="reset">&nbsp;&nbsp;&nbsp;
            <a href="register.jsp">没有账号?</a>
        </div>
    </form>
</div>
</body>
</html>

register.jsp

在这里插入图片描述

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎注册</title>
    <link href="css/register.css" rel="stylesheet">
</head>
<body>

<div class="form-div">
    <div class="reg-content">
        <h1>欢迎注册</h1>
        <span>已有帐号?</span> <a href="login.html">登录</a>
    </div>
    <form id="reg-form" action="/brand_demo/registerServlet" method="post">
        <table>
            <tr>
                <td>用户名</td>
                <td class="inputs">
                    <input name="username" type="text" id="username">
                    <br>
                    <span id="username_err" class="err_msg" >${register_msg}</span>
                </td>

            </tr>

            <tr>
                <td>密码</td>
                <td class="inputs">
                    <input name="password" type="password" id="password">
                    <br>
                    <span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
                </td>
            </tr>
            <tr>
                <td>验证码</td>
                <td class="inputs">
                    <input name="checkCode" type="text" id="checkCode">
                    <img id="checkCodeImg" src="/brand_demo/checkCodeServlet">
                    <a href="#" id="changeImg" >看不清?</a>
                </td>
            </tr>

        </table>

        <div class="buttons">
            <input value="注 册" type="submit" id="reg_btn">
        </div>
        <br class="clear">
    </form>
</div>

<script>
    document.getElementById("changeImg").onclick = function () {
        // 如果这里的src只写src = "/brand_demo/checkCodeServlet",那么因为浏览器会缓存静态资源而导致切换不了
        // 使用new Data(),是保证每次路径都不一致
        document.getElementById("checkCodeImg").src = "/brand_demo/checkCodeServlet?"+new Date().getMilliseconds();
    }
</script>
</body>
</html>

brand.jsp

在这里插入图片描述

只需要在先前的brand.jsp中添加一句

<h1>${user.username},欢迎您</h1>

登录功能

基于第7.4节的登陆注册案例和第10节的brand_demo案例实现
准备:在brand_demo里添加之前写过的 User.java,UserMapper接口,UserMapper.xml,并对login.jsp和register.jsp做出修改

在这里插入图片描述

流程:login.jsp --> LoginServlet.java --> UserService.java --> selectAllServlet.java --> brand.jsp

UserService.java

package org.example.service;

public class UserService {
    
    
    private SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
    /**
     * 登录方法login(),它将调用UserMapper里的select方法:根据用户名和密码查询用户对象
     * @param username
     * @param password
     * @return
     */
    public User login(String username, String password){
    
    
        SqlSession session = factory.openSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.select(username, password);
        session.close();
        return user;
    }
}

LoginServlet.java

编写LoginServlet.java代码

package org.example.web;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    
    
    private UserService service = new UserService();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        //1. 获取用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //2. 调用service查询
        User user = service.login(username, password);
        //3. 判断
        if(user != null){
    
    
            //登录成功,跳转到查询所有的brand.jsp
            //将登录成功后的User对象,存储到session中
            HttpSession session = request.getSession();
            session.setAttribute("user", user);

            String contextPath = request.getContextPath();
            response.sendRedirect(contextPath+"/selectAllServlet");//使用重定向,表示这是两次请求
        }else{
    
    
            //登录失败,跳转回login.jsp
            request.setAttribute("login_msg", "用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

在login.jsp中获取登录失败时的提示信息

<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>

代码重点

  • 在LoginServlet.java使用session,从而在登录之后访问其他页面也可以获取到用户信息

“记住我”功能

需求: 如果用户勾选“Remember” ,则下次访问登陆页面自动填充用户名密码
实现:使用Cookie;

  • 将用户名和密码写入Cookie中,并且持久化存储Cookie,下次访问浏览器会自动携带Cookie
  • 在页面获取Cookie数据后,设置到用户名和密码框中
  • 何时写入Cookie?
    • 用户必须登陆成功后才需要写
    • 用户必须在登录页面勾选了记住我的复选框

LoginServlet.java

在login.jsp中设置提交地址和方式,以及remember复选框

<form action="/brand_demo/loginServlet" method="post" id="form">
...
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>

LoginServlet.java中存储Cookie

package org.example.web;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    
    
    private UserService service = new UserService();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //获取复选框数据
        String remember = request.getParameter("remember");
        User user = service.login(username, password);
        if(user != null){
    
    
            //登录成功后,判断用户是否勾选记住我(如果登录失败,则不存储Cookie)
            if("1".equals(remember)){
    
    
                //如果没勾选,也不存储。只有2个条件都满足,才存储Cookie
                //1. 创建Cookie对象
                Cookie c_username = new Cookie("username", username);
                Cookie c_password = new Cookie("password", password);
                //2. 设置Cookie的存活时间
                c_username.setMaxAge(60*60*24*7);//设置存活时间为一周
                c_password.setMaxAge(60*60*24*7);
                //3. 发送
                response.addCookie(c_username);
                response.addCookie(c_password);
            }
            HttpSession session = request.getSession();
            session.setAttribute("user", user);
            String contextPath = request.getContextPath();
            response.sendRedirect(contextPath+"/selectAllServlet");//使用重定向,表示这是两次请求
        }else{
    
    
            request.setAttribute("login_msg", "用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

login.jsp

login.jsp中获取Cookie

<p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
<p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>

这样设置后,如果勾选了“Remember”,那么第二次再访问login.jsp即可自动填充

注册功能

UserService.java

在UserService.java中添加注册方法 register()

/**
 * 注册方法register,它将调用UserMapper里的selectByUsername方法验证是否用户名重复,和add方法增加用户
 * @param user
 * @return
 */
public boolean register(User user){
    
    
    SqlSession session = factory.openSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    //1. 判断用户名是否存在
    User u = mapper.selectByUsername(user.getUsername());
    if(u == null){
    
    
        //用户名不存在
        mapper.add(user);
        session.commit();
    }
    session.close();
    return u == null;
}

RegisterServlet.java

在register.jsp中设置

<form id="reg-form" action="/brand_demo/registerServlet" method="post">

编写RegisterServlet.java代码

package org.example.web;

@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
    
    
    private UserService service = new UserService();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        //1. 获取用户名和密码
        User user = new User();
        user.setUsername(request.getParameter("username"));
        user.setPassword(request.getParameter("password"));
        //2. 调用service注册
        boolean flag = service.register(user);
        //3. 判断注册成功与否
        if(flag){
    
    
            request.setAttribute("register_msg", "注册成功,请登录");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }else {
    
    
            request.setAttribute("register_msg", "用户名已存在");
            request.getRequestDispatcher("/register.jsp").forward(request, response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

在登录界面login.jsp中设置

<div id="errorMsg">${login_msg} ${register_msg}</div>
...
<div id="subDiv">
    <input type="submit" class="button" value="login up">
    <input type="reset" class="button" value="reset">&nbsp;&nbsp;&nbsp;
    <a href="register.jsp">没有账号?</a>
</div>

在注册界面register.jsp中设置

<span id="username_err" class="err_msg" >${register_msg}</span>

编写完成后,访问login.jsp,再点击“没有账户?”,即可进行注册

验证码功能 - 展示

需求:生成验证码图片,点击图片切换验证码

  • 验证码就是使用Java代码生成的一张图片
  • 验证码的作用: 防止机器自动注册,攻击服务器

CheckCodeUtil.java

用于生成图片验证码,这里只需了解里面的outputVerifyImage()方法

package org.example.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Arrays;
import java.util.Random;
/**
 * 生成验证码工具类
 */
public class CheckCodeUtil {
    
    
    public static final String VERIFY_CODES = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static Random random = new Random();
	//测试方法,将生成"e://a.jpg"验证码图片
    public static void main(String[] args) throws IOException {
    
    
        OutputStream fos = new FileOutputStream("d://a.jpg");
        String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, fos, 4);
        System.out.println(checkCode);
    }
    /**
     * 输出随机验证码图片流,并返回验证码值(一般传入输出流,响应response页面端,Web项目用的较多)
     *
     * @param width 图片宽度
     * @param height 图片高度
     * @param os  输出流
     * @param verifySize 数据长度
     * @return 验证码数据
     * @throws IOException
     */
    public static String outputVerifyImage(int width, int height, OutputStream os, int verifySize) throws IOException {
    
    
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(width, height, os, verifyCode);
        return verifyCode;
    }
    /**
     * 使用系统默认字符源生成验证码
     *
     * @param verifySize 验证码长度
     * @return
     */
    public static String generateVerifyCode(int verifySize) {
    
    
        return generateVerifyCode(verifySize, VERIFY_CODES);
    }
    /**
     * 使用指定源生成验证码
     *
     * @param verifySize 验证码长度
     * @param sources    验证码字符源
     * @return
     */
    public static String generateVerifyCode(int verifySize, String sources) {
    
    
        // 未设定展示源的字码,赋默认值大写字母+数字
        if (sources == null || sources.length() == 0) {
    
    
            sources = VERIFY_CODES;
        }
        int codesLen = sources.length();
        Random rand = new Random(System.currentTimeMillis());
        StringBuilder verifyCode = new StringBuilder(verifySize);
        for (int i = 0; i < verifySize; i++) {
    
    
            verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
        }
        return verifyCode.toString();
    }
    /**
     * 生成随机验证码文件,并返回验证码值 (生成图片形式,用的较少)
     *
     * @param w
     * @param h
     * @param outputFile
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException {
    
    
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, outputFile, verifyCode);
        return verifyCode;
    }
    /**
     * 生成指定验证码图像文件
     *
     * @param w
     * @param h
     * @param outputFile
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, File outputFile, String code) throws IOException {
    
    
        if (outputFile == null) {
    
    
            return;
        }
        File dir = outputFile.getParentFile();
        //文件不存在
        if (!dir.exists()) {
    
    
            //创建
            dir.mkdirs();
        }
        try {
    
    
            outputFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(outputFile);
            outputImage(w, h, fos, code);
            fos.close();
        } catch (IOException e) {
    
    
            throw e;
        }
    }
    /**
     * 输出指定验证码图片流
     *
     * @param w
     * @param h
     * @param os
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {
    
    
        int verifySize = code.length();
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Random rand = new Random();
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 创建颜色集合,使用java.awt包下的类
        Color[] colors = new Color[5];
        Color[] colorSpaces = new Color[]{
    
    Color.WHITE, Color.CYAN,
                Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                Color.PINK, Color.YELLOW};
        float[] fractions = new float[colors.length];
        for (int i = 0; i < colors.length; i++) {
    
    
            colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
            fractions[i] = rand.nextFloat();
        }
        Arrays.sort(fractions);
        // 设置边框色
        g2.setColor(Color.GRAY);
        g2.fillRect(0, 0, w, h);

        Color c = getRandColor(200, 250);
        // 设置背景色
        g2.setColor(c);
        g2.fillRect(0, 2, w, h - 4);

        // 绘制干扰线
        Random random = new Random();
        // 设置线条的颜色
        g2.setColor(getRandColor(160, 200));
        for (int i = 0; i < 20; i++) {
    
    
            int x = random.nextInt(w - 1);
            int y = random.nextInt(h - 1);
            int xl = random.nextInt(6) + 1;
            int yl = random.nextInt(12) + 1;
            g2.drawLine(x, y, x + xl + 40, y + yl + 20);
        }
        // 添加噪点
        // 噪声率
        float yawpRate = 0.05f;
        int area = (int) (yawpRate * w * h);
        for (int i = 0; i < area; i++) {
    
    
            int x = random.nextInt(w);
            int y = random.nextInt(h);
            // 获取随机颜色
            int rgb = getRandomIntColor();
            image.setRGB(x, y, rgb);
        }
        // 添加图片扭曲
        shear(g2, w, h, c);
        g2.setColor(getRandColor(100, 160));
        int fontSize = h - 4;
        Font font = new Font("Algerian", Font.ITALIC, fontSize);
        g2.setFont(font);
        char[] chars = code.toCharArray();
        for (int i = 0; i < verifySize; i++) {
    
    
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);
            g2.setTransform(affine);
            g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
        }
        g2.dispose();
        ImageIO.write(image, "jpg", os);
    }
    /**
     * 随机颜色
     *
     * @param fc
     * @param bc
     * @return
     */
    private static Color getRandColor(int fc, int bc) {
    
    
        if (fc > 255) {
    
    
            fc = 255;
        }
        if (bc > 255) {
    
    
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
    private static int getRandomIntColor() {
    
    
        int[] rgb = getRandomRgb();
        int color = 0;
        for (int c : rgb) {
    
    
            color = color << 8;
            color = color | c;
        }
        return color;
    }
    private static int[] getRandomRgb() {
    
    
        int[] rgb = new int[3];
        for (int i = 0; i < 3; i++) {
    
    
            rgb[i] = random.nextInt(255);
        }
        return rgb;
    }
    private static void shear(Graphics g, int w1, int h1, Color color) {
    
    
        shearX(g, w1, h1, color);
        shearY(g, w1, h1, color);
    }
    private static void shearX(Graphics g, int w1, int h1, Color color) {
    
    

        int period = random.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);

        for (int i = 0; i < h1; i++) {
    
    
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(0, i, w1, 1, (int) d, 0);
            if (borderGap) {
    
    
                g.setColor(color);
                g.drawLine((int) d, i, 0, i);
                g.drawLine((int) d + w1, i, w1, i);
            }
        }

    }
    private static void shearY(Graphics g, int w1, int h1, Color color) {
    
    

        int period = random.nextInt(40) + 10; // 50;

        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < w1; i++) {
    
    
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(i, 0, 1, h1, 0, (int) d);
            if (borderGap) {
    
    
                g.setColor(color);
                g.drawLine(i, (int) d, i, 0);
                g.drawLine(i, (int) d + h1, i, h1);
            }
        }
    }
}

CheckCodeServlet.java

首先在register.jsp中设置验证码图片路径

<tr>
    <td>验证码</td>
    <td class="inputs">
        <input name="checkCode" type="text" id="checkCode">
        <img id="checkCodeImg" src="/brand_demo/checkCodeServlet">
        <a href="#" id="changeImg" >看不清?</a>
    </td>
</tr>

编写CheckCodeServlet代码:生成验证码图片

package org.example.web;

@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        ServletOutputStream os = response.getOutputStream();
        String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

还需要实现“看不清”功能,即换一张验证码,只需要在register.jsp中设置

<script>
    document.getElementById("changeImg").onclick = function () {
        // 如果这里的src只写src = "/brand_demo/checkCodeServlet",那么因为浏览器会缓存静态资源而导致切换不了
        // 使用new Data(),是保证每次路径都不一致
        document.getElementById("checkCodeImg").src = "/brand_demo/checkCodeServlet?"+new Date().getMilliseconds();
    }
</script>

验证码功能 - 校验

需求:判断程序生成的验证码 和 用户输入的验证码 是否一样,如果不一样,则阻止注册
实现:验证码图片访问和提交注册表单是两次请求,所以要将程序生成的验证码存入Session中

CheckCodeServlet.java

将验证码存入Session

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    ServletOutputStream os = response.getOutputStream();
    String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
    //将验证码存入Session
    HttpSession session = request.getSession();
    session.setAttribute("checkCodeGen", checkCode);
}

RegisterServlet.java

package org.example.web;

@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
    
    
    private UserService service = new UserService();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        User user = new User();
        user.setUsername(request.getParameter("username"));
        user.setPassword(request.getParameter("password"));

        // 获取用户输入的验证码
        String checkCode = request.getParameter("checkCode");
        // 获取程序生成的验证码
        HttpSession session = request.getSession();
        String checkCodeGen = (String) session.getAttribute("checkCodeGen");
        // 在注册之前进行比对
        if(!checkCodeGen.equalsIgnoreCase(checkCode)){
    
     //一般忽略大小写
            //不允许注册
            request.setAttribute("register_msg", "验证码错误");
            request.getRequestDispatcher("/register.jsp").forward(request, response);
            return;
        }

        boolean flag = service.register(user);
        if(flag){
    
    
            request.setAttribute("register_msg", "注册成功,请登录");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }else {
    
    
            request.setAttribute("register_msg", "用户名已存在");
            request.getRequestDispatcher("/register.jsp").forward(request, response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        this.doGet(request, response);
    }
}

猜你喜欢

转载自blog.csdn.net/widsoor/article/details/128612561