JavaWeb第十六天

会话技术

1、会话

会话
  一次会话中包含多次请求和响应
一次会话
  浏览器第一次给服务器西元发送请求,会话建立,
  直到有一方断开为止(服务器或客户端关闭)
功能
  在一次会话的范围内的多次请求之间共享数据
  HTTP协议无状态,即浏览器发送多次请求,服务器发送多次响应,每次请求响应和其它的请求响应之间不共享数据
  栗子:将多件商品分多次加入购物车,是多次请求响应,最终进入购物车结算又是一次请求响应,它们彼此间应该交换数据,否则不知道结算的商品是什么
方式
  客户端会话技术 Cookie
    将数据存到客户端
  服务器端会话技术 Session
    将数据存到服务器端

2、Cookie 客户端会话技术,将数据存到客户端

第一次请求得到浏览器响应的数据后,将其存到本地,下一次发送请求时带着这些数据
快速入门
  (1)创建Cookie对象,绑定数据
    new Cookie(String name, String value);
  (2)第一次响应时,服务器发送Cookie对象给浏览器
    response.addCookie(Cookie cookie);
  (3)下一次发送请求时,服务器获取浏览器携带的Cookie数组,遍历得到数据
    Cookie[] request.getCookies();

/*
    Cookie快速入门
 */
@javax.servlet.annotation.WebServlet("/demo01cookie")
public class Demo01Cookie extends javax.servlet.http.HttpServlet {
    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
        //1、创建Cookie对象
        Cookie c = new Cookie("msg", "hello");
        //2、服务器发送响应时将Cookie对象发送给浏览器
        response.addCookie(c);
    }

    protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
        this.doPost(request, response);
    }
}
@WebServlet("/demo02cookie")
public class Demo02Cookie extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //3、服务器获取下一次请求携带的Cookie对象数组,遍历得到数据
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                String value = cookie.getValue();
                System.out.println(name + ":" + value);
                //msg:hello
                //Idea-8296e770:710ed074-221b-45d7-a3b5-9d177d11d78a
            }
        }
    }

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

实现原理
  服务器中的两个Servlet Demo01Cookie发送cookie Demo02Cookie获取cookie
  底层都是通过HTTP协议的请求和响应来完成的
  浏览器请求Demo1Cookie的资源,Demo1Cookie发送cookie给浏览器,cookie中保存的数据是msg:hello,发送的形式是响应头Set-Cookie:msg=hello
  浏览器收到响应头,根据HTTP协议的响应头约束,将响应头中的数据msg=hello保存到浏览器中
  下一次浏览器发送请求时,将msg=hello放在请求的消息头中Cookie:msg=hello,发送给服务器,然后服务器可以使用getCookies方法获取数据
cookie的细节
  一次可不可以发送多个cookie?可以
    Cookie c1 = new Cookie(“msg”, “hello”);
    Cookie c2 = new Cookie(“name”, “zhangsan”);
    response.addCookie(c1);
    response.addCookie(c2);
    响应头SetCookie:mag=hello SetCookie:name=zhangsan
    请求头Cookie:msg=hello;msg=zhangsan
  cookie在浏览器中保存多久?
    默认情况下,当浏览器关闭后,Cookie数据被销毁
    可以设置Cookie的生命周期,进行持久化存储
      cookie.setMaxAge(int seconds);
      整数 将cookie数据写到硬盘的文件中,持久化存储,
      seconds表示cookie存活时间为多少秒,到时间文件会自动删除
        负数 默认值 当浏览器关闭后,Cookie数据被销毁
        0 删除cookie信息
  cookie能不能存中文?
    tomcat8之前,cookie不能直接存储中文数据
      需要将中文数据转码,一般使用URL编码(%+2个16进制数字表示一个字节)
    tomcat8之后,cookie支持中文数据
      但不支持特殊字符,如空格([32])。
      需要使用URL编码来存储,URL解码来解析特殊字符
  cookie共享问题?
    假设在一个tomcat服务器中部署了多个web项目,
    这些web项目中的cookie能不能共享?
      默认情况下,cookie不能共享
      cookie.set(String path)可以设置cookie的获取范围。
      默认情况下设置的是当前web项目的虚拟目录,
      如setPath("/day16");则在该虚拟目录下的资源才能获取该cookie
      若设置cookie.setPath("/");则表示当前服务器的根路径下的所有项目都可以
  获取该cookie不同的tomcat服务器下的cookie能否共享?
    可以。
    如tieba.baidu.com和news.baidu.com部署在不同的服务器上
    (因为一个服务器支持不了这么大的用户访问量)
    但它们之间的cookie应该共享,因为它们都归属于百度这个项目
    其中tieba和news属于二级域名,.baidu.com属于一级域名
    使用cookie.setDomain(String path)设置相同的一级域名
    cookie.setDomain(".baidu.com"),
    则tieba.baidu.com和news.baidu.com之间的cookie可以共享
cookie的特点
  cookie存储数据在客户端浏览器
    客户端浏览器的防卫级别或安全级别比较低,数据的安全性比较低,很容易丢失或被篡改
  浏览器对于单个cookie的大小有限制(4kb左右),且对于同一个域名下的总cookie数量也有限制(20个左右),这里指的是持久化存储在硬盘中的cookie
cookie的作用
  (1)cookie一般用于存储少量的不太敏感的数据
  (2)在不登录的情况下,完成服务器对客户端的身份识别,如保存用户的一些个性化设置
    实现:服务器将设置以cookie的形式发送并存到浏览器,下次再访问该网页时浏览器发送
       请求会携带这些cookie,服务器就可以获取这些cookie信息
       如果登录了进行的设置,可以存进数据库
案例 记住上一次的访问时间
需求
  (1)访问一个servlet,如果是第一次访问,则提示:您好,欢迎您首次访问
  (2)如果不是第一次访问,则提示:欢迎回来,您上次的访问时间为:显示时间字符串
分析
  服务器中创建一个servlet
  判断是否有一个名字为lastTime的cookie,
    有则不是第一次
      响应数据:欢迎回来,您上次的访问时间为:显示时间字符串
      写回cookie,更新时间
    没有则是第一次
      响应数据:您好,欢迎您首次访问
      写回cookie
    不管有没有,都需要将Cookie:lastTime=时间 写到客户端保存

@WebServlet("/demo03lasttime")
public class Demo03LastTime extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置响应消息体的content-type
        response.setContentType("text/html;charset=utf-8");
        //获取所有cookie,判断是否有名为lastTime的cookie
        Cookie[] cookies = request.getCookies();
        boolean flag = false;//没有名为lastTime的cookie
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                if ("lastTime".equals(cookie.getName())) {
                    //有该cookie,不是第一次访问
                    flag = true;
                    //1、响应数据 先获取lastTime的值,即上次访问的时间
                    String lastTime = cookie.getValue();
                    //给时间解码
                    System.out.println("解码前的时间:" + lastTime);
                    lastTime = URLDecoder.decode(lastTime, "utf-8");
                    System.out.println("解码后的时间:" + lastTime);
                    response.getWriter().write("<h1>欢迎回来,您上次的访问时间为:" + lastTime + "</h1>");
                    //2、写回cookie到浏览器保存,先获取当前时间的字符串
                    Date date = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
                    String time = sdf.format(date);
                    System.out.println("编码前的时间:" + time);
                    //给时间编码,因为tomcat8不能直接存储特殊字符,如空格
                    time = URLEncoder.encode(time, "utf-8");
                    System.out.println("编码后的时间:" + time);
                    cookie.setValue(time);
                    //设置cookie的存活时间为一个月
                    cookie.setMaxAge(60 * 60 * 24 * 30);
                    response.addCookie(cookie);
                    break;
                }
            }
        }
        if (cookies == null || cookies.length == 0 || flag == false) {
            //没有该cookie,是第一次访问
            //1、响应数据
            response.getWriter().write("<h1>您好,欢迎您首次访问</h1>");
            //2、写回cookie到浏览器保存,先获取当前时间的字符串
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
            String time = sdf.format(date);
            System.out.println("编码前的时间:" + time);
            //给时间编码,因为tomcat8不能直接存储特殊字符,如空格
            time = URLEncoder.encode(time, "utf-8");
            System.out.println("编码后的时间:" + time);
            Cookie cookie = new Cookie("lastTime", time);
            //设置cookie的存活时间为一个月
            cookie.setMaxAge(60 * 60 * 24 * 30);
            response.addCookie(cookie);
        }
    }

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

lastTime.jsp

<%@ page import="java.net.URLDecoder" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="java.net.URLEncoder" %><%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/28
  Time: 10:22
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Home</title>
</head>
<body>
<%
    //设置响应消息的content-type
    //response.setContentType("text/html;charset=utf-8");//上面已经指定了
    //设置flag表示是否有lastTime这个响应头
    boolean flag = false;
    //获取cookies
    Cookie[] cookies = request.getCookies();
    //遍历cookie数组
    if (cookies != null && cookies.length > 0) {
        for (Cookie cookie : cookies) {
            //有该响应头则不是首次访问
            if ("lastTime".equals(cookie.getName())) {
                flag = true;
                //响应数据 先获取cookie的值,即上一次的访问时间
                String lastTime = cookie.getValue();
                //解码时间为UTF-8格式
                URLDecoder.decode(lastTime, "utf-8");
%>
                <h1>欢迎回来,您上次的访问时间为:<%=lastTime%></h1>
<%
                //写回响应头,更新时间 先获取当前时间
                Date date = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
                String time = sdf.format(date);
                //编码时间为URL格式
                time = URLEncoder.encode(time, "utf-8");
                cookie.setValue(time);
                cookie.setMaxAge(60 * 60 * 24 * 30);
                response.addCookie(cookie);
                break;
            }
        }
    }
    //获取到的cookies数组为空或长度为0或不存在lastTime这个响应头
    if (cookies == null || cookies.length == 0 || flag == false) {
%>
        <h1>您好,欢迎您首次访问</h1>
<%
        //写回消息头
        //获取当前时间
        Date date = new Date();
        //格式化时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String time = sdf.format(date);
        time = URLEncoder.encode(time, "utf-8");
        Cookie cookie = new Cookie("lastTime", time);
        cookie.setMaxAge(60 * 60 * 24 * 30);
        response.addCookie(cookie);
    }
%>
</body>
</html>

2、JSP

概念
  Java Server Pages Java服务器端页面
  既可以定义HTML标签,又可以定义Java代码的页面
    在body标签中通过<%%>定义Java代码
  用于简化书写
原理
  浏览器请求index.jsp
  服务器
    (1)解析请求消息,找是否有index.jsp资源
    (2)有,将index.jsp转换为index_jsp.java文件
    (3)编译index_jsp.java文件,生成index_jsp.class字节码文件
    (4)由该字节码文件index_jsp.class提供访问,给浏览器做出响应
  上述的java文件是一个servlet,因为只有servlet才可以被外界访问,则jsp本质上也是一个servlet
    服务器启动时输出信息Using CATALINA_BASE,该目录包含了当前项目的配置信息conf和logs
    访问index.jsp时会生成一个work文件夹,放置生成的index_jsp.java文件和index_jsp.class文件
    每次访问jsp页面都会如果jsp中的内容变化了,就会更新对应的Java文件的内容
      其中的index_jsp.java继承了HttpJspBase,而HttpJspBase继承了HttpServlet
        index_jsp.java中的_jspService方法使用pageContext.getOut().write()将
      index.jsp中的内容输出到页面上
        之前通过定义servlet来做出响应时,输出页面标签的工作需要手工完成,
      而使用jsp,只需要在jsp中定义标签,它转换为java文件时会自动将定义的标
      签输出到页面上
    在tomcat目录下也包含一个work文件夹,放置运行时产生的资源文件
jsp的脚本
  定义Java代码的方式
  (1)<% Java代码 %> 转换为Java文件后Java代码定义在_jspService方法中,则service方法中可以定义什么,该脚本中就可以定义什么
  (2)<%! Java代码 %> 转换为Java文件后Java代码定义在成员位置,则成员位置可以定义什么,该脚本就可以定义什么,如成员变量、成员方法、静态代码块
    但在jsp中或servlet中尽量不要定义成员变量,因为可能会引发线程安全问题
  (3)<%= Java代码 %> 转换为Java文件后Java代码定义在_jspService方法中,定义的Java代码会被输出到页面上,输出语句中可以定义什么,该脚本就可以定义什么
jsp的内置对象
  在jsp页面中不需要获取和创建,可以直接使用的对象
  如out对象和request response对象,其在service方法中已经进行了声明,因此可以直接在jsp的<%%>中使用
  jsp一共有9个内置对象
  out.write()/print()和response.getWriter().write()的区别
    out输出语句定义在什么位置,就会在什么位置输出,但response.getWriter的输出语句不管
  定义在什么位置都会先于out输出
    原理:服务器中的response对象和out对象都有字符输出流,因此都有缓冲区,浏览器请求
  jsp页面时,tomcat做出响应之前会先找response的缓冲区,将其中的数据拼到响应体之后
    建议都使用out进行输出,防止打乱布局

3、Session

概念
  服务器端会话技术
  在一次会话的多次请求之间共享数据,将数据保存在服务器端的对象HttpSession中
快速入门
  HttpSession对象
    Object getAttribute(String name);
    void setAttribute(String name, Object value);
    void removeAttribute(String name);
    (1)获取session对象
      HttpSession session = request.getSession();
    (2)存储数据
      session.setAttribute(“msg”, “hello session”);
    (3)获取数据
      Object msg = session.getAttribute(“msg”);

@WebServlet("/demo01session")
public class Demo01Session extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用session共享数据
        //获取session
        HttpSession session = request.getSession();
        //存储数据
        session.setAttribute("msg", "hello session");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}
@WebServlet("/demo02session")
public class Demo02Session extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取session
        HttpSession session = request.getSession();
        Object msg = session.getAttribute("msg");
        System.out.println(msg);
    }

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

原理
  服务器中的2个servlet分别获取了session,获取的是同一个session
  浏览器先请求第一个servlet,再请求第二个servlet
  服务器怎么确保在一次会话中多次获取的session是同一个?
    session的实现依赖于cookie
    第一次获取session,没有cookie,会在内存中创建一个新的session对象,其拥有
  一个唯一的id=…
    服务器做出响应时会发送一个响应头set-cookie:JSESSIONID=…,也就是给浏览器
  发送了一个id为…的cookie
    浏览器收到该响应头,会将cookie信息存到浏览器内部,下次请求服务器资源时会
  携带该cookie,通过请求头cookie:JSESSIONID=…,服务器根据该id查找内存中是否有
  对应的session对象
细节
  当客户端关闭后,服务器不关闭,2次获取的session是否是同一个?
    默认情况下不是。因为客户端关闭意味着会话结束,再次获取将没有对应的cookie信息
    可以通过
      新建一个cookie,new Cookie(“JSESSIONID”, seesion.getId());
      设置cookie存活时间 cookie.setMaxAge(60 * 60);
      将这个cookie加入到响应中 response.addCookie(cookie);
      则即使关闭浏览器,一小时内获取的session还是同一个
  当客户端不关闭,服务器关闭后,2次获取的session是否是同一个?
    不是同一个。因为服务器关闭,内存被释放,session对象被销毁。
    问题:
      京东上将商品加入购物车,购物车通过Map集合实现,且Map集合定义在
    session对象中。
      如果加入购物车后,京东服务器重启了,导致session对象不是同一个,购
    物车中的商品数据就会丢失
    需要确保不是一个session对象的情况下,数据不丢失
      session的钝化
        在服务器正常关闭之前,将session对象序列化到硬盘上
      session的活化
        在服务器启动后,将session文件反序列化为内存中的session对象
      idea无法完成session的钝化和活化,需要使用本地的tomcat服务器
        (1)将idea项目中的out目录下部署的当前项目复制到桌面,
      将项目内的文件,如WEB-INF jsp文件等压缩为war包(压缩后改后
      缀为.war),如day16.war
        (2)将war包复制到tomcat的webapps文件夹中,然后通过bin文件
      夹中的startup.bat启动本地tomcat,则war包对应的项目会被部署,会
      在webapps文件夹中生成一个war包对应的项目文件夹day16,从而可
      以通过浏览器访问其中的资源,如localhost:8080/day16/demo01session
        (3)tomcat的work文件夹存放程序运行中动态生成的数据,如jsp转换
      的Java文件,session被序列化之后的文件
          正常关闭tomcat(双击shutdown.bat)后,tomcat/work/Catalina/
        localhost/day16文件夹中会生成一个SESSIONS.ser文件,存放序列
        化的session对象
          再次启动tomcat(双击startup.bat)后,该ser文件会被自动读取并
        删除,即将session文件反序列化为session对象,还原到服务器内存中
          虽然序列化前后的session对象的地址值不同,但session对象包含
        的数据和对应的id都是一样的,再次访问资源,仍然可以获取session对
        象存储的数据
      idea可以完成钝化,但无法活化
        idea中启动服务器后,打印信息会显示当前服务器的配置文件所在目录
          Using CATALINA_BASE
        其中也有一个work目录,
        访问资源如localhost:8080/day16/demo01session后将服务器关闭,
        work/Catalina/localhost/day16文件夹中会生成一个SESSIONS.ser文件
        但再次启动服务器会先将work目录删除,再创建一个work目录,
        因此无法自动读取ser文件并进行反序列化
      一般部署项目都是将项目放到tomcat的webapps文件夹下,
      tomcat的work目录不会被自动删除
  session什么时候被销毁?
    服务器关闭
    session对象调用invalidate()方法
    session默认失效时间为30min,如登录一个网站后30min内没有进行任何操作,
  session会失效,被服务器销毁,会提示重新登录
    tomcat/conf/web.xml中可以设置失效时间,也可以在每个项目自己的web.xml中
  设置,进行覆盖
     <Session-config>
        <Session-timeout>30</Session-timeout>
     </Session-config>
特点
  session用于存储一次会话的多次请求的数据,存在服务器端
  重定向是2次请求,2次请求之间共享数据可以使用session或ServletContext,但ServletContext范围太大,一般使用session
  session可以存储任意类型,任意大小的数据
    session和cookie的区别
      (1)session存储数据在服务器,cookie存储数据在客户端
      (2)session存储数据没有大小限制,cookie有
      (3)session数据存储在服务器,相对安全;cookie相对不安全
      (4)session一般存储的数据量比较大
案例-验证码
需求
  (1)访问带有验证码的登录页面login.jsp
  (2)用户输入用户名、密码以及验证码
    如果用户名或密码有误,跳转到登录页面,提示:用户名或密码错误
    如果验证码输入有误,跳转到登录页面,提示:验证码错误
    如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
分析
  login.jsp包含用户名输入框、密码输入框、验证码图片和验证码输入框,以及登录按钮
  点击登录,将输入的数据提交到LoginServlet。
  LoginServlet
    设置request的编码
    获取提交的参数的Map集合
    获取验证码
    将用户信息封装到User对象
    先判断验证码是否正确,再判断用户名和密码是否正确,避免数据库的开销
      判断程序生成的验证码和用户输入的验证码是否一致,其中程序生成的
    验证码图片在一个单独的servlet中,需要单独请求
      请求验证码图片和用户数据的提交是2次请求,需要共享数据
      生成验证码图片后将其存到session中,在LoginServlet中获取session中
    的验证码进行比较
      不一致,提示:验证码错误,并将用户信息存到request中,转发到登录页面
      一致,判断用户名和密码是否正确
        正确 存储数据到session中 重定向到主页success.jsp
        不正确 提示:用户名或密码错误 并将用户信息存到request中,转发到
      登录页面
    登录失败跳转到login.jsp 使用Java代码通过request中存储的数据显示错误信息
login.jsp

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/28
  Time: 20:16
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
    <script>
        window.onload = function () {
            var img = document.getElementById("img");
            img.onclick = function () {
                img.src = "/day16/checkcode?time=" + new Date().getTime();
            };
        };
    </script>
    <style>
        div {
            color: red;
        }
    </style>
</head>
<body>
    <form action="/day16/loginServlet" method="post">
        <table>
            <tr>
                <td>
                    <label for="username">用户名:</label>
                </td>
                <td>
                    <input type="text" placeholder="请输入用户名" name="username" id="username" />
                </td>
            </tr>
            <tr>
                <td>
                    <label for="password">用户名:</label>
                </td>
                <td>
                    <input type="password" placeholder="请输入密码" name="password" id="password" />
                </td>
            </tr>
            <tr>
                <td>
                    <label for="checkCode">验证码:</label>
                </td>
                <td>
                    <input type="text" name="checkCode" id="checkCode" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <img src="/day16/checkcode" id="img" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" value="登录">
                </td>
            </tr>
        </table>
    </form>
    <div>
        <%=request.getAttribute("cc_error") == null ? "" : request.getAttribute("cc_error")%>
    </div>
    <div>
        <%=request.getAttribute("login_error") == null ? "" : request.getAttribute("login_error")%>
    </div>
</body>
</html>

LoginServlet

package servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置request编码
        request.setCharacterEncoding("utf-8");
        //获取提交的参数Map集合
        Map<String, String[]> map = request.getParameterMap();
        //获取参数
        String[] usernames = map.get("username");
        String[] passwords = map.get("password");
        String[] checkCodes = map.get("checkCode");
        //判断验证码是否正确
        //获取CheckCodeServlet中生成的验证码
        String checkCode = (String) request.getSession().getAttribute("checkCode");
        //删除session中存储的验证码,防止登录成功后后退网页验证码不变
        request.getSession().removeAttribute("checkCode");
        if (checkCode != null && checkCode.equalsIgnoreCase(checkCodes[0])) {
            //验证码正确,验证用户名和密码是否正确
            //这里需要调用UserDao查询数据库
            if ("zhangsan".equals(usernames[0]) && "123".equals(passwords[0])) {
                //用户名和密码正确
                //将用户信息存进session 因为重定向是2次请求
                request.getSession().setAttribute("username", usernames[0]);
                //重定向到success.jsp
                response.sendRedirect(request.getContextPath() + "/success.jsp");
            } else {
                //用户名或密码错误
                //存储提示信息到request,并转发到login.jsp
                request.setAttribute("login_error", "用户名或密码错误");
                request.getRequestDispatcher("/login.jsp").forward(request, response);
            }
        } else {
            //验证码错误,将提示信息存进request,并转发到login.jsp
            request.setAttribute("cc_error", "验证码错误");
            //转发不需要虚拟目录
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

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

CheckCodeServlet

package servlet;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

@WebServlet("/checkcode")
public class CheckCodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建一个对象,在内存中代表图片(验证码图片对象)
        int width = 100;
        int height = 50;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        //美化图片
        //填充背景色
        Graphics g = image.getGraphics();//获取画笔对象
        g.setColor(Color.pink);//设置画笔颜色
        g.fillRect(0, 0, width, height);
        //画边框
        g.setColor(Color.blue);
        g.drawRect(0, 0, width - 1, height - 1);//边框有一个像素的宽度
        //写验证码
        String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random r = new Random();//随机角标
        //存储验证码到session中共享
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 4; i++) {
            int index = r.nextInt(str.length());//随机生成字符串中的索引
            char c = str.charAt(index);//随机字符
            sb.append(c);
            g.drawString(c + "", width / 5 * (i + 1), height / 2);
        }
        String checkCode = sb.toString();
        request.getSession().setAttribute("checkCode", checkCode);
        //画干扰线
        g.setColor(Color.green);
        for (int i = 0; i < 10; i++) {
            int x1 = r.nextInt(width);
            int x2 = r.nextInt(width);
            int y1 = r.nextInt(height);
            int y2 = r.nextInt(height);
            g.drawLine(x1, x2, y1, y2);
        }
        //将图片输出到页面展示
        ImageIO.write(image, "jpg", response.getOutputStream());
    }

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

success.jsp

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/28
  Time: 21:05
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>主页</title>
</head>
<body>
    <h1><%=request.getSession().getAttribute("username")%>,欢迎您</h1>
</body>
</html>

4、JSP指令

作用
  用于配置JSP页面,导入资源文件
格式
  <%@ 指令名称 属性名1=属性值1 属性名2=属性值2 … %>
  如<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>
分类
  page
    配置JSP页面的
  contentType 等同于response.setContentType()
    设置响应消息体的MIME类型和字符集
    设置当前JSP页面的编码(使用高级开发工具如idea时,charset改成什么,
  当前页面编码就会自动变为什么。如果不是高级开发工具,可以使用pageEncoding
  来设置当前页面的编码)
  language
    一般是java
  buffer
    缓冲区大小,默认8kb 页面中的数据一般使用out对象来输出,即out对象的缓冲区
  大小,out对象相当于一个字符输出流
  import
    导入Java代码需要的包 将来转换为Java文件时也会变为import语句
    如<%@ page import=“java.util.List” %>
  errorPage
    错误页面
    即当前页面发生异常后,会跳到指定的错误页面 使得用户看到的是我们定义好的页
  面,如服务器正忙。。。而不是具有错误信息的页面
  isErrorPage
    表示当前页面是否为错误页面
    默认为false,标识为true后可以使用内置对象exception的方法getMessage()来获取
  异常信息,将其输出到日志文件中
  include
    用来包含页面的,当多个页面的部分内容一样
    如<%@include file=“top.jsp”%>
  taglib
    导入资源,一般用来导入标签库,如JSTL
    如<%@ taglib prefix=“c” uri=“http://java.sun.com/jsp/jstl/core” %>
    prefix 前缀 如prefix=c 使用标签时就是<c:***>

5、JSP注释

html注释
  只能注释html代码片段 会将注释的代码放到response响应体中,显示在页面源代码里,但会告诉浏览器不要解析

  <!-- -->

jsp注释 推荐使用
  可以注释所有代码 不会将注释的代码放到response响应体中,页面源码不会显示,浏览器看不到注释的代码

  <%-- --%>

6、JSP内置对象

在jsp页面中不需要创建,直接使用的对象。因为其已经在转换后的Java文件的service方法中进行了声明
转换后的Java文件在CATALINA_BASE\work\Catalina\localhost\day16\org\apache\jsp
一共9个
  域对象 用来共享数据
    pageContext
    真实类型 PageContext
    作用 当前页面共享数据
    获取其它8个内置对象 get***()方法 如getException() getPage()
  request
    HttpServletRequest
    一次请求访问的多个资源之间共享数据(转发)
  session
    HttpSession
    一次会话的多个请求之间共享数据
  application
    ServletContext
    所有用户之间共享数据,唯一的对象,服务器开启被创建,服务器关闭被销毁
  响应对象
    response
    HttpServletResponse
  当前页面(servlet)对象,即this
    page
      Object
  输出对象 将数据输出到页面上
    out
      JspWriter
  配置对象 servlet的配置对象
    config
      ServletConfig
  异常对象 需要将isErrorPage设置为true
    exception
      Throwable(Exception的父类)

7、MVC开发模式

jsp演变历史
  早期只有servlet,没有jsp。要给客户端响应动态页面(包括静态的html标签数据,动态的程序获取的数据),只能使用response输出数据,很麻烦。
  后来有了jsp,简化了servlet的开发。既可以写标签,也可以写Java代码。而且jsp页面不需要编译,直接就可以运行,也不需要重新部署,重启服务器。
  但一旦项目比较大,代码可读性很差,很难维护,也很难分工。
  再后来,Java的web开发,借鉴了mvc开发模式,规定代码的书写位置,使得程序的设计更加合理。
mvc
  model 模型
    进行业务操作 如查询数据库,封装对象
    JavaBean
  view 视图
    展示数据
    JSP
  controller 控制器
    (1)获取客户端输入
    (2)调用模型进行业务操作获取数据
    (3)将数据交给视图展示 这里使用域对象共享数据
  Servlet
    浏览器请求资源,先经过控制器,控制器调用模型进行业务操作(如查询数据库,
  封装对象),操作完模型将数据返回给控制器,然后控制器将数据给视图来展示,从而
  给浏览器响应。
优点
  耦合性低,方便维护,利于分工协作 代码分为三部分,各司其职
  重用性高 如控制器调用模型获取了数据之后,可以将一部分数据交给JSP展示,一部分数据交给移动端展示。这里就可以复用模型和控制器
  生命周期成本低
  部署快
缺点
  使得项目架构变得复杂,对开发人员要求高
JSP页面最好只用于展示,不写Java代码。一定要写Java代码可以使用EL表达式和JSTL标签

8、EL表达式

概念
  Expression Language 表达式语言
作用
  替换和简化jsp页面中Java代码的编写
语法
  ${表达式}
JSP默认支持EL表达式,要使得EL表达式不被解析,可以
  在JSP的page配置中设置isELIgnored=“true”,表示忽略当前jsp页面中所有EL表达式
  直接在EL表达式的$前加\ 即${表达式} 表示忽略该EL表达式
使用
运算
  算术运算符 + - * /(div) %(mod)
  比较运算符 > < >= <= == !=
  逻辑运算符 &&(and) ||(or) !(not)
  空运算符 empty
    用于判断字符串、集合、数组对象是否为null以及长度是否为0
    ${empty list} list对象为null或长度为0才会返回true
    ${not empty list} list对象不为null且长度>0

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/30
  Time: 14:44
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>EL表达式</title>
</head>
<body>
    <%--在页面上输出false--%>
    $(3 > 4)
    <%--算术运算符--%>
    $(3 + 4)<br>
    $(3 / 4)<br>
    $(3 div 4)<br>
    $(3 % 4)<br>
    $(3 mod 4)<br>
    <%--比较运算符--%>
    $(3 == 4)<br> <%--false--%>
    <%--逻辑运算符--%>
    $(3 > 4 && 3 < 4)<br> <%--false--%>
    $(3 > 4 and 3 < 4)<br> <%--false--%>
</body>
</html>

获取值
  EL表达式只能从域对象中获取值
  ${域名称.键名} 从指定域中获取指定键的值
  域名称
    pageScope 从pageContext域中获取值
    requestScope 从request域中获取值
    sessionScope 从session域中获取值
    applicationScope 从application域中获取值
  举例
    在request域中存了name=张三
      ${requestScope.name}
    没有获取到值会显示空字符串,不会打乱页面布局
      ${键名}
    表示依次从最小的域中查找是否有指定键对应的值,直到找到为止
      page<request<session<application

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/30
  Time: 15:15
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>EL表达式获取域中的值</title>
</head>
<body>
    <%
        //在域中存储值
        request.setAttribute("name", "张三");
        session.setAttribute("age", "22");
    %>
    ${requestScope.name} <%--页面上输出张三--%>
    ${sessionScope.age} <%--页面上输出22--%>
</body>
</html>

获取对象、List集合、Map集合的值
对象
  ${域名称.键名.属性名} 本质上是先获取键名对应的对象,
  然后调用对象的get方法来获取属性值
  举例
    User user = new User();
    user.setName(“张三”);
    user.setAge(22);
    user.setBirthday(new Date());
    request.setAttribute(“u”, user);
    直接${requestScope.u}获取的是user的地址值
    ${requestScope.u.name}可以获取对象的属性值(对象的属性指的是对应的类中的get
  和set方法名去掉get和set,再变为小写 即getName->Name->name)
      原理:先在request域中找是否有u这个键对应的值,
         有的话再调用getName方法
  获取name这个属性的值,没有getName方法会报错
    ${requestScope.u.birthday.year}获取year
      原理:获取了birthday这个属性的值后,
         调用getYear方法获取year这个属性对应的值
      如果要格式化日期对象,可以在User类中添加一个方法getBirStr()
        public String getBirStr() {
          if(birthday != null) {
            //格式化日期
            SimpleDateFormat sdf =
            new SimpleDateFormat(“yyyy年MM月dd日 HH:mm:ss”);
            birthday = sdf.format(birthday);
            //返回字符串
            return birthday;
          } else {
            return “”;
          }
        }
        然后${u.birStr}就可以获取格式化之后的生日
        上面的getBirStr方法被称为逻辑视图,没有对应的成员变量,只是为了在
      页面上更好地展示数据而提供的方法
        因此要获取对象指定的属性值***,需要这个对象对应的类中有get***这个
      方法,否则会报错
List集合
  ${域名称.键名[索引]}
  举例
    List list = new List();
    list.add(“aaa”);
    list.add(“bbb”);
    List.add(user);
    request.setAttribute(“list”, list);
    $(requestScope.list) 输出{aaa,bbb}
    $(requestScope.list[0]) 输出aaa
    索引越界,输出的是空字符串
    $(requestScope.list[2].name) 输出张三
Map集合
  $(域名称.键名.key名)
  $(域名称.键名["key名"])
  举例
    Map map = new HashMap();
    map.put(“sname”, “李四”);
    map.put(“gender”, “男”);
    map.put(“user”, user);
    $(requestScope.map.gender) 输出男
    $(requestScope.map["gender"]) 输出男
    $(requestScope.map.user.name) 输出张三
    $(requestScope.map["user"].name) 输出张三
隐式对象
  EL表达式中不用创建,可以直接使用的对象
  EL表达式中一共有11个隐式对象 pageScope requestScope sessionScope applicationScope
  pageContext 是jsp中的内置对象,也是EL中的隐式对象
    获取jsp其它8个内置对象
    $(pageContext.***)$(pageContext.request)
             $(pageContext.request.contextPath)在jsp页面动态获取虚拟目录

9、JSTL标签

概念
  JavaServer Pages Tag Library JSP标准标签库
  由apache组织提供的开源的免费的jsp标签
作用
  用于简化和替换jsp页面上的Java代码
使用步骤
  导入jstl相关jar包
  引入标签库
    taglib指令 <%@ taglib prefix="" uri="" %>
    prefix 标签前缀
    uri
      http://java.sun.com/jsp/jstl/core 高版本 一般用这个
      http://java.sun.com/jstl/core
  使用标签
常用标签
if if语句
  <c:if test=""></c:if>
  test是必需的属性,接收boolean表达式,一般结合EL表达式一起使用
    表达式为true,显示if标签体内容
    表达式为false,不显示if标签体内容
  没有else情况,需要再定义一个if标签

<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %><%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/30
  Time: 18:55
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>jstl的if标签</title>
</head>
<body>
    <%
        //判断request域中的一个List集合是否为空,如果不为空则显示遍历的集合
        List list = new ArrayList();
        request.setAttribute("list", list);
        list.add("aaa");
    %>
    <c:if test="${not empty requestScope.list}">
        <c:forEach begin="1" end="${list.size()}" var="i" step="1" varStatus="s">
            ${s.index} ${s.count} ${i} <%--1 1 1--%>
        </c:forEach>
        <c:forEach items="${list}" var="i" varStatus="s">
            ${s.index} ${s.count} ${i} <%--0 1 aaa--%>
        </c:forEach>
    </c:if>
</body>
</html>

choose switch语句
  完成数字编号对应星期几的案例
  (1)域中存储1个数字
  (2)使用choose标签取出数字 相当于switch
  (3)使用when标签做数字判断 相当于case
  (4)otherwise标签做其它情况的声明 相当于default
    <c:choose>
      <c:when test="${number == 1}">星期一</c:when>
      <c:when test="${number == 2}">星期二</c:when>
      <c:when test="${number == 3}">星期三</c:when>
      <c:when test="${number == 4}">星期四</c:when>
      <c:when test="${number == 5}">星期五</c:when>
      <c:when test="${number == 6}">星期六</c:when>
      <c:when test="${number == 7}">星期日</c:when>
      <c:otherwise>数字不在范围内</c:otherwise>
    </c:choose>

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/30
  Time: 19:20
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>jstl的choose标签</title>
</head>
<body>
    <%--完成数字编号对应星期几的案例
            1、域中存储1个数字
            2、使用choose标签取出数字 相当于switch
            3、使用when标签做数字判断 相当于case
            4、otherwise标签做其它情况的声明 相当于default
    --%>
    <%
        request.setAttribute("number", 3);
    %>
    <c:choose>
        <c:when test="${number == 1}">星期一</c:when>
        <c:when test="${number == 2}">星期二</c:when>
        <c:when test="${number == 3}">星期三</c:when>
        <c:when test="${number == 4}">星期四</c:when>
        <c:when test="${number == 5}">星期五</c:when>
        <c:when test="${number == 6}">星期六</c:when>
        <c:when test="${number == 7}">星期日</c:when>
        <c:otherwise>数字不在范围内</c:otherwise>
    </c:choose>
</body>
</html>

foreach for循环
  完成重复的操作 普通for循环
    属性
      begin 开始值
      end 结束值
      var 临时变量
      step 步长
      varStatus 循环状态对象
      index 容器中元素的索引 从0开始 在普通for循环中表示每个元素的值
      count 第几次循环 从1开始
  遍历容器 增强for循环
    for(i : list) {

}
    items 容器对象 相当于list
    var 容器中元素的临时变量 相当于i
    varStatus 循环状态对象
    index 容器中元素的索引 从0开始 在普通for循环中表示每个元素的值
    count 第几次循环 从1开始

<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %><%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/30
  Time: 19:29
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>jstl的foreach标签</title>
</head>
<body>
    <%--普通for循环--%>
    <c:forEach begin="1" end="10" var="i" step="1" varStatus="s">
        ${i}
        ${s.index}
        ${s.count}
    </c:forEach>
    <%--增强for循环--%>
    <%
        List list = new ArrayList();
        list.add("aaa");
        list.add("bbb");
    %>
    <c:forEach items="${list}" var="i" varStatus="s">
        ${s.index}
        ${s.count}
        ${i}
        <%--1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10--%>
    </c:forEach>
</body>
</html>

练习
  需求
    在request域中有一个存有User对象的List集合,需要使用jstl+el将list集合中的数据
  展示到jsp页面的表格中

<%@ page import="practice.User" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %><%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/12/30
  Time: 19:59
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>jstl+el练习</title>
</head>
<body>
    <%
        List list = new ArrayList();
        list.add(new User("张三", 18, new Date()));
        list.add(new User("李四", 22, new Date()));
        list.add(new User("王五", 23, new Date()));
        request.setAttribute("list", list);
    %>
    <table border="1" cellpadding="0" cellspacing="0" width="500" align="center">
        <tr>
            <th>编号</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>生日</th>
        </tr>
        <c:forEach items="${list}" var="user" varStatus="s">
            <c:if test="${s.count % 2 != 0}">
                <%--奇数行为蓝--%>
                <tr bgcolor="#add8e6" align="center">
                    <td>${s.count}</td>
                    <td>${user.name}</td>
                    <td>${user.age}</td>
                    <td>${user.birStr}</td>
                </tr>
            </c:if>
            <c:if test="${s.count % 2 == 0}">
                <%--偶数行为粉--%>
                <tr bgcolor="#ffb6c1" align="center">
                    <td>${s.count}</td>
                    <td>${user.name}</td>
                    <td>${user.age}</td>
                    <td>${user.birStr}</td>
                </tr>
            </c:if>
        </c:forEach>
    </table>
</body>
</html>

10、三层架构

软件设计架构 在服务器中部署的web项目分为3层
  界面层(表示层/web层) 用户看得到的界面,用户可以通过界面上的组件和服务器交互
    接收用户参数,封装数据,调用业务逻辑层完成处理,转发jsp页面完成显示。
    包名:公司名称.项目名称.web 如cn.itcast.项目名.web 放servlet等
  业务逻辑层(service层) 处理业务逻辑,即功能的
    组合数据访问层中的简单方法,形成复杂的功能(业务逻辑操作)。
    如cn.itcast.项目名.service
  数据访问层(dao层 data access object) 操作数据存储文件的
    定义了对于数据库最基本的CRUD操作。
    如cn.itcast.项目名.dao
原理
  浏览器给服务器发送请求,服务器操作数据库,数据库返回数据给服务器,服务器将数据响应给浏览器,展示在页面上
  浏览器访问界面层,界面层调用业务逻辑层,业务逻辑层访问数据访问层,数据访问层操作数据库,数据库返回数据给数据访问层,数据访问层将数据封装返回给业务逻辑层,业务逻辑层将数据返回给界面层,界面层将数据展示在浏览器中
  mvc中,浏览器访问界面层中的控制器servlet,servlet接收用户请求,获取用户提交的参数,封装数据,然后调用业务逻辑层,数据访问层,数据库。最后业务逻辑层将数据返回给servlet,servlet存储数据,转发到视图jsp页面,由jsp页面给浏览器做出响应。
注意
  数据访问层中定义了对于数据库最基本的CRUD操作,即增删改查等方法。这些方法可以在业务逻辑层中组合成比较复杂的功能(业务逻辑操作),大大增强了数据访问层中简单方法的复用性。
  框架就是对三层结构的封装和简化
    界面层对应SpringMVC(web层框架),简化servlet的编写
    数据访问层对应MyBatis(持久层框架),简化对数据库的访问操作
    业务逻辑层对应Spring,其也操作界面层和数据访问层,是JavaEE开发的灵魂框架

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值