JavaWeb---HttpServlet会话管理

javaEE的三层架构:web层、Service层、Dao层。

  • web层
    JSP:获取用户信息,展示数据(利用重定向、请求转发),使用EL表达式展示页面

  • Service层
    具体的业务层(用Servlet)
    调用Dao层

  • Dao层(Data Access Object:数据访问对象)
    查询数据库

Session:一个闭环 Session于Session之间没有关系
在这里插入图片描述

会话管理

程序中的会话:打开浏览器—》访问特定的网站(访问服务器)–》关闭浏览器
将浏览器和服务器之间产生的数据就称之为会话。

URL重写

URL重写是一种会话跟踪技术,它将一个或者多个token添加到URL的查询字符串汇总,每个token通常为 key=value 形式,如:url?key-1=value-1&key-2=value-2&key-3=value-3...&key-n=value-n
URL在某些浏览器上的最大长度为2000字符,因此URL重写仅适合在信息较少的页面间传递。

package com.sweet.github.servlet.urlrepeat;

/**
 * Author:sweet
 * Created:2019/5/25
 *
 * 省市区三级联动,利用URL重写查询
 */
import javax. servlet. ServletException;
import javax. servlet. http. HttpServlet;
import javax. servlet. http. HttpServletRequest;
import javax. servlet. http. HttpServletResponse;
import java. io. IOException;
import java. io. PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java. util . List;
import java. util . Map;
public class TokenServlet extends HttpServlet {

    private Map<String, List<String>> cityMap = new HashMap<>();

    private Map<String, List<String>> countryMap = new HashMap<>();

    @Override
    public void init() throws ServletException {

        List<String> shannxi = new ArrayList<>();
        shannxi . add("西安市");
        shannxi . add("宝鸡市");
        shannxi . add("铜川市");
        shannxi . add("咸阳市");
        cityMap. put("陕西省", shannxi );


        List<String> xian = new ArrayList<>();
        xian. add("临潼区");
        xian. add("灞桥区");
        xian. add("长安区");
        countryMap. put("西安市", xian);


    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
            ServletException, IOException {
        resp. setContentType("text/html; charset=UTF-8");
        PrintWriter writer = resp. getWriter();
        String pro = req. getParameter("pro");

        String city = req. getParameter("city");
        if (pro == null && city == null ) {
            writer. write("没有数据");
        } else {
            if (pro == null ) {
                writer. write("pro 参数不能为空");
            } else {
                if (city == null ) {
                    List<String> cityList = cityMap. get(pro);
                    StringBuilder sb = new StringBuilder();
                    for (String c : cityList) {
                        sb. append("<a href='/token")
                                . append("?")
                                . append("pro="). append(pro)
                                . append("&")
                                . append("city="). append(c)
                                . append(" '>")
                                . append(pro)
                                . append(",")
                                . append(c)
                                . append("</a>")
                                . append("</br>");

                    }
                    writer. write(sb. toString());
                } else {
                    List<String> cityList = cityMap. get(pro);
                    if (cityList. contains(city)) {
                        StringBuilder sb = new StringBuilder();
                        for (String country : countryMap. get(city)) {
                            sb. append("<a href='/token")
                                    . append("?")
                                    . append("pro="). append(pro)
                                    . append("&")
                                    . append("city="). append(city)
                                    . append(" '>")
                                    . append(pro)
                                    . append(",")
                                    . append(city)
                                    . append(",")
                                    . append(country)
                                    . append("</a>")
                                    . append("</br>");

                        }
                        writer. write(sb. toString());
                    } else {
                        writer. write(city + " 不属于 " + pro);
                    }
                }
            }
        }
    }

}

隐藏域

利用隐藏域保持状态类似于URL重写,但是不把值附加到URL上,因此没有长度限制而是反映到HTML表单的隐藏域中,当表单提交时,隐藏域的值也提交到服务器端。
隐藏域,需要通过表单提交时的hidden属性:

<!-- 表单隐藏域使用hidden属性 -->
<input type="text" hidden="hidden">

浏览器端会话技术:Cookie

cookie登录应用场景:
1.打开浏览器—》访问网站—》填写用户登录数据(会话数据)—》校验成功—》首页显示当前用户信息-----》关闭浏览器;当再次打开浏览器,访问同一个网站就会直接显示用户信息,这就是用到了cookie会话技术。
cookie浏览应用场景:
2.访问商品列表—》点击某一个商品—》浏览器关闭—》下一次进来----》商品列表—》记录之前访问过的商品数据(图片)

cookie的使用:
  1. 服务器端创建Cookie,将Cookie数据携带给浏览器public Cookie(String name,String value)
  2. 通过浏览器端将数据存储在缓冲区(请求头中cookie:key=value)
    public void addCookie(Cookie cookie):将cookie写回浏览器,等待下次访问时,将指定cookie添加到响应。多次调用此方法设置一个以上的cookie
  3. public Cookie[] getCookies();浏览器再次访问的时候,服务端就可以获取到cookie数组
public class HelloCookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.设置编码
        resp.setContentType("text/html;charset=utf-8");
        //2.cookie由服务器创建,并携带给浏览器
        Cookie cookie=new Cookie("akey","avalue");

        //3.由服务器写回到浏览器
        resp.addCookie(cookie);

        resp.getWriter().write("cookie已写回");

        //4.获取浏览器中存储的cookie数据
     Cookie[] cookies=   req.getCookies();
     if(cookies!=null){
         for(Cookie cookie1:cookies){
             System.out.println(cookie1);
         }
     }
    }
}

删除Cookies
cookie存活时间为0,就是删除cookie:rmCookie. setMaxAge(0);
注: cookie不适合存储私有数据!!

  • cookie内容只能存储String类的数据
  • 浏览器端存储cookie的个数有限制:300个,每一个站点可以存储20多个cookie,每一个cookie大小不超过4kb
  • 有效期问题:浏览器关闭,cookie就会消失(可以设置最大生存时间(秒为单位,参数为正整数,超过了当前的值,就表示cookie过期了,参数为0,表示直接结束cookie),延长cookie的时间)

服务端会话技术:HttpSession

HttpSession对象是在用户第一次访问网站时自动创建,一个用户有且仅有一个HttpSession,并且不会被其他用户访问到,不同的浏览器对不同的会话有不同的定义:有的浏览器定义新打开一个标签页就是一个新的会话,在谷歌浏览器中打开新标签页也会返回原来的会话。

HttpSessionAPI:

创建HttpSession:HttpSession httpSession=req.getSession()
返回当前HttpSession,如果没有就返回null:HttpSession httpSession=req.getSession(false);
返回当前HttpSession,如果没有就新创建一个:HttpSession httpSession=req.getSession(true);
为Session赋值(session是以key-value的形式存放的,key为String类型,value为Object类型):httpSession.setAttribute("city","xian");

HttpSession 的特点:

不同于于URL重写、 隐藏域或cookie,HttpSession 的值是会存储到内存中的,因此,尽量不要往HttpSession中放入太多对象或大对象。Servlet容器在内存不够用时,会将HttpSession对象保存到磁盘上(二级存储)。
因为session对象是任何类型的对象,因此也可以采用序列化技术将session对象放入文件或数据库中。

HttpSession内部实现会话跟踪的原理:

HttpSession的对象会创建一个唯一的编号,就是我们在浏览器源码页面中看到的jsessionid。当创建一个session时,其内部相当于创建一个cookie,cookie的name就是jsessionid,value就是jsessionid的编号,浏览器每一次发起请求时,servlet容器会将cookie提交给服务器(两种方式:1.通过请求头2.带入到url中),这样服务器就能识别到由哪个用户发起的。
获取JESSIONIDString jsessionId= httpSession.getId();

HttpSession的有效性:

session是存储在服务端的内存中的,因此当有大量用户访问,并且一直要保存会话的话,会对服务器造成很大的负担。
1.在程序中设置过期时间:
如果设置为0,表示永不过期:httpSession.setMaxInactiveInterval(60 * 30);

`2.在web.xml中设置

<session-config>   
 <session-timeout>20</session-timeout> 
 </session-config>

不设置时间时默认时间是30min(在tomcat容器中)

过滤器Filter

Filter可以拦截Request请求对象,可以对客户端浏览器的请求进行一些预处理。
Filter的配置可以通过注解或者部署描述来完成。当一个资源或者某些资源需要被多个Filter所使用到,且它的触发 顺序很重要时,只能通过部署描述来配置。

Filter类API

Filter的实现类必须继承javax.servlet.Filter 接口。

  • Filter的初始化方法public void init(FilterConfig filterConfig) throws ServletException

  • Filter的过滤方法public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
    一个资源可能需要被多个Filter关联,也成为Filter链。这时候FilterChain#doFilter()将会触发Filter链中下一个 Filter。只有在Filter链条中后一个Filter里调用的FilterChain#doFilter(),才会触发处理资源的方法。
    如果在Filter#doFilter()的实现中,没有在结尾处调用FilterChain#doFilter()的方法,那么该Request请求终止,后面的处理就会中断

  • Filter的销毁方法public void destroy();

请求计数器

实现一个过滤器,单独进行请求访问次数的统计

@WebFilter(
        urlPatterns = {"/*"},
        initParams = {
                @WebInitParam(name = "logging_prefix", value = "Request"),
                @WebInitParam(name =
                        "logging_filename", value = "filter.logging")
        }
)
public class LoggingFilter implements Filter {
    
    //1. 指定初始化参数   记录的前缀,记录的文件名
    //2. 每个请求的URL记录达到文件中
    //3. web.xml配置(Filter , 拦截的地址, 初始化参数)
    
    private String prefix = "";
    private String loggingFile;
    private PrintWriter writer;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.prefix = filterConfig.getInitParameter("logging_prefix");
        this.loggingFile = filterConfig.getInitParameter("logging_filename");
        try {
            //获取Web程序的根目录
            String webAppRoot = filterConfig.getServletContext().getRealPath("/");
            this.writer = new PrintWriter(new File(webAppRoot, this.loggingFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        
        System.out.println("Logging Filter init called.");
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Logging Filter doFilter called.");
        String url = ((HttpServletRequest) request).getRequestURI();
        this.writer.println(this.prefix + " " + LocalDateTime.now() + " " + url);
        this.writer.flush();
        //request  -> filter (chain.doFilter)  -> Servlet
	
        chain.doFilter(request, response);
    }
    
    @Override
    public void destroy() {
        System.out.println("Logging filter destroy called.");
    }
    
    
}

Filter顺序

如果多个Filter应用同一个资源,Filter的触发顺序有限制,那么就需要通过web.xml中进行部署配置,写在前面的Filter就会先执行。

监听器Listeners

JavaWeb程序构成:
在这里插入图片描述

监听器接口可以分为三类: ServletContext, HttpSession, ServletRequest
实现监听器接口后通过覆写sessionCreatedsessionDestroyed方法,来对相应的监听级别进行监听逻辑处理。

1.应用级监听

  • ServletContextListener接口能对ServletContext的创建和销毁做出响应。
  • ServletContextAttributeListener接口会响应ServletContext范围的属性被添加,删除或者替换。
    应用场景:全局数据共享、程序的启动,销毁时的准备或清理工作、监听属性的变化

2.会话级监听

  • HttpSessionListenern能够监听HttpSession的创建和销毁。
  • HttpSessionAttributeListener和ServletContextAttributeListener类似,它会响应HttpSession范围的属性的 添加,删除,替换。
    应用场景:浏览器会话(客户端)用户相关

3.请求级监听

  • ServletRequestListener监听器会对ServletRequest的创建和销毁事件进行响应。容器会通过一个池子来存放 并复用多个ServletRequest,ServletRequest的创建时从容器池里被分配出来的时刻开始,而它的销毁时刻 是放回容器池里的时间。
  • ServletRequestAttributeListener会响应ServletRequest范围的属性被添加,删除,或者替换。
    应用场景:计算请求耗时、监听请求属性的变化、监听请求公共属性
@WebListener
public class SessionCountListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {
    
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        ServletContext context = se.getSession().getServletContext();
        //当应用程序建立一个会话(session),session_count +1
        Integer sessionCount = Integer.parseInt(String.valueOf(context.getAttribute("session_count")));
        sessionCount++;
        
        context.setAttribute("session_count", sessionCount);
        System.out.println("HttpSession 创建啦");
    }
    
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        ServletContext context = se.getSession().getServletContext();
        //当应用程序关闭一个会话(session),session_count -1
        Integer sessionCount = Integer.parseInt(String.valueOf(context.getAttribute("session_count")));
        sessionCount--;
        
        context.setAttribute("session_count", sessionCount);
        System.out.println("HttpSession 销毁啦");
    }
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //应用程序启动的时候,默认的session数量是0
        ServletContext context = sce.getServletContext();
        context.setAttribute("session_count", 0);
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //应用程序停止的时候,移除session_count属性
        ServletContext context = sce.getServletContext();
        context.removeAttribute("session_count");
    }
    
    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        System.out.println("应用程序会话的属性新增:" + event.getName() + " = " + event.getValue());
    }
    
    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        System.out.println("应用程序会话的属性移除:" + event.getName() + " = " + event.getValue());
    }
    
    @Override
    public void attributeReplaced(HttpSessionBindingEvent event) {
        System.out.println("应用程序会话的属性替换:" + event.getName() + " = " + event.getValue());
    }
}

注解描述

要求ServletAPI标准为3.1才支持注解!!

  • WebServlet:标识Servlet类
  • WebFilter:标识Filter类
  • WebListener:标识Listener类
  • WebInitParam:标识初始化参数
  • MulitpartConfig:标识上传附件的配置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值