一、Servlet基础
1.Servlet基础知识
- Servlet的作用:为Java程序提供一个统一的web应用的规范
- 应用容器可以使用提供的规范来实现自己的特性,如tomcat的代码和jetty的代码就不一样,但作为程序员只需了解servlet规范就可以从request中取值,可以操作session等等
- 不用在意应用服务器底层的实现的差别而影响开发
- HTTP 协议:只是一个规范,定义服务请求和响应的大致式样
- Java servlet 类 将HTTP中那些低层的结构包装在 Java 类中,这些类所包含的便利方法使其在 Java 语言环境中更易于处理
- 当用户通过 URL 发出一个请求时,这些 Java servlet 类就将之转换成一个 HttpServletRequest,并发送给 URL 所指向的目标
- 当服务器端完成其工作时,Java 运行时环境(Java Runtime Environment)就将结果包装在一个 HttpServletResponse 中,然后将原 HTTP 响应送回给发出该请求的客户机
- 在与 Web 应用程序进行交互时,通常会发出多个请求并获得多个响应。所有这些都是在一个会话语境中,Java 语言将之包装在一个 HttpSession 对象中。在处理响应时,您可以访问该对象,并在创建响应时向其添加事件。它提供了一些跨请求的语境
2.容器
- 容器(如 Tomcat):为 servlet 管理运行时环境,可以通过容器的配置文件配置该容器,定制 J2EE 服务器的工作方式,以便将 servlet 暴露给外部世界
- 通过该容器中的各种配置文件,您在 URL(由用户在浏览器中输入)与服务器端组件之间搭建了一座桥梁,这些组件将处理您需要该 URL 转换的请求
- 在运行应用程序时,该容器将加载并初始化 servlet,管理其生命周期
3. Servlet详解
- Servlet的框架是由两个Java包组成的:
- javax.servlet:定义了所有的Servlet类都必须实现或者扩展的通用接口和类
- javax.servlet.http:定义了采用Http协议通信的HttpServlet类
- Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这个接口
- javax.servlet.Servlet接口中定义的5个方法:
- init(ServletConfig)方法:负责初始化Servlet对象,在Servlet的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题
- service(ServletRequest req,ServletResponse res)方法:负责响应客户的请求;为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性
- destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源
- getServletInfo:返回Servlet的描述
- getServletConfig:返回由Servlet容器传给init方法的ServletConfig
1)ServletRequest & ServletResponse
- 对于每一个HTTP请求,servlet容器会创建一个封装了HTTP请求的ServletRequest实例传递给servlet的service方法
- ServletResponse则表示一个Servlet响应,其隐藏了将响应发给浏览器的复杂性。通过ServletRequest的方法你可以获取一些请求相关的参数,而ServletResponse则可以将设置一些返回参数信息,并且设置返回内容
2)ServletConfig
- ServletConfig封装可以通过@WebServlet或者web.xml传递一个Servlet的配置信息,以这种方式传递的每一条信息都称做初始化信息,初始化信息就是一个个K-V键值对
- 为从一个Servlet内部获取某个初始参数的值,init方法中调用ServletConfig的getinitParameter方法或getinitParameterNames方法获取,除此之外,还可以通过getServletContext获取ServletContext对象
3)ServletContext
- ServletContext是代表了Servlet应用程序,每个Web应用程序只有一个context
- 在分布式环境中,一个应用程序同时部署到多个容器中,并且每台Java虚拟机都有一个ServletContext对象
- 有了ServletContext对象后,就可以共享能通过应用程序的所有资源访问的信息,促进Web对象的动态注册,共享的信息 通过 一个内部Map中的对象 保存在ServiceContext中来实现,保存在ServletContext中的对象称作属性
4)GenericServlet
- 前面编写的Servlet应用中 通过实现Servlet接口来编写Servlet,但是我们每次都必须为Servlet中的所有方法都提供实现,还需要将ServletConfig对象保存到一个类级别的变量中
- GenericServlet抽象类就是为了为我们省略一些模板代码,实现了Servlet和ServletConfig,完成了以下几个工作:
- 将init方法中的ServletConfig赋给一个类级变量,使其可以通过getServletConfig来获取
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }
为避免覆盖:子类调用init方法是必须调用super.init(servletConfig),但是GenericServlet还提供了一个不带参数的init方法
当ServletConfig赋值完成就不会再调用带参数的init方法,而调用不带参数的init方法,这样子类就可以通过覆盖不带参数的init方法编写初始化代码,而ServletConfig实例依然得以保存
为Servlet接口中的所有方法提供默认实现
提供方法来包装ServletConfig中的方法
5)HTTPServlet
- 在编写Servlet应用程序时,大多数都要用到HTTP,也就是说可以利用HTTP提供的特性,javax.servlet.http包含了编写Servlet应用程序的类和接口,其中很多覆盖了
javax.servlet中的类型,我们自己在编写应用时大多时候也是继承的HttpServlet
二、Servlet工作原理
- 当Web服务器接收到一个HTTP请求时:
- 它会先判断请求内容——如果是静态网页数据,Web服务器将会自行处理,然后产生响应信息
- 如果牵涉到动态数据,Web服务器会将请求转交给Servlet容器。此时Servlet容器会找到对应的处理该请求的Servlet实例来处理,结果会送回Web服务器,再由Web服务器传回用户端。
- 针对同一个Servlet
- Servlet容器会在第一次收到http请求时建立一个Servlet实例,然后启动一个线程
- 第二次收到http请求时,Servlet容器无须建立相同的Servlet实例,而是启动第二个线程来服务客户端请求。所以多线程方式不但可以提高Web应用程序的执行效率,也可以降低Web服务器的系统负担
Tomcat与Servlet工作原理时序图:
Tomcat与Servlet工作原理时序图
时序图解释:
Web Client 向Servlet容器(Tomcat)发出Http请求;
Servlet容器接收Web Client的请求;
Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中;
Servlet容器创建一个HttpResponse对象;
Servlet容器 调用 HttpServlet对象 的 service方法:把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet对象;
HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息;
HttpServlet调用HttpResponse对象的有关方法,生成响应数据;
Servlet容器把HttpServlet的响应结果传给Web Client;
三、Servlet生命周期
Servlet类加载--->实例化--->服务--->销毁
Servlet生命周期
在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次。当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务
1.在Servlet接口中定义了5个方法,其中3个方法代表了Servlet的生命周期:
- init(ServletConfig)方法:负责初始化Servlet对象,在Servlet的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题
- service(ServletRequest req,ServletResponse res)方法:负责响应客户的请求;为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性
- destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源
编程注意事项说明:
- 当Server Thread线程执行Servlet实例的init()方法时,所有的Client Service Thread线程都不能执行该实例的service()方法,更没有线程能够执行该实例的destroy()方法,因此Servlet的init()方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题。
- 当服务器接收到来自客户端的多个请求时,服务器会在单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。
- 虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下:
如果service()方法没有访问Servlet的成员变量,也没有访问全局资源,如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制
如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制
如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句
如果service()方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句
如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句
在创建一个 Java servlet 时,一般需要子类 HttpServlet。该类中的方法允许您访问请求和响应包装器(wrapper),您可以用这个包装器来处理请求和创建响应
2.创建Servlet对象的时机:
- 默认情况下:在Servlet容器启动后:客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servlet对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet对象的service方法
- Servlet容器启动时:如果 web.xml文件中 元素中 指定了子元素时,Servlet容器在启动web服务器时,将按照顺序创建并初始化Servlet对象
- 如果需要让Servlet容器在启动时即加载Servlet,可以在web.xml文件中配置元素
- Servlet的类文件被更新后,重新创建Servlet:Servlet容器在启动时自动创建Servlet,这是由在web.xml文件中为Servlet设置的属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在
注意:在web.xml文件中,某些Servlet只有servlet元素,没有servlet-mapping元素,这样我们无法通过url的方式访问这些Servlet,这种Servlet通常会在元素中配置一个子元素,让容器在启动的时候自动加载这些Servlet并调用init(ServletConfig config)方法来初始化该Servlet。其中方法参数config中包含了Servlet的配置信息,比如初始化参数,该对象由服务器创建
- <servlet-mapping>元素:在Servlet和URL样式之间定义一个映射,它包含了两个子元素<servlet- name>和<url-pattern>
- <servlet-name>元素给出的Servlet名字必须是 在<servlet>元素中声明过的Servlet的名字
- <url-pattern>元素指定 对应于Servlet的URL路径,该路径是相对于Web应用程序上下文根的路径
3.销毁Servlet对象的时机:
- Servlet容器停止或者重新启动:Servlet容器调用Servlet对象的destroy方法来释放资源
4.Servlet容器如何知道创建哪一个Servlet对象?Servlet对象如何配置?实际上这些信息是通过读取web.xml配置文件来实现:
- 当Servlet容器启动的时候读取配置节信息,根据配置节信息创建Servlet对象,同时根据配置节信息创建HttpServletConfig对象,然后执行Servlet对象的init方法,并且根据配置节信息来决定创建Servlet对象的顺序
- 如果此配置节信息为负数或者没有配置,那么在Servlet容器启动时,将不加载此Servlet对象
- 当客户访问Servlet容器时,Servlet容器根据客户访问的URL地址,通过配置节中的配置节信息找到指定的Servlet对象,并调用此Servlet对象的service方法
<servlet>
<!-- Servlet对象的名称 -->
<servlet-name>action<servlet-name>
<!-- 创建Servlet对象所要调用的类 -->
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<!-- 参数名称 -->
<param-name>config</param-name>
<!-- 参数值 -->
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<!-- Servlet容器启动时加载Servlet对象的顺序 -->
<load-on-startup>2</load-on-startup>
</servlet>
<!-- 要与servlet中的servlet-name配置节内容对应 -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<!-- 客户访问的Servlet的相对URL路径 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
四、Servlet中的Listener
- Listener是基于观察者模式设计的,Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个纵向维度控制程序和数据
- 目前 Servlet 中提供了 5 种 两类事件的观察者接口
- 4 个 EventListeners 类型的:ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener
- 2 个 LifecycleListeners 类型的:ServletContextListener、HttpSessionListener
- Listener基本上涵盖了整个 Servlet 生命周期,你感兴趣的每种事件
- Listener 的实现类可以配置在 web.xml 中的 标签中,也可以在应用程序中动态添加 Listener,需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所监听的事件已经不会再出现
五、Cookie与Session
- cookie可以被客户端禁用,session不可以
- cookie只能保存string类型的数据,session可以保存任意类型的数据
- cookie储存在客户端,session储存在服务器端
Servlet 能够给我们提供两部分数据:
- 在 Servlet 初始化时调用 init 方法时设置的 ServletConfig类,这个类基本上含有了 Servlet 本身和 Servlet 所运行的 Servlet 容器中的基本信息
- 还有一部分数据是由 ServletRequest 类提供,从提供的方法中发现 主要是描述这次请求的 HTTP 协议的信息(关于这一块还有一个让很多人迷惑的 Session 与 Cookie)
Session 与 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态,它们有各自的优点也有各自的缺陷。然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的:
- 使用 Cookie 来传递信息时,随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也也会越来越大。所以大访问量的时候希望用 Session,但是 Session 的致命弱点是不容易在多台服务器之间共享,所以这也限制了 Session 的使用
1.Session 如何基于 Cookie 来工作
- 基于 URL Path Parameter,默认就支持
- 基于 Cookie,如果你没有修改 Context 容器 cookies 标识的话,默认也是支持的
- 基于 SSL,默认不支持,只有 connector.getAttribute("SSLEnabled") 为 TRUE 时才支持
1)第一种情况下,当浏览器不支持 Cookie 功能时,浏览器会将用户的 SessionCookieName 重写到用户请求的 URL 参数中,它的传递格式如下:
/path/Servlet?name=value&name2=value2&JSESSIONID=value3
接着 Request 根据这个 JSESSIONID 参数拿到 Session ID 并设置到 request.setRequestedSessionId 中
2)如果是第三种情况的话将会根据 javax.servlet.request.ssl_session 属性值设置 Session ID
- 有了 Session ID 服务器端就可以创建 HttpSession 对象了,第一次触发是通过 request. getSession() 方法,如果当前的 Session ID 还没有对应的 HttpSession 对象那么就创建一个新的,并将这个对象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 类将管理所有 Session 的生命周期,Session 过期将被回收,服务器关闭,Session 将被序列化到磁盘等。只要这个 HttpSession 对象存在,用户就可以根据 Session ID 来获取到这个对象,也就达到了状态的保持
Session相关类图
下图是 Session 工作的时序图:
从图中可以看出从 request.getSession 中获取的 HttpSession 对象实际上是 StandardSession 对象的门面对象,这与前面的 Request 和 Servlet 是一样的原理。
与 Session 关联的 Cookie 与其它 Cookie 没有什么不同,这个的配置可以通过 web.xml 中的 session-config 配置项来指定