servlet:JavaWeb——Servlet(全网最详细教程)

什么是Servlet?

   Servlet是Server Applet的简称,是用Java编写的服务器端程序,是JavaEE平台下的技术标准。其主要功能在于和浏览器交互并生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

   Servlet通常通过 HTTP(超文本传输协议)接收和响应来自 Web 客户端的请求。从原理上来讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。Servlet有三种实现方式:实现Servlet接口、继承抽象类GenericServlet、继承HttpServlet。

Servlet在程序中到底处于一个什么地位?

1. Servlet是可以接收http请求并作出响应的一种技术,是JAVA语言编写的一种动态资源;
2. Servlet是前后端衔接的一种技术,不是所有的JAVA类都可以接收请求和作出响应,但是Servlet可以;
3. 在MVC模式中,Servlet作为Controller层(控制层)主要技术,用于和浏览器完成数据交互,控制交互逻辑;

Servlet的工作模式

  • 客户端发送请求至服务器
  • 服务器运行并调用Servlet,Servlet根据客户端请求生成响应内容并将其传给服务器,响应内容动态生成,通常取决于客户端的请求
  • 服务器将响应返回客户端

Servlet体系结构与Tomcat的关系

Servlet是Tomcat的一个组件,Servlet的功能需要依赖一个servlet-api.jar,这个包是由Tomcat提供的,Tomcat在初始化Servlet时,首先读取web.xml文件,根据web.xml文件中的参数信息初始化ServletConfig、ServletContext对象,同时帮助我们创建HttpServletRequest和HttpServletResponse对象一并交给Servlet实例,此时,Servlet就具有了相关的功能。

Servlet API 概览

Servlet API 包含以下4个Java包:

1. javax.servlet:其中包含定义Servlet和Servlet容器之间契约的类和接口。

2. javax.servlet.http:主要定义了与HTTP协议相关的HttpServlet类,HttpServletRequest接口和HttpServletResponse接口。

3. javax.servlet.annotation: 其中包含标注Servlet、Filter、Listener的标注。它还为被标注元件定义元数据。

4. javax.servlet.descriptor:其中包含提供程序化登录Web应用程序的配置信息的类型。

Servlet的主要类型

Servlet、ServletConfig、ServletContext、ServletRequest、ServletResponse、GenericServlet(抽象类)

Servlet的继承结构

Servlet接口,只负责定义Servlet程序的访问规范;

GenericServlet抽象类实现了Servlet接口,做了很多空实现,并持有一个ServletConfig类的引用,并提供了一些ServletConfig的使用方法;

HttpServlet抽象类实现了service方法,并实现了请求分发处理;

Servlet的使用方法

Servlet技术的核心是Servlet,它是所有Servlet类必须直接或者间接实现的一个接口。在编写实现Servlet接口的类时,直接实现它,在扩展实现Servlet这个接口的类时,间接实现它。

Servlet的工作原理

 Servlet接口定义了Servlet与Servlet容器之间的契约,这个契约是:Servlet容器将Servlet类载入内存,生成Servlet实例并调用它具体的方法。但是要注意的是,在一个应用程序中,每种Servlet类型只能有一个实例

    用户发起请求使Servlet容器调用Servlet的service()方法,并传入一个ServletRequest对象和一个ServletResponse对象。ServletRequest对象和ServletResponse对象都是由Servlet容器(例如Tomcat)封装好的,并不需要开发人员去实现,开发者可以直接使用这两个对象。

    ServletRequest中封装了当前的Http请求,因此,开发人员不必解析和操作原始的Http数据。ServletResponse表示当前用户的Http响应,开发人员只需直接操作ServletResponse对象就能把响应轻松的返回给用户。

   对于每一个应用程序,Servlet容器还会创建一个ServletContext对象,这个对象中封装了上下文(应用程序)的环境详情。每个应用程序只有一个ServletContext,每个Servlet对象也都有一个封装Servlet配置的ServletConfig对象

Servlet 接口中定义的方法

 我们首先来看一看Servlet接口中定义了哪些方法:

public interface Servlet { void init(ServletConfig var1) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo(); void destroy();}

其中,init( )、service( )、destroy( )是Servlet生命周期的方法,代表了Servlet从“诞生”到“执行”再到“销毁 ”的过程。

Servlet的生命周期

Servlet的生命周期是由容器管理的,Servlet容器(例如Tomcat)会根据下面的规则来调用这三个方法:

1. 初始化方法init( ),只会执行一次(启动Tomcat的时候默认是不执行的,在访问的时候才会执行)当Servlet第一次被请求时,Servlet容器会实例化这个Servlet,然后就会调用这个方法来初始化Servlet,但是这个方法在后续请求中不会在被Servlet容器调用,我们可以利用init()方法来执行相应的初始化工作。

2. 服务方法service( ),每当请求Servlet时,Servlet容器就会调用这个方法。第一次请求时,Servlet容器会先调用init( )方法初始化一个Servlet对象出来,然后会调用它的service( )方法进行工作,但在后续的请求中,Servlet容器只会调用service方法了。

3. 销毁方法destory(),当要销毁Servlet时,Servlet容器就会调用这个方法,卸载应用程序或者关闭Servlet容器时,就会发生这种情况,一般在这个方法中会写一些清除代码。

一般在实际项目开发中,都是使用继承HttpServlet类的方式去实现 Servlet 程序,下面编写一个简单的Servlet程序来验证一下它的生命周期:

public class ServletLifeCycle extends HttpServlet { @Override public void init() throws ServletException { System.out.println("ServletLifeCycle 初始化方法执行" ); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws //执行服务 System.out.println("ServletLifeCycle service方法执行" ); } @Override public void destroy() { System.out.println("ServletLifeCycle 销毁方法执行" ); } @Override public ServletConfig getServletConfig() { return super.getServletConfig(); } @Override public String getServletInfo() { return super.getServletInfo(); } } <servlet> <servlet-name>ServletLifeCycle</servlet-name> <servlet-class>com.cgcstudy.servlet.ServletLifeCycle</servlet-class> <!-- <load-on-startup>6</load-on-startup>--></servlet><servlet-mapping> <servlet-name>ServletLifeCycle</servlet-name> <url-pattern>/servletLifeCycle.do</url-pattern></servlet-mapping>

在xml中配置正确的映射关系,在浏览器中访问Servlet,第一次访问时,控制台输出了如下信息:

再次访问Servlet,控制台输出的信息变成了下面这样:

接下来,关闭Servlet容器,控制台输出了Servlet的销毁信息:

这就是一个Servlet的完整生命周期。

通过以上案例发现Servlet默认情况下是在第一次访问时被创建并初始化的,如果初始化的工作比较多,那么第一次访问就会比较耗时,我们可否改变它的创建时机呢?
     我们可以在web.xml文件中配置<load-on-startup>,使得Servlet在服务器启动时创建并初始化,<load-on-startup>节点必须写在<servlet>节点的内部,且作为最后一个节点,取值必须是整数,如果是大于等于0的整数表示在服务器启动时就被创建并初始化,如果有多个Servlet设置了<load-on-startup>节点的值,那么值越小越优先执行;如果是负数则表示第一次被访问时创建并初始化,也就是默认情况,可以省略不写。

 总结

Servlet的生命周期是由容器管理的,Servlet从创建到销毁的全过程,共分为四个阶段:

1. 创建阶段:创建Servlet对象;

2. 初始化阶段:会执行init方法,只会执行一次。默认是在第一次访问Servlet时执行,可以在web.xml进行配置,配置<load-on-startup> 让Servlet随着Tomcat的启动而进行初始化,配置的值大于0即可,配置为负数就是默认情况,当多个 Servlet配置了<load-on-startup>值越小的优先执行;

3. 服务阶段:执行service方法,会执行多次;

4. 销毁阶段:会执行destroy方法,只会执行一次;

 注意点
      访问Servlet会实例化Servlet对象,发现构造方法只会执行一次,说明对象只创建了一
次,即单例模式。

Servlet 的其它两个方法:

getServletInfo(),这个方法会返回Servlet的一段描述,可以返回一段字符串;

getServletConfig(),这个方法会返回由Servlet容器传给init()方法的ServletConfig对象;

ServletConfig接口

ServletConfig对象对应web.xml文件中的<servlet>节点,当Servlet容器(如Tomcat)初始化Servlet时,会将该Servlet的配置信息,封装到一个ServletConfig对象中,我们可以通过该对象读取<servlet>节点中的配置信息

<servlet> <servlet-name>servletName</servlet-name> <servlet-class>servletClass</servlet-class> <init-param> <param-name>key</param-name> <param-value>value</param-value> </init-param></servlet>

当Servlet容器初始化Servlet时,Servlet容器会给Servlet的init( )方式传入一个ServletConfig对象,ServletConfig其中几个方法如下:

public interface ServletConfig { String getServletName(); //获得Servlet在web.xml中配置的name值 ServletContext getServletContext(); //获得ServletContext对象 String getInitParameter(String var1); //获得Servlet的初始化参数 Enumeration<String> getInitParameterNames(); //获得所有Servlet的初始化参数的名称}

servletConfig.getInitParameter("key"),该方法可以读取web.xml文件中<servlet>标签中<init-param>标签中的配置信息;

servletConfig.getInitParameterNames(),该方法可以读取web.xml文件中当前<servlet>标签中所有<init-param>标签中的值;

ServletContext

ServletContext官方叫Servlet上下文。ServletContext对象表示Servlet应用程序,服务器会为每一个Web应用创建一个ServletContext对象,每个Web应用程序都只有一个ServletContext对象,这个对象全局唯一,而且Web应用中的所有Servlet都共享这个对象,所以叫全局应用程序共享对象;在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web应用都会有一个ServletContext对象。

那么为什么要存在一个ServletContext对象呢?因为有了ServletContext对象,可以利用该对象获取整个Web 应用程序的初始化信息、读取资源文件等,并且可以动态注册Web对象。这些信息是保存在ServletContext中的一个内部Map中,保存在ServletContext中的对象被称作属性。

ServletContext获取方式有两种:

1. request.getServletContext();         

2. this.getServletContext();

ServletContext的作用:

1. 获取web.xml文件中的信息,可以为所有的Servlet提供初始化数据;

<context-param> <param-name>key</param-name> <param-value>value</param-value></context-param>

<context-param>配置是一组键值对, <context-param>的作用:这个元素用来声明应用范围内的上下文初始化参数,其中的 <param-name> 设定上下文的参数名称,这个必须是唯一的,而<param-value>设定的参数名称的值。当服务器启动时,服务器会读取web.xml配置,当读到<context-param></context-param>这个节点的时候,容器会将这个节点中配置的值set到ServletContext(上下文对象)中,这样我们在程序中就能通过这个上下文对象去取得我们这个配置值。

servletContext.getInitParameter("key"),该方法可以读取web.xml文件中<context-param>标签中的配置信息。

servletContext.getInitParameterNames(),该方法可以读取web.xml文件中所有 <param-name> 标签中的值。

<context-param>配置和<init-param>的区别:

我们可以看到<init-param>是放在一个Servlet内的,所以这个参数是只针对某一个Servlet而言的,所以它们的区别就有点像全局变量和局部变量的,<context-param>是针对整个项目,所有的Servlet都可以取得使用,<init-param>只能是在某个Servlet下面配置,就在那个Servlet里面调用。

2. 域对象,共享数据(ServletContext对象是最大的域对象,被Web应用中的所有Servlet共享,而且 ServletContext可以帮助我们实现数据的传递);

ServletContext中的下列方法负责处理属性: 

Object getAttribute(String var1); Enumeration<String> getAttributeNames(); //从全局容器中获取数据 void setAttribute(String var1, Object var2); //向全局容器中存放数据 void removeAttribute(String var1); //根据key删除全局容器中的value

ServletContext对象生命周期:当容器启动时会创建ServletContext对象并一直缓存该对象,直到容器关闭后该对象生命周期结束,ServletContext对象的生命周期非常长,所以在使用全局容器时不建议存放业务数据。

GenericServlet抽象类

    我们可以通过实现Servlet接口来编写Servlet程序,但是,使用这种方法,必须要实现Servlet接口中定义的所有的方法,即使有一些方法中没有任何东西也要去实现,并且还需要自己手动的维护ServletConfig这个对象的引用。因此,这样去实现Servlet是比较麻烦的,GenericServlet抽象类的出现很好的解决了这个问题,本着尽可能使代码简洁的原则,GenericServlet实现了Servlet和ServletConfig接口,下面是GenericServlet抽象类的具体代码:

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { private static final long serialVersionUID = 1L; private transient ServletConfig config; public GenericServlet() { } public void destroy() { } public String getInitParameter(String name) { return this.getServletConfig().getInitParameter(name); } public Enumeration<String> getInitParameterNames() { return this.getServletConfig().getInitParameterNames(); } public ServletConfig getServletConfig() { return this.config; } public ServletContext getServletContext() { return this.getServletConfig().getServletContext(); } public String getServletInfo() { return ""; } public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { } public void log(String message) { this.getServletContext().log(this.getServletName() + ": " + message); } public void log(String message, Throwable t) { this.getServletContext().log(this.getServletName() + ": " + message, t); } public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; public String getServletName() { return this.config.getServletName(); }}

GenericServlet抽象类相比于直接实现Servlet接口,有以下几个好处:

1.为Servlet接口中的所有方法提供了默认的实现,开发者需要什么就直接改什么,不再需要把所有的方法都自己实现了。

2.提供方法,获取ServletConfig对象中的Servlet的配置信息。

3.将init( )方法中的ServletConfig参数赋给了一个内部的ServletConfig类型的成员变量,从而来保存ServletConfig对象,不需要开发者自己去维护ServletConfig了。

public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }

但是,我们发现在GenericServlet抽象类中还存在着另一个没有任何参数的Init()方法:

public void init() throws ServletException {}

设计者的初衷到底是为了什么呢?在第一个带参数的init()方法中就已经把ServletConfig对象传入并且通过属性(成员变量)保存好了,完成了Servlet的初始化过程,那么为什么后面还要加上一个不带任何参数的init()方法呢?这不是多此一举吗?

    当然不是多此一举了,存在必然有存在它的道理。我们知道,抽象类是无法直接产生实例的,需要另一个类去继承这个抽象类,那么就会发生方法覆盖的问题,如果在类中覆盖了GenericServlet抽象类的init()方法,那么开发者就必须手动地去维护ServletConfig对象了,就得调用super.init(servletConfig)方法去调用父类GenericServlet的初始化方法来保存ServletConfig对象,这样会给开发者带来很大的麻烦。GenericServlet提供的第二个不带参数的init( )方法,就是为了解决上述问题的。

    这个不带参数的init()方法,是在ServletConfig对象被赋给ServletConfig引用后,由第一个带参数的init(ServletConfig servletconfig)方法调用的,那么这意味着,当开发者如果需要覆盖这个GenericServlet的初始化方法,则只需要覆盖那个不带参数的init( )方法就好了,此时,ServletConfig对象仍然由GenericServlet保存着。通过扩展GenericServlet抽象类,就不需要覆盖没有计划改变的方法。因此,代码将会变得更加的简洁,程序员的工作也会减少很多。

   虽然GenricServlet是对Servlet一个很好的加强,但是也不经常用,因为它不像HttpServlet那么高级,HttpServlet在现实的应用程序中被广泛使用。

总结

    GenericServlet实现了Servlet接口中有参的init方法,同时自己还声明了一个无参的init方法,有参的init方法是Servlet的生命周期方法,而生命周期方法一定会被Servlet容器(如Tomcat服务器)调用,但我们自己编写的Servlet程序通常是重写无参的init方法,原因是我们自己编写的Servlet程序通常都是通过继承HttpServlet类的方式去实现 ,所以会间接继承GenericServlet,从而继承了它有参的init方法,当Tomcat初始化我们自己编写的Servlet时,会调用继承的init(ServletConfig config)方法,在init(ServletConfig config)方法中会调用无参的init方法,从而就会执行我们重写的无参的init方法;

javax.servlet.http包内容

HttpServlet是由GenericServlet抽象类扩展而来,针对于处理 HTTP协议的请求所定制。HttpServlet之所以运用广泛的另一个原因是现在大部分的应用程序都要与HTTP结合起来使用,这意味着我们可以利用HTTP的特性完成更多更强大的任务。Javax.servlet.http包是Servlet API中的包,其中包含了用于编写Servlet应用程序的类和接口,Javax.servlet.http中的许多类型都覆盖了Javax.servlet中的类型。

HttpServlet抽象类

HttpServlet继承自GenericServlet,使用HttpServlet抽象类时,还需要借助分别代表Servlet请求和Servlet响应的HttpServletRequest和HttpServletResponse对象,HttpServletRequest接口扩展于javax.servlet.ServletRequest接口,HttpServletResponse接口扩展于javax.servlet.servletResponse接口

public interface HttpServletRequest extends ServletRequest ​public interface HttpServletResponse extends ServletResponse

HttpServlet抽象类重写了GenericServlet抽象类中的service( )方法,并且添加了一个自己独有的service(HttpServletRequest req, HttpServletResponse resp)方法,首先来看一下HttpServlet是怎么重写这个service方法的:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch (ClassCastException var6) { throw new ServletException(lStrings.getString("http.non_http")); } this.service(request, response);}

我们发现,HttpServlet中的service方法把接收到的ServletRequsest类型的对象转换成了HttpServletRequest类型的对象,把ServletResponse类型的对象转换成了HttpServletResponse类型的对象。之所以能够这样强制的转换,是因为在调用Servlet的service方法时,Servlet容器总会传入一个HttpServletRequest对象和HttpServletResponse对象,一般使用HTTP,因此,转换类型当然不会出错了。

转换之后,service方法把两个转换后的对象传入了另一个service方法,我们再来看看这个方法是如何实现的:

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); }}

     我们发现,这个service方法的参数是HttpServletRequest对象和HttpServletResponse对象,是接收了上一个service方法传过来的两个对象。那么这个service方法做了些什么?我们会发现在service方法会解析HttpServletRequest中的方法参数,判断请求方式,并调用以下方法之一:doGet,doPost,doHead,doPut,doTrace,doOptions和doDelete。这7种方法中,每一种方法都表示一个Http请求方法,doGet和doPost是最常用的。所以,如果我们需要实现具体的服务逻辑,不再需要覆盖service方法了,只需要覆盖doGet或者doPost就好了。

总之,HttpServlet有两个特性是GenericServlet所不具备的:

1.不用覆盖service方法,而是覆盖doGet或者doPost方法。在少数情况,还会覆盖其他的5个方法。

2.使用的是HttpServletRequest和HttpServletResponse对象。

doGet、doPost方法与service方法的关系

    在HttpServlet的API中,新增了两个特殊的方法doGet和doPost,这两个方法是对Servlet方法的拆分,目的是希望不同的请求方式使用不同的方法处理。这让我们联想到表单的两种常用提交方式get和post,如果是get提交方式则使用doGet方法处理,如果时post提交方式则使用doPost方法处理。而service方法可以处理任何类型的请求,当我们去查看HttpServlet中service方法的源码,不难发现内部也通过对method请求方式做了验证决定调用doGet或doPost方法,所以三个方法之间的关系如下:

结论:在我们自定义的Servlet中,如果想区分请求方式,不同的请求方式使用不同的代码处理,那么我们重写 doGet 、doPost即可,如果我们没有必要区分请求方式的差异,那么我们直接重写service方法即可;
基于上述,要么重写doGet、doPost ,要么重写 service,必须二选一,而且必须进行重写。

Servlet处理请求的过程(基于Get请求)

当浏览器基于get方式请求我们创建的Servlet时,我们自定义的Servlet中的doGet方法会被执行。doGet方法之所以能够被执行并处理get请求的原因是:

1. 容器在启动时会解析web工程中WEB-INF目录中的web.xml文件,在该文件中我们配置了Servlet与URI的绑定,容器通过对请求的解析可以获取请求资源的URI,然后找到与该URI绑定的Servlet并做实例化处理(注意:只实例化一次,如果在缓存中能够找到这个Servlet就不会再做次实例化处理)。

2. 在实例化时会使用Servlet接口类型作为引用类型的定义,并调用一次init方法(调用的是继承的有参的init方法,在有参的init方法中调用无参的init方法),进而调到了我们自定义的Servlet中的init方法(我们自定义的init方法一般是重写无参的init方法),然后在新的线程中调用service方法。

3. 由于在HttpServlet中重写了Servlet的service方法,并且添加了一个自己独有的service(HttpServletRequest req, HttpServletResponse resp)方法,在HttpServlet中通过重写的service方法,把接收到的ServletRequsest类型的对象转换成了HttpServletRequest类型的对象,把ServletResponse类型的对象转换成了HttpServletResponse类型的对象,然后调用自己独有的service方法,所以最终执行的是HttpServlet中的service方法。

4. 在service方法中通过request.getMethod()获取到请求方式进行判断,如果是Get方式请求就执行doGet方法,如果是POST请求就执行doPost方法。如果是基于GET方式提交的,并且在我们自定义的Servlet中又重写了HttpServlet中的doGet方法,那么最终会根据Java的多态特性转而执行我们自定义的Servlet中的doGet方法。

Servlet映射方式(url-pattern的匹配规则)

前置说明

<servlet> <servlet-name>ServletDemo1</servlet-name> <servlet-class>com.cgcstudy.servlet.ServletDemo1</servlet-class></servlet><servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <!--url-pattern标签配置访问地址 "/" 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径/ /servletDemo1.do 表示地址为:http://ip:port/工程路径/ServletDemo1.do --> <url-pattern>/servletDemo1.do</url-pattern></servlet-mapping>

 

 精确匹配

精确匹配是指<url-pattern>中配置的值必须与url完全精确匹配

<servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/servletDemo1.do</url-pattern></servlet-mapping>

示例:

http://localhost:8080/ServletModule3/servletDemo1.do 匹配

http://localhost:8080/ServletModule3/demo/servletDemo1.do 不匹配

扩展名匹配

在<url-pattern>允许使用通配符“*”作为匹配规则,“*”表示匹配任意字符。在扩展名匹配中只要扩展名相同都会被匹配,与路径无关。注意,在使用扩展名匹配时在<url-pattern>中不能使用“/”,否则容器启动就会抛出异常。

<servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>*.do</url-pattern></servlet-mapping>

示例:

http://localhost:8080/ServletModule3/demo/abc.do 匹配

http://localhost:8080/ServletModule3/demo/test/abc.do 匹配

http://localhost:8080/ServletModule3/demo/abc 不匹配

路径匹配

根据请求路径进行匹配,在请求中要包含该路径,同时要跟在工程路径后面,“*”表示任意路径以及子路径。

<servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/demo/*</url-pattern></servlet-mapping>

示例:

http://localhost:8080/ServletModule3/demo/abc.do 匹配

http://localhost:8080/ServletModule3/demo/test/abc.do 匹配

http://localhost:8080/ServletModule3/test/abc/abc.do 不匹配

任意匹配

匹配所有,但不包含JSP页面

<url-pattern>/</url-pattern>

http://localhost:8080/ServletModule3/servletDemo1.do 匹配

http://localhost:8080/ServletModule3/addUser.html 匹配

http://localhost:8080/ServletModule3/addUser.jsp 不匹配

匹配所有

<url-pattern>/*</url-pattern>

http://localhost:8080/ServletModule3/demo/servletDemo1.do 匹配

http://localhost:8080/ServletModule3/demo/addUser.html 匹配

http://localhost:8080/ServletModule3/servletDemo1.do 匹配

优先顺序

当一个url与多个Servlet的匹配规则可以匹配时,则按照 “ 精确路径 > 最长路径 >扩展名”这样的优先级匹配到对应的Servlet。

Servlet1映射到 /abc/*

Servlet2映射到 /*

Servlet3映射到 /abc

Servlet4映射到 *.do

当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,Servlet引擎将调用Servlet1。

当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,Servlet引擎将调用Servlet3。

当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,Servlet引擎将调用Servlet1。

当请求URL为“/a.do”时,“/*”和“*.do”都匹配,Servlet引擎将调用Servlet2。

当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,Servlet引擎将调用Servlet2。

URL映射方式

在web.xml文件中支持将多个URL映射到一个Servlet中,但是相同的URL不能同时映射到两个Servlet中。

方式一

<servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/demo/*</url-pattern> <url-pattern>*.do</url-pattern></servlet-mapping>

方式二

<servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>/demo/*</url-pattern></servlet-mapping><servlet-mapping> <servlet-name>ServletDemo1</servlet-name> <url-pattern>*.do</url-pattern></servlet-mapping>

基于注解式开发Servlet

    在Servlet3.0以及之后的版本中支持注解式开发Servlet,对于Servlet的配置不在依赖于web.xml配置文件而是使用@WebServlet将一个继承于javax.servlet.http.HttpServlet的类定义为Servlet组件。

属性名

类型

描述
initParamsWebInitParam[]指定一组Servlet初始化参数,等价于标签
nameString指定Servlet的name属性,等价于标签。如果没有显示指定,则该Servlet的取值即为类的全限定名
urlPatterns

String[]

Servlet的访问URL,支持多个
value

String[]

该属性等价于urlPatterns属性,两个属性不能同时使用

loadOnStartupint指定Servlet的加载顺序,等价于标签
descriptionStringServlet的描述信息,等价于标签
displayName

String

Servlet的显示名称,通常配合工具使用,等价于标签
asyncSupportedboolean
 
声明Servlet是否支持异步操作模式,等价于标签

案例:

@WebServlet(urlPatterns = "/myServlet .do",displayName = "ms",name = "MyServlet",loadOnStartup = 6,asyncSupported = false, initParams = {@WebInitParam(name="brand",value = "asus"),@WebInitParam(name="screen",value = "京东方")}) <servlet> <display-name>ms</display-name> <servlet-name>MyServlet </servlet-name> <servlet-class>com.study.servlet.MyServlet</servlet-class> <init-param> <param-name>brand</param-name> <param-value>asus</param-value> </init-param> <init-param> <param-name>screen</param-name> <param-value>京东方</param-value> </init-param> <load-on-startup>6</load-on-startup> <async-supported>false</async-supported></servlet><servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/myServlet.do</url-pattern></servlet-mapping> @WebServlet(urlPatterns = "/myServlet .do",loadOnStartup = 6, initParams = {@WebInitParam(name="brand",value = "asus"),@WebInitParam(name="screen",value = "京东方")})public class MyServlet extends HttpServlet { public MyServlet () { System.out.println("MyServlet 构造方法"); } @Override public void init() throws ServletException { System.out.println("MyServlet inited"); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyServlet Service invoked"); ServletConfig servletConfig = this.getServletConfig(); System.out.println(servletConfig.getInitParameter("brand")); System.out.println(servletConfig.getInitParameter("screen")); }}

请求转发和请求重定向

定义

请求转发(forward):发生在服务端程序内部,当服务器端收到一个客户端的请求之后,会先将请求转发给目标地址,再将目标地址返回的结果转发给客户端, 而客户端对于这一切毫无感知的。在Servlet中,Servlet(源组件)先对客户请求做一些预处理操作,然后把请求转发给其它Servlet(目标组件)来完成包括生成响应结果在内的后续操作。

生活案例:张三(客户端)找李四(服务器端)借钱,而李四没钱,于是李四又去王五那借钱,并把钱借给了张三,整个过程中张三只借了一次款,剩下的事情都是李四完成的,这就是请求转发。

RequestDispatcher 接口

javax.servlet 包中定义了一个 RequestDispatcher 接口,RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源,利用 RequestDispatcher 对象可以把请求转发给其它的 Web 资源。

Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:

1.调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;
2.调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。

RequestDispatcher 接口中提供了以下方法。

返回类型方法描述
voidforward(ServletRequest request,ServletResponse response) 用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常
voidinclude(ServletRequest request,ServletResponse response) 用于将其他的资源作为当前响应内容包含进来

请求转发(forward)的工作原理

在 Servlet 中,通常使用 forward() 方法将当前请求转发给其他的 Web 资源进行处理。请求转发的工作原理如下图所示:

请求转发的分类

请求转发又分为forward转发和include转发。

实现方法:request.getRequestDispatcher (“接收请求的 Servlet 路径”). forward (request,response)

getRequestDispatcher (String path):该方法的返回值类型是 RequestDispatcher,请求发送器,该方法的参数是指明要接收请求的 Servlet 的路径;

forward (ServletRequest req,ServletResponse res):该方法是 RequestDispatcher 接口的方法,将请求从一个 Servlet 转发到服务器上的另一个资源(Servlet、JSP 文件或 HTML 文件)。此方法允许一个 Servlet 对请求进行初步处理,并使另一个资源生成响应,需要传递两个参数,这两个参数是当前 Servlet 的request 对象和response 对象传递过去的。

forward () 方法的处理流程:

● 清空用于存放响应正文(响应体)数据的缓冲区。

● 如果目标组件为 Servlet 或 JSP,就调用它们的 service () 方法,把该方法产生的响应结果发送到客户端,如果目标组件为文件系统中的静态 html 文档,就读去文档中的数据并把它发送到客户端。

● 由于 forward () 方法先清空用于存放响应正文数据的缓冲区,因此Servlet 源组件生成的响应结果不会被发送到客户端,只有目标组件生成的结果才会被发送到客户端。

● 如果源组件在进行请求转发之前,已经提交了响应结果(例如调用了 flush 或 close () 方法),那么 forward () 方法会抛出IllegalStateException。为了避免该异常,不应该在源组件中提交响应结果。

请求转发的特点
请求转发不支持跨域访问,只能跳转到当前应用中的资源。
请求转发之后,浏览器地址栏中的 URL 不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
参与请求转发的 Web 资源之间共享同一 request 对象和 response 对象。

include转发:Servlet(源组件)把其它 Servlet(目标组件)生成的响应结果包含到自身的响应结果中。

实现方式:request.getRequestDispatcher (“接收请求的 Servlet 路径”). include (request,response)

include (ServletRequest request,ServletResponse response):该方法是 RequestDispatcher 接口的方法,表示包含,它的参数同 forward () 方法的参数一样都是由当前 Servlet 传递过去的。

包含与转发相比,源组件与被包含的目标组件的输出数据都会被添加到响应结果中,在目标组件中对响应状态代码或者响应头所做的修改都会被忽略。

注意:当 Servlet 源组件调用 RequestDispatcher 的 forward 或 include 方法时,都要把当前的 ServletRequest 对象和 ServletResponse 对象作为参数传给 forward 或 include 方法,这就使得源组件和目标组件共享同一个 ServletRequest 对象和 ServletResponse 对象,就实现了多个 Servlet协同处理同一个请求。

附:RequestDispatcher 接口中定义了两个方法:forward () 方法和 include () 方法,它们分别用于将请求转发到 RequestDispatcher 对象封装的资源和将 RequestDispatcher 对象封装的资源作为当前响应内容的一部分包含进来.

请求转发(forward)和请求包含(include)的比较 

相同点:

请求转发和请求包含都是在处理一个相同的请求,多个 Servlet 之间使用同一个 request 对象和 response 对象。

不同点:

● 如果在 AServlet 中请求转发(forward)到BServlet,那么在 AServlet 中不允许再输出响应体,即不能使用 response.getWriter () 和 response.getOutputStream () 向客户端输出,这一工作交由 BServlet 来完成;如果是由 AServlet 请求包含(include)BServlet,则没有这个限制。

● forward转发不能设置响应体,但是可以设置响应头,例如:response.setContentType ("text/html;charset=utf-8”) 是可以留下来的;include转发不仅可以设置响应头,还可以设置响应体。

● 请求转发大多应用在 Servlet 中,转发目标大多是 jsp 页面;请求包含大多应用在 jsp 页面中,完成多页面的合并。一般情况下经常使用的是请求转发。

请求转发的应用 

● 在 Servlet 中向数据库获取数据,保存到 request 域中;

● 转发到 jsp 页面,jsp 从 request 域中获取数据,显示在页面上。

请求重定向的概念

请求重定向指的是服务器端接收到客户端的请求之后,会给客户端返回了一个临时响应头,这个临时响应头中记录了客户端需要再次发送请求(重定向)的 URL 地址,客户端再次收到了地址之后,会将请求发送到新的地址上,这就是请求重定向。响应重定向是通过HttpServletResponse对象sendRedirect(“路径”)的方式实现是,是服务器通知浏览器,让浏览器去自主请求其他资源的一种方式。

生活案例:张三(客户端)向李四(服务器端)借钱500元,李四说:“我也没有,你去王五那借”,然后张三根据李四的指示,去找王五借钱,这就是请求重定向。

重定向工作原理

 响应码为200表示响应成功,而响应码为302表示重定向,所以完成重定向的第一步就是设置响应码为302。

实现方式:

resp.setStatus(302);resp.setHeader("location","资源路径");

简化方式

resp.sendRedirect("资源路径");

重定向的运作流程如下:

1.用户在浏览器端输入特定URL,请求访问服务器端的某个Servlet。

2.服务器端的Servlet返回一个状态码为302的响应结果,该响应结果的含义为:让浏览器端再请求访问另一个Web资源,在响应结果中提供了另一个Web资源的URL。说明:另一个Web资源有可能在同一个Web服务器上,也有可能不再同一个Web服务器上。

3.当浏览器端接收到这种响应结果后,再立即自动请求访问另一个Web资源。

4.浏览器端接收到另一个Web资源的响应结果。

请求转发和重定向的区别

● 对于客户端浏览器来说,转发是一个请求,重定向是两个请求;

● 转发浏览器地址栏不变化,重定向会变成转发后的 URL ;

● 转发只能在一个项目内,而重定向没有限制,可以重定向到任意网(也就是跨域),如京东、淘宝等 ;

● 转发可以使用 request 域传递数据,而重定向不能。因为转发是一个请求,重定向是两个请求;

● 转发只有一个请求,原来是什么请求方式就是什么方式;而重定向两个请求,第一个可能为 post 可能为 get ,但是第二个请求一定是 get 。

区别转发重定向
浏览器地址栏URL是否发生改变
是否支持跨域跳转
请求与响应的次数一次请求和一次响应两次请求和两次响应
是否共享request对象和response对象
能否通过request域传递数据
速度相对要快相对要慢
行为类型服务器行为客户端行为

路径问题

项目结构

测试代码

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Title</title> <!--base标签可以简化相对路径,当使用相对路径时,默认会在相对路径之前补充base[href]中的内容--> <base href="http://127.0.0.1:8080/ServletModule5/"> <!-- 如果没有定义base标签,那么默认在相对路径之前补充的就是当前文件所在的路径 等价于<base href="http://127.0.0.1:8080/ServletModule5/a/a2/"> --></head><body> this is page a1 <br/> <!-- 相对(基准)路径:相对自己的路径,以当前文件所在的位置为基准位置,是以当前文件本身的位置去定位其他文件(既然是相对,就要有参照物,参照物就是当前文件位置) 绝对(基准)路径:以一个固定的路径作为定位文件的基准位置,也就是以一个固定的位置去定位其他文件,和文件本身位置无关 相对路径:不以"/"开头,就是相对路径, ../代表向上一层 绝对路径:以"/"开头 在页面上 "/"代表从项目的部署目录开始找 即从Servlet容器(Tomcat)的webapps目录开始找 --> <!--相对路径--><a href="a2.html" target="_self">相对路径跳转至a2</a> <br/><a href="../../b/b2/b1.html" target="_self">相对路径跳转至b1</a><br/> <!--base相对路径--><a href="a/a2/a2.html" target="_self">base相对路径跳转至a1</a> <br/><a href="b/b2/b1.html" target="_self">base相对路径跳转至b1</a><br/><!--绝对路径,页面设置的绝对路径要有项目名(这里为ServletModule5),除非我们的项目没有设置项目名--><a href="/ServletModule5/a/a2/a2.html" target="_self">绝对路径跳转至a2</a> <br/><a href="/ServletModule5/b/b2/b1.html" target="_self">绝对路径跳转至b1</a><br/></body></html>

请求转发路径问题

@WebServlet(urlPatterns = "/Servlet1.do")//@WebServlet(urlPatterns = "/c/c2/servlet1.do")public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /** * 请求转发的路径写法 * 相对基准路径:相对于当前Servlet本身的位置,urlPattern决定了位置 * 绝对基准路径:永远是以项目为基准路径(不允许跨服务,所以绝对路径只能是本服务内的资源) */ /*相对路径访问a1.html*/ //RequestDispatcher requestDispatcher = req.getRequestDispatcher("a/a2/a1.html"); /*urlpatterns会影响相对路径*/ //RequestDispatcher requestDispatcher = req.getRequestDispatcher("../../a/a2/a1.html"); /*绝对路径访问a1*/ RequestDispatcher requestDispatcher = req.getRequestDispatcher("/a/a2/a1.html"); requestDispatcher.forward(req,resp); }}

配置web.xml 注意url-pattern

请求转发路径总结:

1. 以"/"开头的路径是绝对路径,不以"/"开头是相对路径;

2. 绝对路径以当前项目名(或叫部署名)为根路径,"/"后不需要写当前项目名;

3. ../代表向上一层的路径;

4. Servlet的相对路径是相对于url-pattern中的路径,是虚拟的路径

请求重定向中的路径

//@WebServlet(urlPatterns = "/c/c2/servlet2.do")@WebServlet(urlPatterns = "/Servlet2.do")public class Servlet2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 请求重定向到a1.html /** * 相对路径 相对于urlPatterns定义的路径 * 绝对路径 :以项目部署路径为基准路径 (webapps) * 请求重定向的绝对路径中,要加项目部署名,除非当前项目没有给定部署名 * */ // resp.sendRedirect("../../a/a2/a1.html"); //resp.sendRedirect("a/a2/a1.html"); ServletContext servletContext = this.getServletContext(); String contextPath = servletContext.getContextPath();// /ServletModule5 resp.sendRedirect(contextPath+"/a/a2/a1.html"); }}

请求重定向路径总结:

1. 以"/"开头的路径是绝对路径,不以"/"开头是相对路径;

2. 绝对路径以当前项目所在目录(Tomcat的webapps下)为根路径,绝对路径后需要写当前项目部署名;

3. ../代表向上一层的路径;

4. Servlet的相对路径是相对于url-pattern中的路径,是虚拟的路径;

路径的使用和记忆建议:

建议在url-pattern中,不要书写没有必要的多层次路径如:"/c/c2" ,因为这会影响请求转发和重定向的相对路径写法;

绝对路径在书写时,只有请求转发不需要写项目部署名,页面(如超链接)上和重定向的绝对路径都需要写项目的部署名;

相对路径在使用时,无论是页面(如超链接)还是请求转发还是请求重定向都不需要项目名;

会话管理

Cookie对象与HttpSession对象简介

    Cookie对象与HttpSession对象的作用是维护客户端浏览器与服务端的会话状态的两个对象。由于HTTP协议是一个种无状态的协议,WEB服务器本身不能识别出哪些请求是同一个浏览器发出的,所以服务端并不会记录当前客户端浏览器的访问状态,浏览器的每一次请求都是完全孤立的。但是在有些时候我们是需要服务端能够记录客户端浏览器的访问状态的,如获取当前客户端浏览器的访问服务端的次数时就需要会话状态的维持。在Servlet中提供了Cookie对象与HttpSession对象用于维护客户端与服务端的会话状态的维持。二者不同的是:Cookie是通过客户端浏览器实现会话的维持,而HttpSession是通过服务端来实现会话状态的维持。

如何记录用户状态信息原理,举例:银行卡

 会话管理:Http协议本身不具备直接记录用户状态的功能,JavaEE标准下给我们提供了Cookie和HttpSession技术帮助我们记录用户的状态,

Cookie就是保存少量文本数据在用户端的有一种技术,HttpSession是保存更多数据在服务端的一种技术;Cookie对应这里的银行卡,HttpSession对应银行账户。

有了Cookie和HttpSession可以:

1. 记录用户是否登录

2. 用于用户权限的控制

3. 统计在线人数...

Cookie

Cookie是一种保存少量信息至浏览器的一种技术,第一次请求时,服务器可以响应给浏览器一些Cookie信息,第二次请求,浏览器会携带之前的cookie发送给服务器,通过这种机制可以实现在浏览器端保留一些用户信息,为服务端获取用户状态获得依据。

Cookie对象的特点

Cookie使用字符串存储数据

Cookie使用key与value结构存储数据

单个Cookie存储数据大小限制在4097个字节

Cookie存储的数据中不支持中文,Servlet4.0中支持

Cookie是与域名绑定所以不支持跨一级域名访问

Cookie对象保存在客户端浏览器内存上或系统磁盘中

Cookie分为持久化Cookie(保存在磁盘上)与状态Cookie(保存在内存上)

浏览器在保存同一域名所返回Cookie的数量是有限的,不同浏览器支持的数量不同,Chrome浏览器为50个

浏览器每次请求时都会把与当前访问的域名相关的Cookie在请求中提交到服务端。

Cookie对象的创建

Cookie cookie = new Cookie("key","value"),通过new关键字创建Cookie对象。

resp.addCookie(cookie),通过HttpServletResponse对象将Cookie写回给客户端浏览器。

Cookie中数据的获取
通过HttpServletRequest对象获取Cookie,返回Cookie数组。

Cookie[] cookies = req.getCookies()

Cookie不支持中文解决方案

在Servlet4.0中的Cookie是支持中文存储的,在Servlet4.0版本之前的Cookie中是不支持中文存储的,如果存储的数据中含有中文,代码会直接出现异常。

java.lang.IllegalArgumentException:   Control character in cookie value or attribute.

我们可以通过对含有中文的数据重新进行编码来解决该问题,可以使用对中文进行转码处理

将内容按照指定的编码方式做URL编码处理:URLEncoder.encode("content","code")

将内容按照指定的编码方式做URL解码处理:URLDecoder.decode("content","code")

持久化Cookie和状态Cookie

状态Cookie:浏览器会缓存Cookie对象,浏览器关闭后Cookie对象销毁。

持久化Cookie:浏览器会对Cookie做持久化处理,基于文件形式保存在系统的指定目录中。在Windows10系统中为了安全问题不会显示Cookie中的内容。

当Cookie对象创建后默认为状态Cookie,可以使用Cookie对象下的cookie.setMaxAge(60)方法设置失效时间,单位为秒。一但设置了失效时间,那么该Cookie为持久化Cookie,浏览器会将Cookie对象持久化到磁盘中,当失效时间到达后文件删除。

Cookie对象总结

Cookie对于存储内容是基于明文的方式存储的,所以安全性很低,不要在Cookie中存放敏感数据,在数据存储时,虽然在Servlet4.0中Cookie支持中文,但是,建议对Cookie中存放的内容做编码处理,也可提高安全性。第一次请求一个服务的时候,浏览器的请求报文中(请求头)是没有携带Cookie相关数据的,如果这次请求从当前的服务中(响应头)获得了Cookie之后,只要请求当前项目中的任何资源,按理论来说,它应该会携带Cookie。

HttpSession

    HttpSession是一种保存少量信息至服务器端的一种技术,第一请求时,服务器会创建HttpSession对象,我们可以在HttpSession对象中保存一些关于用户的状态信息,并将HttpSession的JSESSIONID以Cookie形式响应给浏览器,第二次请求,浏览器会携带之前的JSESSIONID的Cookie,发送给服务器,服务器根据JSESSIONID获取对应的HttpSession对象,通过这种技术可以解决HTTP协议本身无法记录用户状态的问题。

 

HttpSession对象的特点

HttpSession保存在服务端

HttpSession可以存储任何类型的数据

HttpSession使用key与value结构存储数据 value是Object类型

HttpSession存储数据大小无限制

HttpSession对象的创建

    HttpSession对象是通过request.getSession()方法来创建的。客户端浏览器在请求服务端资源时,如果在请求中没有JSESSIONID,getSession()方法将会为这个客户端浏览器创建一个新的HttpSession对象,并为这个HttpSession对象生成一个JSESSIONID,在响应中通过Cookie写回给客户端浏览器,如果在请求中包含了JSESSIONID,getSession()方法则根据这个ID返回与这个客户端浏览器对应的HttpSession对象。getSession()方法还有一个重载方法getSession(true|false),当参数为true时与getSession()方法作用相同。当参数为false时,只会根据JSESSIONID查找是否有与这个客户端浏览器对应的HttpSession,如果有则返回,如果没有,则不会创建新的HttpSession对象。

HttpSession中数据的获取

将数据存储到HttpSession对象中:session.setAttribute("key",value)

根据key获取HttpSession中的数据,返回Object:Object value = session.getAttribute("key")

获取HttpSession中所有的key,返回枚举类型:Enumeration<String> attributeNames = session.getAttributeNames()

根据key删除HttpSession中的数据:session.removeAttribute("key")

获取当前HttpSession的JSESSIONID,返回字符串类型:String id = session.getId()

测试代码:

Servlet1创建HttpSession对象,并实现存储数据

@WebServlet("/Servlet1.do")public class Servlet1 extends HttpServlet { /** * getSession方法执行内容:从req中尝试获取JSESSIONID的Cookie * 如果获取失败,则认为上次会话已经结束,在这里要开启新的会话,创建一个新的HttpSession对象, * 并将新的HttpSession对象的JSESSIONID以Cookie的形式放到Response对象中,响应给浏览器 * * 如果获取成功,则根据JSESSIONID在服务器内找对应HttpSession对象,这里有两种情况: * 1) 找到了,返回找到的HttpSession对象 * 2) 没找到,创建新的HttpSession对象并将JSESSIONID以Cookie的形式放到Response对象中,响应给浏览器 * */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // HttpSession是一种保存更多数据在服务器端的一种技术,一般保存当前登录的用户信息,例如:用户的权限、用户的其他信息... // 获得HttpSession对象 HttpSession httpSession=req.getSession(); // 向HttpSession中存放一些数据 httpSession.setAttribute("username", "zhansan"); httpSession.setAttribute("password", "123"); httpSession.setAttribute("level", "A"); // 手动设置HttpSession不可用-------使用场景:例如,退出登录 //httpSession.invalidate(); }}

Servlet2 获取Servlet1创建的HttpSession对象并获取数据和session的其他信息

@WebServlet("/Servlet2.do")public class Servlet2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取HttpSession HttpSession session = req.getSession(); // 尝试从HttpSession中获取数据 String username = (String)session.getAttribute("username"); String password = (String)session.getAttribute("password"); String level = (String)session.getAttribute("level"); System.out.println("username:" + username + ",password:" + password + ",level:" + level); // 获取Session对象的其他信息 System.out.println("创建时间:"+session.getCreationTime()); System.out.println("最后一次访问时间:"+session.getLastAccessedTime()); System.out.println("最大不活动时间:"+session.getMaxInactiveInterval()); System.out.println("JSESSIONID:" + session.getId()); Enumeration<String> attributeNames = session.getAttributeNames(); while(attributeNames.hasMoreElements()){ System.out.println(attributeNames.nextElement()); } }}

HttpSession的销毁方式

HttpSession的销毁方式有两种:

1. 通过web.xml文件指定超时时间(最大不活动时间);

2. 通过HttpSession对象中的invalidate()方法销毁当前HttpSession对象;

   我们可以在web.xml文件中指定HttpSession的超时时间,当到达指定的超时时间后,容器就会销毁该HttpSession对象,单位为分钟。该时间对整个web项目中的所有HttpSession对象有效。时间的计算方式是根据最后一次请求时间作为起始时间,如果有哪个客户端浏览器对应的HttpSession的失效时间已到,那么与该客户端浏览器对应的HttpSession对象就会被销毁,其他客户端浏览器对应的HttpSession对象会继续保存不会被销毁。

<session-config> <session-timeout>1</session-timeout></session-config>

   我们也可以在Tomcat的web.xml文件中配置HttpSession的销毁时间,如果在Tomcat的web.xml文件中配置了HttpSession的超时时间,对应的Tomcat中所有的Web项目都有效,相当于配置了全局的HttpSession超时时间,Tomcat的web.xml文件中配置如下:

如果我们在Web项目中配置了超时时间,那么会以Web项目中的超时时间为准。invalidate()方法是HttpSession中所提供的用于销毁当前HttpSession对象的方法,通过调用该方法可以销毁当前HttpSession对象。

HttpSession生命周期

   在HttpSession对象生命周期中没有固定的创建时间与销毁时间,何时创建取决于我们什么时候第一次调用了getSession()或getSession(true)的方法;HttpSession对象的销毁时间取决于超时时间的到达以及调用了invalidate()方法,如果没有超时或者没有调用invalidate()方法,那么HttpSession会一直存储,默认超时时间为30分钟(Tomcat的web.xml文件配置的时间就是默认超时时间)。

HttpSession与Cookie的区别

● cookie数据存放在客户的浏览器或系统的文件中,而HttpSession中的数据存放在服务器中

● cookie不安全,而HttpSession是安全的

● 单个cookie保存的数据不能超过4K,很多浏览器都限制一个域名保存cookie的数量,而HttpSession没有容量以及数量的限制

HttpSession的使用建议

HttpSession对象是保存在服务端的,所以安全性较高,我们可以在HttpSession对象中存储数据,但是由于HttpSession对象的生命周期不固定,所以不建议存放业务数据,一般情况下我们只是存放用户登录信息。

总结

什么是同一次会话?

WEB应用中的会话是指一个客户端浏览器与WEB服务器之间连续发生的一系列请求和响应过程。而同一次会话是指客户端浏览器与WEB服务器之间多次的请求和响应过程中,客户端一直使用同一个JSESSIONID,服务端一直使用同一个HttpSession对象,两者之间保持着一一对应的关系。

哪些情况会结束会话?

1. 浏览器没有携带JSESSIONID,例如:浏览器关闭、手动清除;

2. 服务端丢失HttpSession,例如:服务器重启、到达最大不活动时间(指请求和请求之间的时间间隔,或者是指获取HttpSession之间的时间间隔),默认30min、手动销毁HttpSession;

Servlet三大域对象

 什么是域对象? 域对象主要用在web应用中,能够存储数据,获取数据,传递数据,通俗的讲就是这个对象本身可以存储一定范围内的所有数据,通过它就能获取和存储数据,可以理解为万能的一个属性,只要调用它就可以获得这个范围(域)内的想要的数据,也可以修改删除数据,当然也可以给这个域添加数据。

在JavaWeb中,Servlet中三大域对象分别是:request、session、application,其主要是用来存放共享数据的。三大作用域的使用,其本质是根据作用域的范围以及生命周期决定其使用的方式:

● request:每一次请求都是一个新的request对象,如果在Web组件之间需要共享同一个请求中的数据,只能使用请求转发

● session:每一次会话都是一个新的session对象,如果需要在一次会话中的多个请求之间需要共享数据,只能使用session

● application:应用对象,Tomcat 启动到关闭,表示一个应用,在一个应用中有且只有一个application对象,作用于整个Web应用,可以实现多次会话之间的数据共享

对象名称对象类型有效范围
requestHttpServletRequest在一个服务器请求范围内有效(一次请求/请求转发)
sessionHttpSession在一次会话范围有效(可以跨请求)
applicationServletContext在一个应用服务器范围内有效(任意一次请求和会话,可以跨会话)

之所以它们是域对象,是因为它们都内置了map集合,都有setAttribute和getAttribute方法

作用域对象共享数据相关的方法

设置作用域中的共享数据:作用域对象.setAttribute(String name,Object value)

获取作用域中的共享数据:Object value = 作用域对象.getAttribute(String name)

删除作用域中指定的共享数据:作用域对象.removeAttribute(String name)

注意:在哪个作用域中设置共享数据,就只能从该作用域中取出数据。

Request域

Request域对象的有效范围及数据传递

    一次请求内有效,请求转发时数据可以传递,除此之外该域没有办法实现数据共享;ServletRequest对象提供了一个getRequestDispatcher方法,该方法返回一个RequestDispatcher对象,调用这个对象的forward方法可以实现请求转发,从而共享请求中的数据。

生命周期

● 创建:客户端向服务器发送一次请求,服务器就会创建request对象

● 使用:仅在当前请求中有效,如果web组件之间需要共享同一个请求中的数据,只能使用请求转发

● 销毁:服务器对这次请求作出响应后就会销毁request对象

总结:Request域对象,是建议使用,并被频繁使用的域对象,因为它生命周期比较短,也就代表着它效率比较高,释放资源比较及时。

测试代码

向request域中放入数据

/** * 向request域中放入数据 */@WebServlet("/request/Servlet1.do")public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //向request域中添加数据 List<String> list = new ArrayList<>(); Collections.addAll(list,"a","b","c"); req.setAttribute("list",list); req.setAttribute("name","Carl"); req.setAttribute("gender","男"); req.setAttribute("age",18); //请求转发 req.getRequestDispatcher("Servlet2.do").forward(req,resp); // 重定向不能将request域中的数据传递给Servlet2 //resp.sendRedirect("Servlet2.do"); }}

request域中读取数据

/** * 从request域中读取数据 */@WebServlet("/request/Servlet2.do")public class Servlet2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //从request域中移除数据 req.removeAttribute("gender"); // 从request域中读取数据 List<String> list = (List<String> )req.getAttribute("list"); System.out.println(list); System.out.println((String) req.getAttribute("name")); System.out.println((Integer) req.getAttribute("age")); System.out.println((String) req.getAttribute("gender")); //如果Request中有请求参数,获取Request中的请求参数值 //System.out.println(req.getParameter("参数名")); }}

Session域

有效范围:单次会话内有效,可以跨多个请求

生命周期

创建:服务器第一次调用getSession()方法(保存在服务器内存中)

使用:本次会话之内,浏览器和服务器之间多次请求和响应有效

销毁:会话结束,如浏览器失去JSESSIONID、到达最大不活动时间(当一段时间内session没有被使用,在Tomcat中配置的默认为30分钟)、手动清除(invalidate方法)、服务器非正常关闭,没有到期的session也会跟着销毁

Session的作用范围

HttpSession 在服务器中,为浏览器创建独一无二的内存空间,在其中保存会话相关的信息。服务器正常关闭,再启动,session对象会进行钝化和活化操作,同时如果服务器钝化的时间在session 默认销毁时间之内, 则活化后session还是存在的,否则session不存在。 如果JavaBean 数据在session钝化时,没有实现Serializable ,当session活化时,会消失。

在同一服务器上不同的request请求是会得到唯一的session

Session生成时机:request对象调用getSession方法时生成,服务器会为该Session对象生成一个唯一的ID,服务器端响应客户端请求时会在报文头中设置Set-Cookie属性,该属性内容中有一个JSESSIONID即是session对象的标识,返回后由浏览器进行处理。客户端再次发送请求时,浏览器会在报文头中自动追加Cookie属性,该属性将JSESSIONID传到服务器端,在服务器端用request.getSession时会取得JSESSIONID对应的对象而不会重新生成Session。

监听HttpSession的利器HttpSessionListener

Session创建事件发生在每次,一个新的Session创建的时候,类似地Session失效事件发生在每次,一个Session失效的时候。比如在统计网站在线人数的这个场景下,可以使用HttpSessionListenner进行监听。

//这个接口也只包含两个方法,分别对应于Session的创建和失效public interface HttpSessionListener extends EventListener { default void sessionCreated(HttpSessionEvent se) {} default void sessionDestroyed(HttpSessionEvent se) {}}

Session何时创建

并不是一打开网页就创建了session对象。对于Servlet的请求,只有当Servlet内部调用了如下代码,才会生成session

HttpSession session = req.getSession();//或者HttpSession session = req.getSession(true);

如果写成如下,则不会创建session

HttpSession session = req.getSession(false);

假如我们访问的是jsp页面,因为Jsp页面内置了session对象,封装了调用session的代码,则一打开jsp页面会创建session。

测试代码

向session域中放入数据

/** * 向session域中放入数据 */@WebServlet("/session/Servlet1.do")public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); //向session域中添加数据 List<String> list = new ArrayList<>(); Collections.addAll(list,"a","b","c"); session.setAttribute("list",list); session.setAttribute("name","Carl"); session.setAttribute("gender","男"); session.setAttribute("age",18); resp.sendRedirect("Servlet2.do"); }}

从session域中读取数据

/** * 从session域中读取数据 */@WebServlet("/session/Servlet2.do")public class Servlet2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); // 从session域中读取数据 List<String> list = (List<String> )session.getAttribute("list"); System.out.println(list); System.out.println((String) session.getAttribute("name")); System.out.println((Integer) session.getAttribute("age")); System.out.println((String) session.getAttribute("gender")); }}

Application域

有效范围:当前web服务内,跨请求,跨会话

生命周期

创建:项目启动,服务器为每个Web应用创建一个属于该Web项目的对象ServletContext类

使用:项目运行任何时间有效

销毁:项目关闭

测试代码

向Application域中放入数据

/** * 向application域中放入数据 */@WebServlet("/application/Servlet1.do")public class Servlet1 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext application = req.getServletContext(); //向application域中添加数据 List<String> list = new ArrayList<>(); Collections.addAll(list,"a","b","c"); application.setAttribute("list",list); application.setAttribute("name","Carl"); application.setAttribute("gender","男"); application.setAttribute("age",18); }}

从Application域中读取数据

/** * 从application域中读取数据 */@WebServlet("/application/Servlet2.do")public class Servlet2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext application = this.getServletContext(); // 从application域中读取数据 List<String> list = (List<String> )application.getAttribute("list"); System.out.println(list); System.out.println((String) application.getAttribute("name")); System.out.println((Integer) application.getAttribute("age")); System.out.println((String) application.getAttribute("gender")); }}

相关推荐

相关文章