cookie&session(下)

271 阅读22分钟

第三章 Session技术

我们学习完cookie技术之后,我们发现cookie的弊端是数据的不安全性。而在开发中对于存储的数据安全性有极高要求的则不能使用cookie技术来存储。例如登录网站之后,用户的用户名和密码就不能存储在cookie中,而需要存储在另一个会话技术session中。那么我们先了解下session的原理。

session原理图解.png

clip_image030.jpg

1、Session是什么

Session是服务器为每个访问这个服务器的客户端用户创建的一个容器。这个容器中存储的数据能够在多个request之间实现共享。而且,这个容器只属于当前这个用户。

2、Session是怎么标识每一个用户的

表面上,我们说Session容器是属于某一个用户。其实在会话过程中,只有客户端(浏览器)和服务器两个角色。所以,Session容器标识的不是用户,而是当前用户正在使用的浏览器。浏览器与服务器交流是通过给服务器发送请求实现的,浏览器的每次请求中如果都有一个相同的标记用来标记session。服务器就能够识别这个用户了。这个标记叫做JSESSIONID。

3、Session的作用

  1. 为每个访问服务器的用户创建一个存储数据的容器;

  2. 容器中的数据在多个请求之间共享;

  3. 整个会话过程中,所有的servlet都可以共享session中的数据;

  4. session属于域对象,整个会话过程中,可以向其中存储数据,在其他servlet中获取数据。

  5. 使用范围:

    1)request : 2个servlet,使用在转发。一次请求

    2)servletContext:全局。整个web项目,可以读取配置文件中的数据

    3)session:整个会话过程中的所有的servlet之间

    servletContext > session>request

4、Session容器创建和销毁的API

获取session的API详细说明:api要求记住第一个API

4.1request.getSession()

创建session 如果session不存在,创建session,存在,获取当前session
request.getSession();

4.2HttpSession session1 = request.getSession(true);

HttpSession session1 = request.getSession(true);
//两个API效果相同 request.getSession(true); 等于 request.getSession();
创建session 如果session不存在,创建session,存在,获取当前session

4.3request.getSession(false);

request.getSession(false);
如果当前存在session 获取当前session, 不存在,不获取session,返回null

4.4session.invalidate() ;

session.invalidate()使当前session失效,即销毁当前session

4.5补充:String sessionId = session.getId();

String sessionId = session.getId();  获取session的唯一标识id

【参考代码】

@WebServlet("/createInvaliSessionServlet")
public class CreateInvaliSessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建session 如果session不存在,创建session,存在,获取当前session
        HttpSession session = request.getSession();
        //获取session的id
        String sessionId = session.getId();
        System.out.println(sessionId);//E3ED5876FF2F6D355EC6B5DA8893CEC2
        //request.getSession(true); 等于 request.getSession();
        HttpSession session1 = request.getSession(true);
        //获取session的id
        String session1Id = session1.getId();
        System.out.println(session1Id);//E3ED5876FF2F6D355EC6B5DA8893CEC2
​
        //当前session销毁。。
        //这里使用session或者session1销毁都可以,因为他俩都是一样的
        session.invalidate();
        HttpSession session2 = request.getSession(false);//如果当前存在session 获取当前session, 不存在,不获取session
        System.out.println(session2);//null
        if (null== session2){
            System.out.println("session没有被创建");
        }
    }
}

效果图:

image.png

5、Session的其他常用API(了解)

HttpSession接口方法作用
long getCreationTime()表示会话创建的时间,返回long类型。 表示1970-1-1到这个时间之间相差的毫秒数
boolean isNew()判断当前是否是一个新的会话,是的返回true

代码演示如下:

package com.ithea.sh.web;
​
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 javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Timestamp;
@WebServlet("/sessionAPIServlet")
public class SessionAPIServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //得到会话对象
        HttpSession session = request.getSession();
        //表示会话创建的时间,返回long类型。表示1970-1-1到这个时间之间相差的毫秒数
        long creationTime = session.getCreationTime();
        //String s1 = new Date(creationTime).toString();
        //System.out.println("s1 = " + s1);//2017-10-10
        //使用Timestamp将毫秒转换为日期格式
        String s = new Timestamp(creationTime).toLocaleString();//2017-10-10 15:13:44
        System.out.println("会话创建时间:" + s);
​
        //判断当前是否是一个新的会话,是的返回true
        boolean aNew = session.isNew();
        System.out.println("是否是新的会话:"+aNew);
    }
}

6、Session域对象的API(重要)

由于session属于域对象,所以Session其他的常用API包括:往session容器中存储数据,删除数据,获取数据。

方法使用示例说明
void setAttribute(String name,Object value)session.setAttribute("loginUser",user)将一个对象与一个名称关联 之后存储到session中
Object getAttribute( String name)session.getAttribute("loginUser")通过名称获取session 中的数据
void removeAttribute(String name)session.removeAttribute("loginUser")根据指定名称删除 session中的数据

【练习】Session常用方法练习

需求:

  1. 在第一个setSessionServlet中向session中存储数据;
  2. 在另外一个getSessionServlet中获取session中存储的值,打印session值;

【代码一】SetSessionServlet:往session中设置值

@WebServlet("/setSessionServlet")
public class SetSessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建session
        HttpSession session = request.getSession();
        System.out.println(session.getId());
        //向session中存储数据
        session.setAttribute("one","test");
    }
}

【代码二】GetSessionServlet:从session中取值

@WebServlet("/getSessionServlet")
public class GetSessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取session
        HttpSession session = request.getSession();
        System.out.println(session.getId());
        //获取session中存储值
        String one = (String) session.getAttribute("one");
        System.out.println(one);
    }
}

效果图:

image.png

7、Session的生命周期(了解即可)

7.1 Session创建

当客户端浏览器第一次访问服务器时,服务器为每个浏览器创建不同的HttpSession对象。在服务器端使用request.getSession()方法来获得HttpSession对象。

当第一次执行 request.getSession()是session对象就被创建了。后续的request.getSession()只能获取已创建的session。

1537160964017.png

7.2 Session销毁

方式一:tomcat服务器非正常关闭(例如电脑电源突然断电)【了解】
方式二:时间超出了session的存活时间

session的默认存活时间是30分钟,在tomcat的全局配置文件web.xml中。(路径:tomcat/config/web.xml)

image.png

我们可以在web.xml中自己设置这个存活时间。我们设置的这个时间会覆盖原来的存活时间。

【注意】

注意:30分钟从什么时候开始计算?
从用户最后一次访问时间开始计算:
Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session"活跃(active)"了一次。

注意: 开发中一般我们将session设置为30分钟,所以没有必要对session的存活时间进行设置。这个知识点了解就好了。

如果想手动配置session的存活时间,可以将session-config这段配置复制到自己项目中的web.xml中。就可以修改session的存活时间了

【web.xml配置】

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
           version="3.1">
    <!--将session的存活时间设置成50-->
    <session-config>
        <session-timeout>50</session-timeout>
    </session-config>
</web-app>

提示:知识点了解即可,不需要掌握。

方式三:立即销毁
HttpSession方法功能描述
invalidate()会话立刻失效,一般用于用户退出,注销

代码演示:

@WebServlet("/sessionAPIServlet")
public class SessionAPIServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //得到会话对象
        HttpSession session = request.getSession();
        String sessionId = session.getId();
        //立即销毁
        session.invalidate();
        System.out.println("sessionId = " + sessionId);
    }
}

每次访问session的唯一标识sessionId都不一样。

image.png

方式四:手动设置过期时间

如何在服务器上查看session的销毁时间?

查看Session的销毁时间:

session中的方法说明
int getMaxInactiveInterval()得到服务器上会话最大的非活动时间间隔,默认是1800秒(30分钟)

【注意】

时间间隔的含义:如果你在这段时间内再次发送请求给服务器,服务器将会重新计时。

手动设置Session的过期时间:

HttpSession的方法功能描述
void setMaxInactiveInterval(int 秒)设置会话最大非活动时间时隔,单位是秒

【演示】设置会话过期的时间为5秒,输出过期的时间,输出会话ID

/**
 * 会话存在的时间
 */
@WebServlet("/demo4")
public class Demo4MaxAgeServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建会话
        HttpSession session = request.getSession();
        //设置过期时间,5秒
        session.setMaxInactiveInterval(5);
        //当前会话存活的时间是
        System.out.println("会话存在的时间:" + session.getMaxInactiveInterval());
        System.out.println("会话ID: " + session.getId());
    }
}

7.3 浏览器关闭后,session持久化方案(重要)

通过上面的学习,我们已经知道在服务器创建session的过程中同时生成一个jsessionid和一个cookie,将jsessionid存储到cookie中,然后响应到浏览器中,当下次访问服务器的时候会将cookie中的jsessionid取出到服务器中找对应的session。那么现在会有2个问题,cookie属于什么级别的呢?如果cookie消失,还可不可以找到对应的session?

答案:首先服务器创建的cookie属于会话级别的cookie,即把浏览器关闭,cookie就会消失,由于jsessionid存储在cookie中,也会随着消失,下次打开浏览器在访问服务器的时候就找不到对应的session了,服务器又会重新创建一个新的session容器。那么如果多次创建session并且多次关闭浏览器就会在服务器端产生多个无用的session,会占服务器的资源,服务器性能降低。

补充:session在服务器中的存活时间默认是30分钟。30分钟之后才可以消失。不是浏览器关闭了就消失。

上述问题的代码演示如下:

@WebServlet("/persistenceServlet")
public class PersistenceServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建session并获取session的id
        String id = request.getSession().getId();
        //输出id
        System.out.println(id);
    }
}

idea控制台结果:

image.png

说明:在浏览器访问上述servlet,然后关闭浏览器,然后再次访问servlet,连续重复多次,发现每次session的id都不一样,说明了浏览器关闭,就找不到之前的session了,重新请求就会重新创建一个新的session。

【解决问题】:

我们使用session持久化方案可以解决 上述问题。我们只需要把会话级别的cookie变为持久化级别的cookie,那么就可以解决了。建议让cookie存活时间也是30分钟,和session存活时间一致。

【实现方案】:

    1. 在Servlet中创建session并获取JSESSIONID;
    2. 创建Cookie对象,将上述从session中获取的JSESSIONID放到cookie中;
    3. 手动设置存放JESSIONID的cookie的存活时间为30分钟;
    4. 将存放JESSIONID的cookie响应给浏览器;

原理图:

1537161282083.png

【参考代码】

@WebServlet("/persistenceServlet")
public class PersistenceServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //进行session持久化操作
        //创建session并获取session的id
        String id = request.getSession().getId();
        /*
            将session的id放到cookie中,注意JSESSIONID是session的id名字
         */
        Cookie cookie = new Cookie("JSESSIONID",id);
        //输出id
        System.out.println("id = " + id);
        //对cookie进行持久化操作 存活30分钟
        cookie.setMaxAge(60*30);
        //cookie响应回浏览器
        response.addCookie(cookie);
    }
}

【运行结果】

image.png

关闭浏览器再次访问:http://localhost:8080/persistenceServlet

image.png

总结:因为cookie被我们持久化了,所以,即使用户再次关闭浏览器,都会一直访问到刚才的session。不会再次创建新的session而消耗内存了。

session持久化很重要,要求大家掌握

8、Session的钝化与活化技术(了解)

目标

了解服务器会话的钝化和活化技术

提问

判断下面的说法是否正确?

  1. 浏览器关闭的时候,服务器端的session就销毁?

    不会。session存在服务器端,和浏览器没有关系。

  2. 服务器关闭的时候,服务端的会话就销毁?

    1)tomcat正常关闭:session会被钝化===》work\Catalina\localhost\项目名

    2)tomcat非正常关闭:session会销毁

概念

在正常的情况下,如果服务器关闭或重启,服务器会将所有session域中数据(对象)以文件的方式保存到服务器的硬盘上,下次服务器重新启动的时候,读取硬盘上文件,把这些对象还原。

回顾

package com.ithea.sh.servletcontext_03;
​
import java.io.Serializable;
​
public class Product implements Serializable {
​
    private String name;
    private Integer price;
​
    public Product(String name, Integer price) {
        this.name = name;
        this.price = price;
    }
​
    public Product() {
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public Integer getPrice() {
        return price;
    }
​
    public void setPrice(Integer price) {
        this.price = price;
    }
​
    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + ''' +
                ", price=" + price +
                '}';
    }
}
​
  1. 序列化: 将内存中对象以文件的方式保存
  2. 反序列化: 读取文件,还原成对象
public class SerializeTest {
    @Test
    public void testWrite( ) throws Exception {
        //对象序列化:对象==》文件保存在本地
        Product product = new Product("宝马",122);
        //1、创建文件输出流
        FileOutputStream outputStream = new FileOutputStream("product.txt");
        //2、创建对象流
        ObjectOutputStream oos = new ObjectOutputStream(outputStream);
        //3、写出文件
        oos.writeObject(product);
        oos.close();
    }
​
​
    /**
     * 测试反序列化
     */
    @Test
    public void testRead( ) throws Exception {
        //1、创建文件输入流
        FileInputStream inputStream = new FileInputStream("product.txt");
        //2、创建对象输入流
        ObjectInputStream ois = new ObjectInputStream(inputStream);
        //3、将product.bin文件转换成 Product对象
        Product product = (Product) ois.readObject();
        System.out.println("product = " + product);
    }
​
}

会话的钝化

钝化:将session中所有的对象写入到服务器硬盘文件上

注:项目要部署在tomcat中才有效果,在idea中没有效果.必须将当前项目打成war包放到安装tomcat时webapps路径下,然后到bin目录下启动tomcat(startup.bat)和关闭tomcat(shutdown.bat),然后就会在如下路径下生成一个session钝化的一个文件:

在work目录下:work\Catalina\localhost\项目名\SESSIONS.ser

会话的活化

活化:将服务器硬盘上文件读取出来,还原成会话对象。

以上过程由tomcat完成,程序员不用理会。要保存的对象必须实现序列化的接口。

步骤

  1. 如果一个会话中的对象要正确的钝化,必须要实现序列化的接口
  2. 向会话域中保存Product对象
  3. 在另一个Servlet中取出Product对象输出看有没有数据
  4. 在tomcat中**==正常关闭服务器==**,观察work目录下是否有序列化的文件
  5. 重启tomcat服务器,观察work目录下变化
  6. 在浏览器没有关闭的情况下再读取会话域中的数据,看能够读取出来

注:项目要部署在tomcat中才有效果,在idea中没有效果.必须将当前项目打成war包放到安装tomcat时webapps路径下,然后到bin目录下启动tomcat(startup.bat)和关闭tomcat(shutdown.bat),然后就会在如下路径下生成一个session钝化的一个文件:

代码演示:

【Session创建】

@WebServlet("/setSession")
public class SetSessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //1、创建session
        HttpSession session = request.getSession();
​
        //2、将对象存储到session中
        Product product = new Product("奥迪", 133);
        session.setAttribute("product", product);
        response.getWriter().print("Session创建成功,sessionId = " + session.getId());
    }
}

【Session获取】

@WebServlet("/getSession")
public class GetSessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、接收请求数据
        HttpSession session = request.getSession();
        //2、处理请求
        Product product = (Product) session.getAttribute("product");
        //System.out.println("从Session中获取到数据:product = " + product);
        //3、响应数据
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print(product.toString()+"----"+session.getId());
    }
}

将项目打成war包:

1.点击idea中的项目结构:在idea的右上角。

image.png

2.进入目录结构按照如下操作:

image.png

3.选择Build工程

image.png

image.png

5.然后按照如下操作:

image.png

6.将上述打好包的工程放到tomcat的安装webapps目录下:

image.png

7.到tomcat的bin目录下去启动tomcat

image-20191209173545805.png

8.在浏览器分别访问setSession和getSession

image.png

image.png

9.正常关闭tomcat:到tomcat的bin目录下去关闭。

image-20191209173925877.png

我们就会在work\Catalina\localhost\day10发下session钝化后的文件:

image.png

10.再次启动tomcat服务器,然后在浏览器中访问setSession和getSession

image.png

image.png

小结

  1. 什么是session的钝化与活化?

    序列化和反序列化

  2. 钝化的对象需要实现哪个接口?

    java.io.Serializable
    

9、Cookie禁用后Session的处理【了解】

目标

  1. Cookie禁用后重定向的处理
  2. Cookie禁用后超连接的处理

禁用Cookie后会话中共享数据导致的问题

  1. 把浏览器的Cookie禁用
  2. 在一个Servlet1中向session中添加一个数据,重定向到Servlet2。
  3. 在另一个Servlet2中的session中读取数据并显示出来
  4. 无法得到原来会话中的信息

禁用cookie

image-20191210095303538.png

image-20191210095413264.png

说明:由于cookie被禁用了,那么浏览器每次访问服务器都会重新创建一个session,并且也不会将sessionid保存到浏览器端了,浏览器中不会有任何关于cookie的信息了。那么请求的时候也不会携带cookie到服务器中了,那么这样会导致每次向服务器发送请求都会生成一个新的session,导致之前的session失效了。那么如果用户禁用浏览器中的cookie,我们应该如何解决上述这种情况呢,保证session不失效,可以一直使用呢?

可以使用两种解决方案:1)重定向重写URL 2)超连接重写URL

说明:

  1. 什么是URL重写:

    通过地址栏将会话ID带回给服务器

  2. 处理机制:

    首先会判断Cookie是否可用(如果cookie被禁用,那么获取不到cookie,是null),如果可用,则URL重写不起作用。如果Cookie不可用,则使用URL重写的功能。

    将URL(访问地址)进行重写,在原来的URL基础上,添加当前会话的ID。

解决方案:

【1】重定向的解决方法
  1. 重定向的解决方法:
方法说明
response.encodeRedirectURL(path)将重定向跳转的地址进行重写,添加一个会话ID在后面。

重写前的URL:

demo3

重写后的URL:

demo3;jsessionid=B28EAE0FBCDEBE675060BDC9E3FF9ABD                   
没有重写URL代码
@WebServlet("/demo2")
public class Demo2SetServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //得到会话(会话域)
        HttpSession session = request.getSession();
        //放会话域中存入数据
        session.setAttribute("product",new Product("手机",888));
        response.sendRedirect("/demo3");
    }
}
@WebServlet("/demo3")
public class Demo3GetServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         response.setContentType("text/html;charset=utf-8");
        //得到会话(会话域)
        HttpSession session = request.getSession();
        Product product = (Product) session.getAttribute("product");
        response.getWriter().print(product);
    }
}

效果:

image-20191210102755768.png

说明:如果没有重写URL,我们发现重定向之后没有获取到商品信息。

重写URL代码
@WebServlet("/demo2")
public class Demo2SetServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //得到会话(会话域)
        HttpSession session = request.getSession();
        String url = "/demo3";
        //放会话域中存入数据
        session.setAttribute("product",new Product("手机",888));
        //使用URL重写
        String demo3 = response.encodeRedirectURL(url);
        System.out.println(demo3);
        //demo3就是重写的URL
        response.sendRedirect(demo3);
    }
}
​
@WebServlet("/demo3")
public class Demo3GetServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //得到会话(会话域)
        HttpSession session = request.getSession();
        Product product = (Product) session.getAttribute("product");
        response.getWriter().print(product);
    }
}

效果:

image-20191210104258615.png

【2】超链接的解决方法
方法说明
response.encodeURL(path)对要跳转到的地址使用URL重写
步骤
  1. 在servlet1中输出一个链接,跳转到servlet2中。
  2. 使用上面的方法对href的地址进行URL重写,再设置href属性
  3. 查看跳转的链接地址
代码
@WebServlet("/demo2")
public class Demo2SetServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //得到会话(会话域)
        HttpSession session = request.getSession();
        String url = "/demo3";
        //放会话域中存入数据
        session.setAttribute("product",new Product("手机",888));
        //使用URL重写
        String demo3 = response.encodeRedirectURL(url);
        System.out.println(demo3);
        //demo3就是重写的URL
//        response.sendRedirect(demo3);
        String encodeURL = response.encodeURL(url);
​
        response.getWriter().print("<a href='" + encodeURL + "'>取出商品</a>");
    }
}
​
@WebServlet("/demo3")
public class Demo3GetServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //得到会话(会话域)
        HttpSession session = request.getSession();
        Product product = (Product) session.getAttribute("product");
        response.getWriter().print(product);
    }
}
两个方法的区别
  1. 共同点:都在URL后面添加了会话ID,实现了URL的重写

    /demo3;jsessionid=9F713440C9DA145D99F4E1C358E402C1

  2. 不同点:

    1. encodeURL方法会修改不正确的URL地址,修改完成以后再添加JSESSIONID
    2. encodeRedirectURL如果路径是错误的则什么都不做

image.png

小结

方法说明
response.encodeRedirectURL(path)重定向中使用
response.encodeURL(path)超链接的使用

10、Session综合案例(重要)

验证码校验案例

10.1 验证码校验

10.1.1【验证码】
验证码是由服务器给浏览器端生成的一张图片,这张图片上有扭曲变形的数字或文字。主要是用来防止恶意注册或者登陆的。
10.1.2【验证码校验流程】

1530238219460.png

10.1.3【登录页面login.html】
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
    <h2>登录页面</h2>
    <form action="/loginCheckCodeServlet" method="post">
        账号<input type="text" name="username"><br>
        密码<input type="password" name="password"><br>
        验证码<input type="text" name="code">
        <!--页面一加载就向checkCodeServlet发送请求-->
        <img src="/checkCodeServlet" alt="验证码" style="cursor: pointer"><br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

【页面效果】:

image.png

10.1.4【生成验证码的servlet】CheckCodeServlet

说明:生成的验证码使用java中的GUI来完成的。了解即可,做案例的时候只需将下面的代码复制到CheckCodeServlet中即可。

/*
    说明:验证码是一张图片,而这个图片中的内容是使用代码生成的。
    分析和步骤:
    1)创建一个可以存放图片的缓冲区BufferedImage作为画布;
    2)通过画布获取到针对这个画布的画笔;
    3)修改画布的背景颜色为白色;
    4)设置画布的边框,画边框的时候需要注意下,如果这里写画布的宽width和高height ,就会超出画布就会看不见,所以width和height 分别-1;
    5)创建一个获取随机数的对象;
    6)给画布上写数据;
    7)给画布上画干扰线;
    8)需要把画布中的内容响应给浏览器;ImageIO.write(bi,"JPG",response.getOutputStream());
 */
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //定义画布的宽和高
        int width=120;
        int height=30;
        //创建一个可以存放图片的缓冲区,作为画布
        //BufferedImage.TYPE_INT_RGB 表示生成图片的类型
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //通过画布获取到针对这个画布的画笔
        Graphics g = bi.getGraphics();
        //修改画布的背景颜色  每次使用画笔的时候都得给画笔指定颜色
        g.setColor(Color.WHITE);
        //填充画布
        g.fillRect(0, 0, width, height);
        //设置画布的边框
        //给画笔指定颜色
        g.setColor(Color.RED);
        //给画布画边框 如果这里写width height 就会超过画布,因为边框也会占一个像素,所以这里宽和高都需要-1
        g.drawRect(0, 0, width-1, height-1);
        //创建一个获取随机数的对象
        Random r = new Random();
        //给画布上画干扰线
        //循环控制画多条线
        for(int i=1;i<=3;i++)
        {
            //设置画笔的颜色
            g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255)));
            //向画布上画干扰线
            //drawLine(x1, y1, x2, y2) 这里四个参数是因为两个点画成一条线
            g.drawLine(r.nextInt(width), r.nextInt(height), r.nextInt(width), r.nextInt(height));
        }
        //定义数据准备向画布中写数据
        String data="abcdefghigklmnpqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ123456789";
        //创建字符串缓冲区
        StringBuilder sb = new StringBuilder();
​
        //循环控制画四个字符
        for (int i = 1; i <=4; i++) {
            //设置画笔的颜色 Color.BLUE这里的颜色固定了,只能是蓝色,我们可以让颜色随机变化
//          g.setColor(Color.BLUE);
            g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255)));
            //设置字体  Font.ITALIC表示斜体
            g.setFont(new Font("宋体", Font.ITALIC, 20));
            //给画布上写内容 20表示从x轴的位置开始书写 25表示y轴位置开始书写
//          g.drawString("哈哈哈哈", 20, 25);
            /*
             * data.charAt()表示根据函数的参数进行查找字符
             * data.length()表示字符串的长度
             * r.nextInt()表示生成随机数,但是随机数的范围在0~data字符串的长度
             */
            String str = data.charAt(r.nextInt(data.length())) + "";
            g.drawString(str, 20*i, 25);
            //将验证码内容拼接到字符串缓冲区中
            sb.append(str);
        }
        //  验证码保存到session中
        request.getSession().setAttribute("checkcode",sb.toString());
        //将生成的验证码图片响应给浏览器
        ImageIO.write(bi,"JPG",response.getOutputStream());
    }
}

注意:上述虽然实现了登录页面加载的时候获取到验证码的图片,但是我们每次点击图片,图片并没有发生改变,我们希望当验证码图片看不清楚的时候可以再次向CheckCodeServlet中发送请求并获取一张新的验证码图片,所以在登录页面给验证码图片添加一个点击的js事件

给验证码图片的img标签添加点击事件。

<img src="/checkCodeServlet" alt="验证码" style="cursor: pointer" onclick="changeCheckCode(this);"><br>

添加完成之后,书写js代码:

<script type="text/javascript">
        //自定义函数向checkCodeServlet发送请求获取验证码图片
        function changeCheckCode(obj)
        {
            alert("锁哥");
            obj.src = "/checkCodeServlet";
        }
 </script>

说明:但是当我们点击验证码图片,函数changeCheckCode被执行了,但是图片没有改变。原因是在浏览器第一次加载图片之后将图片放到浏览器缓存中了,而以后再点击图片发送请求的时候页面不会到changeCheckCode中获取验证码图片,而是从浏览器缓存中获取,因为浏览器会认为你每次点击图片都是原来的请求,而我们希望每次点击图片都是一个新的请求。只有这样浏览器才会将新的请求发送给服务器。而不是从浏览器缓存中获取验证码图片。

做法:我们可以先看下其他网站怎么做的,例如网易的163,网址:www.163.com.访问到注册邮箱的页面,可以使用f12抓包技术来查看。

网易注册网站验证码抓包.png

说明:点击一次验证码,都会抓住一次请求,发现每次后面的参数都不一样,这里使用的是系统时间,因为系统时间一直在变化。这样每次点击图片的时候都是发送新的请求,而不会再从浏览器缓存中获取验证码了。

给js函数中的请求路径添加系统时间,由于是get请求 ,直接使用?拼接参数。

【给验证码添加单击事件】每次点击后重新加载

<script type="text/javascript">
        //自定义函数向checkCodeServlet发送请求获取验证码图片
        function changeCheckCode(obj)
        {
            // alert("锁哥");
            obj.src = "/checkCodeServlet?"+new Date().getTime();//new Date().getTime() 表示将系统时间变为毫秒
        }
 </script>

效果图:点击img标签,验证码会发生变化,不从缓存中获取数据,直接走新的请求。

验证码登录页面首页.png

1537161771334.png 【完整页面】

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
    <h2>登录页面</h2>
    <form action="/loginCheckCodeServlet" method="post">
        账号<input type="text" name="username"><br>
        密码<input type="password" name="password"><br>
        验证码<input type="text" name="code">
        <!--页面一加载就向checkCodeServlet发送请求-->
        <img src="/checkCodeServlet" alt="验证码" style="cursor: pointer" onclick="changeCheckCode(this);"><br>
        <input type="submit" value="登录">
    </form>
    <script type="text/javascript">
        //自定义函数向checkCodeServlet发送请求获取验证码图片
        function changeCheckCode(obj)
        {
            // alert("锁哥");
            obj.src = "/checkCodeServlet?"+new Date().getTime();//new Date().getTime() 表示将系统时间变为毫秒
        }
    </script>
</body>
</html>
10.1.5【校验验证码是否输入正确】
@WebServlet("/loginCheckCodeServlet")
public class CheckServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //处理请求乱码
        request.setCharacterEncoding("utf-8");
        //获取用户提交的用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //获取用户输入的验证码
        String code = request.getParameter("code");
        /*
            从session中获取验证码 ,在生成验证码的servlet中CheckCodeServlet书写这样的代码:
              request.getSession().setAttribute("checkcode",sb.toString());
              表示将验证码已经放到session中了,所以我们要获取出来
         */
        String checkcode = (String) request.getSession().getAttribute("checkcode");
        //判断用户输入的验证码code和随机生成的验证码checkcode是否一致,忽略大小写
        if(checkcode.equalsIgnoreCase(code))
        {
            System.out.println("验证码正确");
        }else
        {
            System.out.println("验证码错误");
        }
    }
}

11、Servlet作用域总结

  • ServletContext域:

    一个WEB应用(项目)对应一个ServletContext,这个对象中保存的数据在整个WEB项目中都有效;

    • 创建:服务器启动的时候;
    • 销毁:服务器关闭或项目移除后;
  • HttpSession:

    一次会话的过程中,服务器给客户端(浏览器)创建一个session。这个对象中保存的数据,一次会话(多次请求)内数据有效;

    • 创建:服务器第一次调用getSession()的时候;

    • 销毁:

      • 服务器不正常关闭;
      • Session过期了:默认存活时间30分钟;
      • 手动调用session的invalidate()方法;
  • HttpServletRequest:

    一次请求创建一个request。这个对象中保存的数据,一次请求(请求链)内数据有效;

    • 创建:客户端向服务器发送一次请求;
    • 销毁:服务器为这次请求做出响应之后,销毁request;

【API操作】操作三个作用域对象的API

  • 存储数据:setAttribute(name,value);

  • 获得数据:getAttribute(name);

  • 删除数据:removeAttribute(name);

    大小:HttpServletRequest<HttpSession<ServletContext

第四章 小结

image.png