1.Servlet介绍
Servlet作为java应用的一个部件,就是一个java对象。
Servlet的作用主要是用来接收例如http协议等的客户端请求,通过调用业务接口或者数据库相关接口后把得到的数据响应给客户端。从架构来看出于客户端和数据库之间的层。
2.创建Servlet
创建Servlet有3种,分别是:
1)实现Servlet接口,要求实现接口的所有方法
2)继承GenericServlet类,需要实现service方法
3)继承HttpServlet类,需要根据需求实现doXXX方法
最常用的是第3种方法。
3.Servlet的生命周期
1)读取Servlet类,创建Servlet实例。
大部分时候是客户端发起相应的url请求后动态创建,也可以通过配置设置成服务启动时加载。
2)调用Servlet的init方法初始化Servlet实例。
每个Servlet生命周期只会调用一次。
3)每次客户端请求都会触发Servlet实例的service方法。
服务器每次接到请求后都会开启一个线程并调用服务。在service方法中会去根据请求类型调用doXXX方法。
4)实例销毁会调用destroy方法。
该方法每个servlet实例只会执行一次,主要用来执行相关清理工作,例如停止后台线程,关闭数据库连接等。
4.获取请求参数
servlet会根据请求参数的类型自动解析并处理好数据,通过doXXX方法中的类型为HttpServletRequest的参数获取,相关方法如下:
1)getParameter() 主要用来获取单个参数
2)getParameterValues() 用来返回多数据的参数,例如复选框
3)getParameterNames() 主要用来获取当前请求的所有参数的参数名。
中文的处理
1)客户端要保证请求数据的编码和后端处理的一致。
可以在页面上设置好字符集
2)获取中文字符串参数可以使用如下方式转码:
String name =new String(request.getParameter("name").getBytes("ISO-8859-1"),"UTF-8");
3)响应头要设置好字符集:
response.setContentType("text/html;charset=UTF-8");
转发和重定向
1)重定向是客户端行为,让浏览器自己重新请求一个新的地址,是一个新的servlet生命周期;
转发是服务端行为,当前请求地址不变,服务端调用其他资源返回给了客户端,还是处在当前生命周期内。
2)servlet的转发调用方式:
request.getRequestDispatcher("/jsp/index.jsp").forward(request, response);
3)servlet的重定向:
response.sendRedirect(String path)
servlet处理多个请求
1)首先要明白这个需求的背景:
1.1 开始的时候一个地址会配置一个servlet,随着业务逻辑扩展需要很多种请求,这样下去就会配置很多个servlet,会造成资源浪费。
1.2 每次请求的的线程是由服务端容器(tomcat等)统一管理(线程池),被调度的线程会去调用servlet实例的方法,执行完毕thread会重新放回线程池,即使同一个servlet的多个方法同时执行,也是在各自的线程内,互不影响,因此完全没有必要加载多个servlet,可以把servlet看做一个策略仓库,不同的业务请求调不同的策略方法就可以了。
2)servlet中处理多个请求的方式:
2.1 写好一个工具方法,在doXXX方法中调用工具方法获取请求的业务标识(可以通过自定义请求url的规则)
2.2 处理调用不同的方法的具体写法:
1) 可以直接用if-else去做判断
2) 也可以利用反射机制,根据业务标识拿到方法名然后去调用
// 获取请求的URI地址信息
String url = request.getRequestURI();
// 截取其中的方法名
String methodName = url.substring(url.lastIndexOf("/")+1, url.lastIndexOf("."));
Method method = null;
try {
// 使用反射机制获取在本类中声明了的方法
method = getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 执行方法
method.invoke(this, request, response);
} catch (Exception e) {
throw new RuntimeException("调用方法出错!");
}
线程安全问题
了解了上述用法,下面说下线程安全问题。因为servlet是一个单例被多个线程共享,如果多线程并发调用的方法使用了实例级别的属性,就容易出现数据混乱的问题。
因此应该避免使用实例属性,应该把业务参数控制在各自的请求参数里,即使有变量也是方法内的变量,多线程互不影响。
Servlet上下文
ServletContext(Servlet上下文)是当前web服务所有工作组线程共享的一个存储空间,随着web服务启动而自动创建,web服务停止自动销毁。
Servlet状态管理
首先要知道http每次请求,对应的HttpRequest对象和HttpResponse对象都会重新创建,之前请求的数据无从获取,也就是常说的http请求是无状态的。
因此一个典型的需求就是,服务端需要知道当前是否是同一个用户的访问,以及获取相关用户的信息,这就是状态管理的问题。
Servlet的状态管理主要有2种技术:cookie和session
cookie技术和session技术的一个显著的区别就是:
cookie信息保存在客户端,session信息保存在服务端。
cookie
服务端临时存放少量信息到客户端,用以跟踪用户状态。
工作原理:
1)浏览器向服务端发起请求。
2)服务端将少量数据以set-cookie消息头的形式发送给浏览器,浏览器将这些数据保存起来。
3)浏览器再次请求相应路径的时候会将之前保存的cookie信息以cookie消息头的形式发送给服务端
4)服务端获取cookie信息
cookie相关操作:
// 创建cookie
Cookie c = new Cookie(String name,String value);
// 服务端发送cookie
response.addCookie(c);
// 服务端获取cookie
Cookie[] request.getCookies();
// 示意,读取每一个cookie对象中的信息
String cookie.getName();
String cookie.getValue();
编码:
因为cookie只能保存合法的ascii码字符,所以中文需要转化,在添加cookie时,建议不管是不是中文统一都先转码
String URLEncoder.encode(String str,String charset);
String URLDecoder.decode(String str,String charset);
生存时间问题:
cookie默认是存在浏览器内存中的,关闭浏览器即销毁。
服务端可以设置cookie存活时间,这时候浏览器会把cookie信息存到本地硬盘中,超过指定存活时间再删除。
cookie生存时间设置:
Cookie c = new Cookie("a","");
// 设置参数单位为秒,0代表删除指定的cookie,
// 大于0浏览器会把cookie信息存在硬盘,小于0浏览器会把cookie存在内存中。
c.setMaxAge(0);
response.addCookie(c);
路径问题:
浏览器访问服务端的某个地址时,会比较是否与本地的某些cookie对应的路径相匹配,如果匹配就会随着cookie消息头发送出去。
默认路径:比如客户端访问"a/b/aaa.html"时候,服务端添加了一个cookie,则"a/b"就是此cookie的默认路径。
匹配规则:cookie会匹配默认路径或者默认路径的子路径。
如果客户端访问“a/b/bbb.html”或者"a/b/c/ddd.html",上述cookie都会被天津到cookie消息头中一并发送;
如果客户端访问的是“a/sss.html”则上述cookie不会被发送。
服务端可以更改cookie的默认路径:
cookie.setPath(String path);
cookie的限制:
1)cookie可以被用户禁止。
2)不安全,明文传输,如果非要存储敏感信息,建议加密处理。
3)存量有限,最多只能保存4k数据。
4)cookie数量有限,一个浏览器大约只能保存几百个cookie。
5)数据类型单一,只能保存字符串。
session
服务端为了保存用户状态而创建的对象。
工作原理:
1)客户端请求服务端
2)服务端会创建一个session对象,并把sessionId(session的唯一标识)以cookie的形式发给客户端。
3)客户端第二次访问的时候会把sessionId以cookie的形式发送给服务端
4)服务端读取sessionId,从本地内存中找到对应的session对象
session相关操作:
// 创建session对象
HttpSession s = request.getSession(boolean flag);
flag为true:
查看请求当中是否有sessionId,如果没有,则创建一个session对象;如果有sessionId,则依据该sessionId去查找对应的session对象,如果找到了,则返回该对象,如果找不到,则创建一个新的session对象。
flag为false:
先查看请求当中是否有sessionId,如果没有,返回null;如果有sessionId,则依据该sessionId去查找对应的session对象,如果找到了,则返回该对象,如果找不到,返回null。
如果不传参数,默认为true。
// session数据操作
void session.setAttribute(String name,Object obj);
void session.getAttribute(String name);
void session.removeAttribute(String name);
// 删除session对象
session.invalidate();
session生命周期:
1)客户端发出请求,服务端创建session对象开始,生命周期开始。
2)客户端关闭,session会被销毁,服务端也会在session超时后自行销毁。生命周期结束。
3)客户端长时间没有请求,2次请求间隔超过了session默认超时时间或者超过了服务端设置的session超时时间,session会被服务端从内存你中销毁,以节约内存。生命周期结束。
修改session超时时间:
方式1:通过修改web.xml来改变默认超时时间
30
方式2:通过编码调整session对象的超时时间
session.setMaxInactiveInterval(int seconds);
session和cookie
1)
session安全,可保存的数据类型多,可保存的数据量多。
cookie不安全,只能保存字符串,最多只能保存4k数据。
2)
session数据存在服务端,如果用户量大,会占用大量内存。
cookie数据存在客户端,不会占用服务端资源。
过滤器
Servlet过滤器是一个java类,用于加强原有servlet流程的功能。
Servlet过滤器使用的是责任链模式。
Servlet过滤器可用于拦截请求信息,进行一些校验类的行为;也可以用于对响应结果进一步处理,例如压缩等优化行为。
需要注意的是web.xml中配置的过滤器可以确定执行顺序,为map标签的顺序;通过注解配置的过滤器,执行顺序跟类名排序有关。
监听器
监听器是一个java类,用于跟踪servlet容器内部相关对象的变化,然后做一些自定义处理。
监听器的设计可以用发布订阅模式理解,也叫观察者模式,触发状态改变的时候会根据当前事件类型调用注册的相关监听器方法,把当前状态相关的对象作为参数传进去。
监听的事件源分别为 ServletContext, HttpSession 和 ServletRequest 这三个域对象。
ServletContextListener:应用上下文生命周期监听器。用于监听Web应用的启动和销毁事件。
ServletContextAttributeListener:应用上下文属性事件监听器。用于监听Web应用上下文中的属性改变的事件。
ServletRequestListener:请求生命周期监听器。用于监听请求的创建和销毁事件。
ServletRequestAttributeListener:请求属性事件监听器。用于监听请求中的属性改变的事件。
HttpSessionListener:会话生命周期监听器。用于监听会话的创建和销毁事件。
HttpSessionActivationListener:会话激活和钝化事件监听器。用于监听会话的激活和钝化的事件。
HttpSessionAttributeListener:会话属性事件监听器。用于监听会话中的属性改变的事件。
HttpSessionBindingListener:会话值绑定事件监听器。这是唯一不需要在web.xml中设定的Listener。
Servlet3.0新特性之异步处理支持
异步处理的支持关键在于对容器管理的工作线程的解放,提高了效率,思路就是通过让异步线程代替原工作线程执行工作,提高了稀缺的工作线程的工作效率。
Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下:首先,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理;接着,调用业务接口的某些方法,以完成业务处理;最后,根据处理的结果提交响应,Servlet 线程结束。其中第二步的业务处理通常是最耗时的,这主要体现在数据库操作,以及其它的跨网络调用等,在此过程中,Servlet 线程一直处于阻塞状态,直到业务方法执行完毕。在处理业务的过程中,Servlet 资源一直被占用而得不到释放,对于并发较大的应用,这有可能造成性能的瓶颈。对此,在以前通常是采用私有解决方案来提前结束 Servlet 线程,并及时释放资源。
Servlet 3.0 针对这个问题做了开创性的工作,现在通过使用 Servlet 3.0 的异步处理支持,之前的 Servlet 处理流程可以调整为如下的过程:首先,Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时 Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其它 Servlet。如此一来, Servlet 线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。
异步处理特性可以应用于 Servlet 和过滤器两种组件,由于异步处理的工作模式和普通工作模式在实现上有着本质的区别,因此默认情况下,Servlet 和过滤器并没有开启异步处理特性,如果希望使用该特性,则必须按照如下的方式启用:
- 对于使用传统的部署描述文件 (web.xml) 配置 Servlet 和过滤器的情况,Servlet 3.0 为 和 标签增加了 子标签,该标签的默认取值为 false,要启用异步处理支持,则将其设为 true 即可。以 Servlet 为例,其配置方式如下所示:
DemoServlet
footmark.servlet.Demo Servlet
true
- 对于使用 Servlet 3.0 提供的 @WebServlet 和 @WebFilter 进行 Servlet 或过滤器配置的情况,这两个注解都提供了 asyncSupported 属性,默认该属性的取值为 false,要启用异步处理支持,只需将该属性设置为 true 即可。以 @WebFilter 为例,其配置方式如下所示:
@WebFilter(urlPatterns = "/demo",asyncSupported = true)
public class DemoFilter implements Filter{...}
Servlet3.0新特性之可插性支持
如果说 3.0 版本新增的注解支持是为了简化 Servlet/ 过滤器 / 监听器的声明,从而使得 web.xml 变为可选配置, 那么新增的可插性 (pluggability) 支持则将 Servlet 配置的灵活性提升到了新的高度。熟悉 Struts2 的开发者都知道,Struts2 通过插件的形式提供了对包括 Spring 在内的各种开发框架的支持,开发者甚至可以自己为 Struts2 开发插件,而 Servlet 的可插性支持正是基于这样的理念而产生的。使用该特性,现在我们可以在不修改已有 Web 应用的前提下,只需将按照一定格式打成的 JAR 包放到 WEB-INF/lib 目录下,即可实现新功能的扩充,不需要额外的配置。
Servlet 3.0 引入了称之为”Web 模块部署描述符片段”的 web-fragment.xml 部署描述文件,该文件必须存放在 JAR 文件的 META-INF 目录下,该部署描述文件可以包含一切可以在 web.xml 中定义的内容。
现在,为一个 Web 应用增加一个 Servlet 配置有如下三种方式 ( 过滤器、监听器与 Servlet 三者的配置都是等价的,故在此以 Servlet 配置为例进行讲述,过滤器和监听器具有与之非常类似的特性 ):
- 编写一个类继承自 HttpServlet,将该类放在 classes 目录下的对应包结构中,修改 web.xml,在其中增加一个 Servlet 声明。这是最原始的方式;
- 编写一个类继承自 HttpServlet,并且在该类上使用 @WebServlet 注解将该类声明为 Servlet,将该类放在 classes 目录下的对应包结构中,无需修改 web.xml 文件。
- 编写一个类继承自 HttpServlet,将该类打成 JAR 包,并且在 JAR 包的 META-INF 目录下放置一个 web-fragment.xml 文件,该文件中声明了相应的 Servlet 配置。web-fragment.xml 文件示例如下:
fragment
footmark.servlet.FragmentServlet
fragment
/fragment
Servlet4.0新特性之服务器推送
服务器推送是最直观的 HTTP/2 强化功能,通过 PushBuilder 接口在 servlet 中公开。
服务器推送使服务器能预测客户端请求的资源需求。然后,在完成请求处理之前,它可以将这些资源发送到客户端。
要了解服务器推送的好处,可以考虑一个包含图像和其他依赖项(比如 CSS 和 JavaScript 文件)的网页。客户端发出一个针对该网页的请求。服务器然后分析所请求的页面,确定呈现它所需的资源,并主动将这些资源发送到客户端的缓存。
在执行所有这些操作的同时,服务器仍在处理原始网页请求。客户端收到响应时,它需要的资源已经位于缓存中。
参考地址:
https://www.runoob.com/servlet/servlet-tutorial.html
https://www.cnblogs.com/xiaochuan94/p/9184444.html
https://blog.csdn.net/a972669015/article/details/86564318
https://developer.ibm.com/zh/articles/j-lo-servlet30/
https://developer.ibm.com/zh/tutorials/j-javaee8-servlet4/