Servlet详解

目录

​编辑

初解Servlet

Servlet的生命周期(非常重要)

GenricServlet(很重要)

ServletConfig(很重要)

ServletContext(很重要)

HttpServlet

HTTP协议(必须会)

HTTP的请求协议(B--->S)

HTTP的响应协议(S------>B)

 GET和POST请求的区别

 先知:

区别:

什么时候用GET什么时候用POST?

HttpServlet

先知

HttpServlet源码剖析

ServletRequest(请求域)和ServletContext(应用域)

  HttpServletRequest接口详解 

先知:

HttpServletRequest的四个常用方法

getParameterMap()

getParameterNames()

getParameterValues(String name)

getParameter(String name) 

 HttpServlet其他常用方法

请求域(Request)

 getRemoteAddr()获取客户端的IP地址

注意:请求域中有两个方法非常容易搞混

设置请求域编码

获取应用的根路径getRequestURI()

获取请求方式getMethod()

获取请求的URI:getRequestURI()

获取servlet path

怎么让服务器启动的时候就实例化servlet对象呢?

自己手动实例化的servlet对象和服务器创建的servlet对象一样吗?


初解Servlet

  1. Servlet 是 JavaEE规范之一。规范就是接口,他有五个方法(init、service、destroy、getServletInfo、getServletConfig),这五个方法都是实例方法,就是说你只能通过实例化Servlet接口才能去使用这五个方法。
  2. Servet就 JavaWeb 三大组件之一。三大组件分别是: Servlet 程序、Filter 过滤器、Listener监听器.
  3. Servlet 是运行在服务器上的一个 java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端
  4. 网站中所有的Servlet接口实现类的实例对象,只能由服务器负责创建。

Servlet的生命周期(非常重要)

以一个servlet的实现类为例:AServlet(已部署到服务器)

我们从(启动服务器-AServlet资源被请求-服务器关闭)来推演一个Servlet的生命周期:

  1. 启动Tomcat服务器,创建一个ServletContext对象(用来存放整个项目的公用配置信息),解析AServlet对应项目中的web.xml文档的公用配置信息。
  2. 用户发送第一次请求AServlet,Tomcat服务器调用AServlet的无参构造实器例化一个AServlet对象。紧接着创建一个ServletConfig(翻译为服务配置对象)对象,该对象从web.xml文档中获取了AServlet的配置信息,然后调用AServlet中的init方法(ServletConfig对象作为实参传入),再接着调用AServlet接口中的service方法。
  3. 之后用户请求一次AServlet,服务器就调用一次service方法。
  4. Tomcat服务器关闭,服务器调用destroy方法,接着释放AServlet对象内存,由此AServlet对象被销毁。
  5. 总结:一个Servlet对象的生命周期:启动服务器,服务器创建一个ServletContext对象,服务器解析整个项目的web.xml文档并把公共配置信息保存到ServletContext对象中 ->调用无参构造器实例化AServlet对象->创建ServletConfig对象,该对象从Web.xml中获取AServlet对象的配置信息->调用init方法初始化(传入一个实参就是前面创建好的ServletConfig对象)->调用service方法服务->调用destroy方法做销毁前工作->服务器关闭实例化对象被释放。

引出点:

 思考:

为什么Servlet对象称为假的单例?

首先我们了解一下单例模式的概念:单例模式满足两个条件,其一是只能被实例化一次,其二是单例模式只能通过暴露的方法去实例化对象,他不能直接去new,因为单例模式的所以构造方法都是私有的。那么Servlet只满足其一。

如果在Servlet对象中创建一个有参构造器会怎么样?

首先创建有参构造器会把默认隐藏的无参构造器覆盖掉,那么此时因为我们创建的Servlet实现类是由服务器来实例化的,服务器根本无法给你传参那么也就是说无法实例化,如果你一定要写一个有参构造器,那么必须把无参构造器显示的写在Servlet类中(不建议)。

init方法用来初始化,那么为什么不直接在无参构造器初始化?

首先从语法层面来看,这个Servlet规范就已经定义好了人家init方法不是一个静态的方法他必须用实例化后的对象去调用。再从Servlet规范的开发者本意来看,服务器在实例化Servlet对象时无法传参,也就是说他只能调用无参构造器,那么无参构造器就具有一定的局限性了,此时你可能又会说那么同时创建无参和有参构造器不就好了?NO,有参构造器的作用是实例化对象和初始化,那还是需要得服务器才能实例化啊,你这个参数还不是一样没办法传,况且这样做也很多此一举啊,那还不如直接规范一个init方法呢。

GenricServlet(很重要)

作用:这是一个抽象类,作用是为了解决Servlet对象直接实现Servlet接口的弊端。

Generic翻译为标准的

ServletConfig(很重要)

作用:这也是一个Servlet规范接口,翻译为服务配置对象,每一个Servlet实例对象都对应着一个ServletConfig对象,他里面封装了获取web.xml文档配置信息的一系列方法,为什么说他和Servlet对象是一对一的关系呢?你想啊,服务器实例化一个Servlet对象后下一步做的步骤就是去实例化一个ServletConfig对象并且会读取前面配置的web.xml文档相关的信息那每一个Servlet对象的Web.xml信息肯定都是不一样的啊,所以是他是一对一的关系。

学习Servlet规范接口需要紧扣住Servlet对象的生命周期,

一个Servlet对象的生命周期:启动服务器,服务器创建一个ServletContext对象,服务器解析整个项目的web.xml文档并把公共配置信息保存到ServletContext对象中 ->调用无参构造器实例化AServlet对象->创建ServletConfig对象,该对象从Web.xml中获取AServlet对象的配置信息->调用init方法初始化(传入一个实参就是前面创建好的ServletConfig对象)->调用service方法服务->调用destroy方法做销毁前工作->服务器关闭实例化对象被释放。

由此我们不难发现这个ServletConfig接口其实就是封装着Servlet对象对应的web.xml对应的配置信息,然后在服务器实例化Servlet对象后会调用init方法,把这个封装好的ServletConfig对象作为实参传入init方法,然后因为我们在构建Servlet类的时候不再直接去实现Servlet接口,而是继承GenericServlet类,GenericServlet类中包含一个返回ServletConfig的方法,可以供我们在service方法中得到这个服务器传过来的实参,那么我们就可以在service中得到这个方法,并且通过ServletConfig提供的方法去得到ServletConfig里面封装好的配置信息,它的作用就在于此。但是!!!!你以为真的会有这么麻烦吗?不,其实在GenricServlet类中他就已经实现了ServletConfig接口,并且提供了一些列的方法足够你获取相关的配置信息。一下是截取的相关源代码

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
        public String getInitParameter(String name) {
            return this.getServletConfig().getInitParameter(name);
        }
        
        public Enumeration<String> getInitParameterNames() {
            return this.getServletConfig().getInitParameterNames();
        }

        public ServletConfig getServletConfig() {
            return this.config;
        }

}

也就是说你可以直接在Servlet对象中的service方法中使用,因为你的Servlet对象实现了GenricServlet,然后GenricServlet继承了ServletConfig这个接口,那么子类就拥有父类的所以方法,然后你就可以通过this来去调用了。

所以:常用方法有两种方式获取:

法一:直接用this获取

this.getInitParameter(名字)得到对应的value,

this.getInitParameterNames()得到配置信息的名字集合Map集合,可以遍历取值

法二:通过对象获取

ServletConfig servletconfig = this.getServletConfig;

servletconfig.getInitParameter(名字)得到对应的value,

servletconfig.getInitParameterNames()得到配置信息的名字集合Map集合,可以遍历取值

前提是继承了GenericServlet抽象类

ServletContext(很重要)

引入:一个Tomcat服务器(也称WEB容器)下包含一个webapps,webapps包含N个webapp,每一个webapp就对应一个项目,一个项目下有多个Servlet实现类。一个Servlet实例对象对应一个ServletConfig对象,那么一个ServletContext对象对应一个webapp项目,你有没有想过在服务器启动的时候会去解析整个web.xml文档的共享配置信息,解析的信息放在哪里?就放在ServletContext对象中,在启动服务器后,服务器就开始创建一个ServletContext对象,ServletContext也叫为服务环境或者服务上下文,所以一个webapp项目对应一个ServletContext对象。这个接口对象不仅可以存放从web.xml中获取的公共信息,我们也可以在service方法中去调用该接口的相关方法去存放别的公用数据,其实这个ServletContext有点类似于缓冲池,你自己琢磨下是不是。

获取ServletContext对象方式:它是通过ServletConfig对象去获取的。

在GenericServlet中有这样一段:

public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
}

我们在Servlet实现类中就有两种方式获取ServletContext对象:

法一:

ServletContext application= this.getServletConfig().getServletContext();

法二:

ServletContext application= this.getServletContext();

前提是继承了GenericServlet抽象类,两种方式都是通过ServletConfig对象去获取的,只不过法二是间接的获取。

常用方法:(ServletContext的方法都需要通过对象才能调用)

获取web.xml中的公共信息,注意在服务器启动的时候就会创建一个ServletContex对象,并且他会把web.xml中的公用信息存入该对象。我们只需要调取相关方法即可获取

setAttribute、getAttribute、removeAttribute

以下方法可以对ServletContext对象进行(添加、删除、修改、查询)公用的数据呢?

    // 存
    public void setAttribute(String name, Object value); // map.put(k, v)
    // 取
    public Object getAttribute(String name); // Object v = map.get(k)
    // 删
    public void removeAttribute(String name); // map.remove(k)

"/"就代表web,看本块第二张图

绝对路径长这样:

 getContextPath()获得web应用的根路径,其实就是项目名,比如:在testConfigServlet类中编写getContextPath()会得到:

localhost:8080/config/testConfigServlet   中的/xmm

也就是你在Idea运行项目前部署的那个东西,如下:

 注意:

 // ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)
    
    // 如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中
    
  // 为什么是所有用户共享的数据?

不是共享的没有意义。因为ServletContext这个对象只有一个。只有共享的数据放进去才有意义。
    
 // 为什么数据量要小?

因为数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能。占用内存较小的数据量可以考虑放进去。
    

 // 为什么这些共享数据很少的修改,或者说几乎不修改?
所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在ServletContext对象中的数据一般都是只读的。数据量小、所有用户共享、又不修改,这样的数据放到ServletContext这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次在用的时候,不需要从数据库中再次获取,大大提升执行效率。

HttpServlet

 但是在学习这个HttpServlet之前得先学会HTTP协议,所以先看下面一节再回过来看这里。

HTTP协议(必须会)

 ​​​​​​

HTTP的请求协议(B--->S)

get请求报文        

post请求报文

HTTP的请求协议分为四部分:

请求行

 

请求头

空白行(用来分割请求头和请求体)

请求体

HTTP的响应协议(S------>B)

HTTP响应报文

HTTP/1.1 200   ok                                                                   状态行 (最主要)       

Content-Type: text/html;charset=UTF8                                   响应头

Content-Length: 133                                                                

Date: Sun, 12 Feb 2023 07:08:13 GMT

Keep-Alive: timeout=20 Connection: keep-alive

                                                                                                空白行

<!DOCTYPE html>                                                                  响应体

<html lang='en'>

<head>

   <title>test(get/post)Servlet</title>

</head><body>

   <h1>from get servlet</h1>

</body></html>

解读HTTP的响应协议分为四部分:

状态行:(比如:HTTP/1.1 200   ok)

响应头

 

空白行(用来分割响应和响应体)

响应体

 GET和POST请求的区别

 先知:

区别:

什么时候用GET什么时候用POST?

HttpServlet

先知

 就是说service方法中的那两个参数请求和响应对象封装了请求报文和响应报文,我们只需要面向这两个对象就可以获取浏览器端的请求和响应信息。

HttpServlet源码剖析

我们通过一个Servlet对象的生命周期来学习HttpServlet的源码(注意这个HttpServlet肯定是和Servlet接口的后代)。

public class HelloServlet extends HttpServlet {
//这是一个继承了HttpServlet抽闲类的Servlet类
}

根据一个普通的Servlet对象来剖析HttpServlet的源码 (HttpServlet继承类必然也会走和普通Servlet类一样的过程)

  1. 启动Tomcat服务器,创建一个ServletContext对象(用来存放整个项目的公用配置信息),解析HelloServlet对应项目中的web.xml文档的公用配置信息。
  2. 用户发送第一次请求HelloServlet,Tomcat服务器调用HelloServlet的无参构造实器例化一个HelloServlet对象。紧接着创建一个ServletConfig(翻译为服务配置对象)对象,该对象从web.xml文档中获取了HelloServlet的配置信息,然后调用HeloServlet中的init方法(ServletConfig对象作为实参传入)此时发现HelloServlet中没有实现init方法,则他肯定是实现了父类中的init方法,逐级往上找我们发现他的直接父类HttpServlet也没有实现该方法,那么在往上一级找我们发现HttpServlet的直接父类GenericServlet实现了该方法,并且他有两个init方法,如下:
       public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
        }
    
        public void init() throws ServletException {
        }

  3. 我们知道服务器只会调用那个有参的init方法,并且那个参数是一个ServletConfig对象,那么服务器到找这里因为这个GenericServlet重写了这个方法,就会调用这个init方法,并且在这个方法中他还调用了无参的init方法,GenericServlet这里重写init的作用就是:是因为有可能在service方法中我们需要使用这个ServletConfig对象所以把它定义为成员变量,那这里为什么还要创建一个无参的init方法?并且最后还在有参的init方法体中调用它,因为init方法的本意就是用来初始化的,我们很有可能也需要在这里进行一些初始化的业务,那么你就可以在Servlet实现类中重写一个无参的init的方法,就会从有参的init方法去调用你重写的那个无参的init方法!!!
  4. 再接着调用HelloServlet中的service(ServletRequest req,ServletRsponse res)方法,该方法在HttpServlet中被重写了
  5. 他重写了Servlet接口中的service方法:在方法体中做了两件事情,把服务器传过来的请求和响应对象强转成(向下转型)HttpServletRequest,HttpServletRsponse,这两个类对应是ServletRequest,ServletRsponse的子类。第二件事情是调用了service(HttpServletRequest req,HttpServletRsponse res)方法,这个方法在HttpServlet中定义。我们来看看service(HttpServletRequest req,HttpServletRsponse res)方法的源码

  6.   protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String method = req.getMethod();
            long lastModified;
            if (method.equals("GET")) {
                lastModified = this.getLastModified(req);
                if (lastModified == -1L) {
                    this.doGet(req, resp);
                } else {
                    long ifModifiedSince;
                    try {
                        ifModifiedSince = req.getDateHeader("If-Modified-Since");
                    } catch (IllegalArgumentException var9) {
                        ifModifiedSince = -1L;
                    }
    
                    if (ifModifiedSince < lastModified / 1000L * 1000L) {
                        this.maybeSetLastModified(resp, lastModified);
                        this.doGet(req, resp);
                    } else {
                        resp.setStatus(304);
                    }
                }
            } else if (method.equals("HEAD")) {
                lastModified = this.getLastModified(req);
                this.maybeSetLastModified(resp, lastModified);
                this.doHead(req, resp);
            } else if (method.equals("POST")) {
                this.doPost(req, resp);
            } else if (method.equals("PUT")) {
                this.doPut(req, resp);
            } else if (method.equals("DELETE")) {
                this.doDelete(req, resp);
            } else if (method.equals("OPTIONS")) {
                this.doOptions(req, resp);
            } else if (method.equals("TRACE")) {
                this.doTrace(req, resp);
            } else {
                String errMsg = lStrings.getString("http.method_not_implemented");
                Object[] errArgs = new Object[]{method};
                errMsg = MessageFormat.format(errMsg, errArgs);
                resp.sendError(501, errMsg);
            }
    
        }

    我浅说一下他在里面做了什么:首先就是根据你传过来的两个请求和响应的参数去判断你是什么类型的请求,然后根据不同的请求写了一些不同的响应,这些响应多为报错响应。

  7. 我问一个问题:他在HttpServlet中重写了顶级父类中的service方法,并且创建了一个新的service方法,那我们做业务开发肯定也是写在service方法中,按照原来的思维我们继承了HttpServlet就得去重写或者实现service方法,在HttpServlet中已经实现了这个方法,若你执意重写肯定就会把HttpServlet中的service方法覆盖。此时你可能会想那在HttpServlet中不是有两个方法吗?我不重写服务器调用的那个service(HServletRequest req,ServletRsponse res)不就好了,我重写另外一个参数是service(HttpServletRequest req,HttpServletRsponse res)的service不就好了,因为前者会调用后者,没毛病老铁!但是你要是把后面这个重写了那人家里面干嘛要写这么多的服务啊???那我重写前面那个可不可以?重写前面那个然后在最后加上this.service(HttpServletRequest req,HttpServletRsponse res);可以吗?可以是可以但是一般开发中不会这样做。

  8. 为什么?你回过头去看service(HttpServletRequest req,HttpServletRsponse res)中写的那些服务,大概都只这样的判断请求类型{响应报错信息},你在联想一下如果你在service中做开发,你是不是也是这样判断请求类型{做相应的业务},卧槽你不觉得很耦合吗?既然如此我直接在继承HttpServlet中的Servlet中重写service(HttpServletRequest req,HttpServletRsponse res)对应的方法不就好了,也就是说我不重写service方法,我就让服务器去实现HttpServlet中的service(ServletRequest req,ServletRsponse res)方法,然后这个方法会调用这个service(HttpServletRequest req,HttpServletRsponse res)方法,区这样既能实现业务,

  9. 他在HttpServlet中实现了顶级的Servlet的service(ServletRequest req,ServletRsponse res)方法,在该方法中调用了this.service(HttpServletRequest,HttpServletRsponse),她已经编写好了service(HttpServletRequest,HttpServletRsponse)方法,在这个方法体中做的内容全是判断请求类型调用doGet或者doPost方法,默认的doGet或者doPost方法就是响405错误,如果一旦调用到了默认doGet或者doPost方法那肯定是会响应405错误,那我们只需要在HelloService类中自行制定请求的类型,如果是需要用get请求的你就重写doGet方法,如果是post类型的就重写doPost方法,这样的话如果浏览器发送的请求类型符合你指定的请求那么当走到service(HttpServletRequest,HttpServletRsponse)这个方法的时候就会调用你写的那个doGet、doPost方法而不是调用默认的。如果调用默认的则代表你设置的请求类型与前端那边设置的请求类型不一致那么就会调用默认的doGet或默认的doPost方法然后响应405错误。
  10. 后面就没必要再说了,你理解到这里已经够了。

总结:继承HttpServlet抽象类Servlet的一个完整过程

一个完整的AServlet项目的生命周期是这样的。

  1. 服务器启动,创建ServletContext对象,该对象与AServlet对应的项目是一对一的关系。注意:是对项目不是对AServlet,项目中可以有多个Servlet实现类
  2. 解析web.xml文档把公用信息存入ServletContext对象
  3. 用户第一次发送AServlet请求,调用AServlet的无参构造器实例化对象。
  4. 创建一个ServletConfig对象,该对象与AServlet实例对象是一对一的关系(也就是说服务器每实例化一个AServlet对象就会创建一个ServletConfig对象);
  5. 调用init(ServletConfig)方法,把第四步中服务器创建的为实参传入。
  6. 从写根据业务自行定制请求方式重写doGet方法或者doPost方法。

ServletRequest(请求域)和ServletContext(应用域)

“请求域”对象

        - “请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。

        - 一个请求对象request对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。

 
 HttpServletRequest接口详解 

先知:

HttpServletRequest对象是什么?作用是啥?在什么时候被创建?被谁创建?

          他是一个服务器创建的请求对象,用户每发送一个请求服务器就对应创建一个ServletRequest对象并且会当成参数传递给service方法,在httpServlet抽象类中把ServletRequest向下转换成了HttpServletRequest,他里面包含了浏览器发送的请求报文,这些我们只需要知道即可,没必要关心他是怎么创建的,我们面向HttpServletRequest这个接口即可,通过该接口的方法可以得到浏览器传过来的请求信息。

HttpServletRequest的四个常用方法

因为我们知道这个接口就是用来操作浏览器发送的请求信息的,那么我们先看一个需求

  那我们就采用Map<String,String[]>的形式存储,即可解决上面的问题。

  注意:前端表单提交数据的时候,假设提交了120这样的“数字”,其实是以字符串"120"的方式提交的,所以服务器端获取到的一定是一个字符串的"120",而不是一个数字。(前端永远提交的是字符串,后端获取的也永远是字符串。)

通过上面的需求我们分析出了使用Map<String,String[]>的方式是最恰当的,那我们来看看HttpServletRequest中获取请求信息常用的四个方法

若前端提交的数据格式:username=abc&userpwd=111&aihao=s&aihao=d&aihao=tt

getParameterMap()

返回一个Map<String,String[]>,即<"key",字符串数组["key1","key2"]>


getParameterNames()

返回Enumeration<String> 返回此次请求的所有的key,以枚举的形式返回


getParameterValues(String name)

返回String[]  他会根据key获取Map集合的value


getParameter(String name) 

返回一个String,根据key获取对应value这个一维数组当中的第一个元素。

这个方法最常用。
      // 以上的4个方法,和获取用户提交的数据有关系。

 HttpServlet其他常用方法

请求域(Request)

需求:对AServlet的请求域(请求对象)添加参数,然后在AServlet中调用BServlet把AServlet的请求和响应传给BServlet;

public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, 
                         HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF8");
        Date date = new Date();
        //往AServlet的请求域添加一个参数
        request.setAttribute("date",date);
        //获关于BServlet的请求转发器对象(传入BServlet的<url-pattern>)
        RequestDispatcher rd = request.getRequestDispatcher("/b");
        //把两个参数传过去
        rd.forward(request,response);
    }
}
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) 
                throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF8");

        Object date = request.getAttribute("date");
        PrintWriter out = response.getWriter();
        out.print("这里是BServlet,输出AServlet传递过来的参数:"+date);
    }
}

切记:以上两段代码写完后,你打开AServlet就可以访问到BServlet了,而且也只能这样访问,若先访问A在访问B是拿不到A传过来的请求域对象和响应对象的。

 getRemoteAddr()获取客户端的IP地址

String remoteAddr = request.getRemoteAddr();

注意:请求域中有两个方法非常容易搞混

设置请求域编码

我们知道前端有两种方式请求,get和post。get请求在请求行上提交数据。post请求在请求体中提交数据。

Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码,怎么解决这个乱码?执行以下第一行代码。既然说了是请求体,那么这种方法只适用于Post请求。

request.setCharacterEncoding("UTF-8");

 

获取应用的根路径getRequestURI()

String contextPath = request.getContextPath();

其实就是获取项目名,比如:localhost:8080/xmm/helloservlet中的/xmm

获取请求方式getMethod()

String method = request.getMethod();

获取请求的URI:getRequestURI()

String uri = request.getRequestURI();  

比如:localhost:8080/xmm/helloservlet中的/xmm/helloservlet

获取servlet path

String servletPath = request.getServletPath();

比如:localhost:8080/xmm/helloservlet中的/helloservlet

session和cookie

什么是会话?

会话是指一个客户端与web服务器之间连续发生的一系列请求和响应的过程。就像是从拨通电话到挂断电话之间聊天的过程就是一个会话。

什么是会话状态?

web应用的会话状态是指服务器与浏览器在会话过程中产生的状态信息,借助会话状态,web服务器能够把属于同一会话中的一系列请求和响应过程关联起来,使得他们之间可以相互依赖和传递信息。例如在一个购物网站购买东西,结算时必须知道登录请求表单的结果,以便知道是哪个账户在操作。还必须知道已选商品的信息。其中的用户登录的账户信息和已选商品信息就是会话的状态信息

什么是session?

session存在于服务器,有服务器创建,一个session对象就代表一次会话过程,一次会话中包含了N个请求(因为你登录web应用肯定是会有很多操作的嘛,每一个操作就是一个请求)。session对象最主要的作用是:保存会话状态。(用户登录成功了,这是一种登录成功的状态,你怎么把登录成功的状态一直保存下来呢?使用session对象可以保留会话状态。)

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

  • 因为HTTP协议是一种无状态协议。
  • 什么是无状态:请求的时候,B和S是连接的,但是请求结束之后,连接就断了。为什么要这么做?HTTP协议为什么要设计成这样?因为这样的无状态协议,可以降低服务器的压力。请求的瞬间是连接的,请求结束之后,连接断开,这样服务器压力小。只要B和S断开了,那么关闭浏览器这个动作,服务器知道吗?不知道。服务器是不知道浏览器关闭的,因为连接都断了服务器怎么可能知道你关闭了浏览器。
  • 那session对象何时销毁?session采用超时机制,在tomcat服务器中,一个会话若超过30分钟没有发送请求则会销毁这个session对象,当然这个超时的时间可以自行设定(0-正无穷),。

由此,引出一个session对象的生命周期

创建session:是指当用户第一次访问jsp页面时,因为jsp页面内置了session对象或Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session。
只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。当然你也可以手动生成session对象:HttpSession session = request.getSession();这行代码的含义是从服务器获取session对象,若session存在则获取,不存在则会创建然后在返回session对象。

第一次是指:浏览器访问服务器时,不带值为sessionId的cookie
销毁session(只有这两种情况):
a.当session有效期到期(默认为30分钟,可手动设置)
b.手动销毁session,用session.invalidate();

session的实现原理

在web服务器中有一个session列表。类似于map集合这个map集合的key存储的是sessionid这个map集合的value存储的是对应的session对象。
用户发送第一次请求的时候: 服务器会创建一个新的session对象,同时给session对象生成一个sessionid,然后web服务器会将sessionid发送给浏览器,浏览器将sessionid保存在浏览器的缓存中
用户发送第二次请求的时候:自动将浏览器缓存中的sessionid自动发送给服务器,服务器获取到sessionid然后从session列表中查找到对应的session对象。

注意:

1.当浏览器关闭后浏览器的缓存就会清空,那么当你重新打开浏览器再去访问前面的web服务时又会创建一个新的session对象,因为之前的缓存被清空了浏览器找不到原来的sessionid。

2.用户发送的第一次请求是没有sessionid的,因为这个sessionid时需要服务器响应的时候传给浏览器的,就是说第一次请求的时候因为还没有创建这个session对象,那何来的sessionid?在第一次访问后服务器创建了session对象后他才会响应给浏览器一个sessionid。但是发送第二次请求的时候就会包含一个cookie,这个cookie就存储了sessionid,服务器会根据cookie中的sessionid来去找到session对象,只要这个session对象没有被销毁那么它就是同一次会话。而且第二次响应的时候服务器中就不会再响应sessionid了。

总结:一次会话指的其实就是一个session对象从创建到销毁的过程,当然中间可以包含N个请求。但是并不是说我们关闭了浏览器session对象就销毁了,只是我们无法再次使用那个session对象,我们关闭浏览器服务器是不知道的,因为浏览器采用的是Http协议,只有请求瞬间才是连接的,存在于服务器的session对象的销毁有两种方式:1.session超时机制。2.手动销毁。其实这两种方式就类似于:比如说登录一个网银,我直接关闭浏览器和点击退出登录账户的区别,直接关闭session对象仍然存在它采用超时机制销毁,点击退出登录账户,就相当于告诉服务器我要销毁session对象。

如何设置session超时机制时间?

1.在web.xml文件中设置

总结一下到目前位置我们所了解的域对象:

request(对应的类名:HttpServletRequest)请求域(请求级别的)

session(对应的类名:HttpSession)会话域(用户级别的)application(对应的类名:ServletContext)应用域(项目级别的,所有用户共享的。)

这三个域对象的大小关系request < session < application他们三个域对象都有以下三个公共的方法:

  • setAttribute(向域当中绑定数据)
  • removeAttribute(删除域当中的数据)
  • getAttribute(从域当中获取数据)
  • 使用原则:尽量使用小的域。

销毁session对象

session.invalidate();

多用于退出登录

cookie

 

关于cookie的有效时间

怎么用java设置cookie的有效时间

        Cookie cookie = new Cookie("product","123");
        //设置cookie的生命时长
        cookie.setMaxAge(60 * 60);//设置生命时长1小时
        //将cookie对象发送给浏览器
        response.addCookie(cookie);

//
设置cookie生命时长有三种情况:

1.只要设置cookie的有效时间 > 0,这个cookie一定会存储到硬盘文件当中。
2.设置cookie的有效时间 = 0 呢?cookie被删除,同名cookie被删除。
3.设置cookie的有效时间 < 0 呢?
表示这个cookie对象不要存在硬盘,让他保存在浏览器运行内存中。
和不设置cookie.setMaxAge效果一样。

没有设置有效时间:默认保存在浏览器的运行内存中,浏览器关闭则cookie消失。





 浏览器发送cookie给服务器了,服务器中的java程序怎么接收?

        //从浏览器获取cookie对象,这里若楼浏览器没有发送cookie则会返回null
        //而不是返回一个长度为0的数组
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                String value = cookie.getValue();
                System.out.println(name+"="+value);
            }
        }

cookie说白了就是保存浏览器中某些数据,用这些数据来维持会话状态。cookie对象可以保存在浏览器内存中也可以保存在硬盘上。

用Cookie实现十天内免登录

怎么让服务器启动的时候就实例化servlet对象呢?

只需要在web.xml中配置<load-on-startup>整数</load-on-startup>,这个整数越小创建的优先级越高,什么意思呢?就是说比如你需要在服务器启动的时候创建多个servlet实现对象那么他们创建的先后顺序就取决于你这里填写的整数。

<servlet>

        <servlet-name>hello</servlet-name> <!--声明一个变量存储servlet接口实现类类路径-->

        <servlet-class>com.bjpowernode.controller.OneServlet</servlet-class>

        <!--声明servlet接口实现类类路径-->

        <load-on-startup>0<load-on-startup><!--填写一个大于或者等于0的整数即可-->

</servlet>

自己手动实例化的servlet对象和服务器创建的servlet对象一样吗?

比如:服务器实例化sutdent_servlet对象,和我们自己手动去new一个sutdent_servlet对象一样吗?不一样!!!服务器创建的sutdent_servlet对象会放在一个Map集合中进行管理,他的生命周期以及所有的方法调用都由服务器操作管理,但是我们手动new的sutdent_servlet对象不会放在服务器下的那个Map集合中。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

new麻油叶先生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值