概述
servlet是java web开发的核心基础。本文将对servlet的基础知识进行回顾、总结,希望能够查漏补缺,同时也对读者有所裨益。点此下载本文案例源码
学习网站
servlet知识脉络梳理
上面就是servlet知识脉络梳理的思维导图,下面我们来分别总结图中的每个小节。最后再通过一个简单的java web项目来演示下。
servlet基础
什么是servlet
简而言之,servlet就是sun公司制定的一种用来拓展web服务器功能的组件规范。因为很多早期的web服务器,比如apache,iis等,不能处理动态资源,servlet就应运而生了。
如何写一个servlet
- 写一个java类,实现Servlet接口或者继承HttpServlet类
- 编译、打包、部署在web容器上(一般使用tomcat)
- 启动,打开浏览器,输入地址,即可访问
servlet的运行机制
- 浏览器依据ip和port,建立连接
- 浏览器创建请求数据包并发送
- 服务器解析请求数据包,并且将解析到的数据存放到request对象中,同时,创建response对象
- 服务器创建servlet对象,然后调用该对象的service方法,服务器会将request和response作为参数传给给service方法
- 服务器从response对象中获取处理结果,然后创建响应数据包并发送
- 浏览器解析响应数据包,然后依据解析到的数据生成相应的页面
http协议(了解)
- 是一种网络应用层协议,规定了浏览器与web服务之间的通信规则及数据包结构
- 数据包结构分为请求数据包和响应数据包,请求数据包分为请求行和实体内容,响应数据包分为状态行和消息头
- 两种请求方式:get请求和post请求
servlet核心
如何获取请求参数值
- String request.getParameter(String paramName);
- String[] request.getParameterValues(String paramName);
中文的处理方式
- post请求:request.setCharacterEncoding(String charset);
- get请求:修改server.xml,添加
<Connector URIEncoding='utf-8'/> - response.setContentType("text/html;charset=utf-8");
转发和重定向
- 重定向: 服务器通知浏览器重新向某个地址发送请求,服务器一般会发送302状态码
- 如何重定向:response.sendRedirect(String url); (重定向之前,容器会清空response对象上存放的所有数据)
- 重定向的特点:重定向的地址是任意的;重定向后,浏览器地址发生改变
- 转发:一个web组将未完成的处理交给另外一个web组件继续做。最常见的情况:一个servlet获得数据,然后将这些数据转发给一个jsp来展现
- 如何转发:
- 将数据绑定在request对象上:request.setAtribute(String name,Object obj);
- 获得转发器:RequestDispatcher rd = request.getRequestDispatcher(String uri);
- 转发:rd.forward(request,response);
- 转发的特点:
- 转发之后,浏览器地址栏地址不变
- 转发的地址有限制(要求属于同一个应用)
servlet容器如何处理请求资源路径
举例说明,在浏览器中输入 http://ip:port/day08-2/abc.html
- 容器默认为访问的是一个servlet。容器会将资源路径的应用名除掉。得到"/abc.html",然后查找web.xml配置文件,看有没有与之匹配的sevlet。匹配方式有如下三种:
- 精确匹配
- 通配符匹配,即使用"*"配置任意的零个或多个字符
- 后缀匹配,使用"*"开头,后接任意的一个后缀,比如
<url-pattern>*.do</url-pattern>
- 如果找不到对应的servlet,容器会查找对应位置的文件,找到则返回文件内容,找不到则返回404
- 注意路径问题。不以“/”开头的路径是相对路径,以“/”开头的路径是绝对路径。获取应用名时不要直接硬编码,可以通过request.getContextPath()来获取
如何让一个servlet处理多种请求
- 使用后缀匹配。如
<url-pattern>*.do</url-pattern> - 分析请求资源路径,调用对应的分支来处理
servlet生命周期
- 生命周期:servlet容器如何创建servlet对象,如何对其进行初始化、如何调用其方法处理请求,以及如何销毁该对象的整个过程,即容器如何管理servlet
- 生命周期的几个阶段:
- 实例化
- 容器调用servlet的构造器,创建servlet实例。容器只会创建一个实例
- 容器可以在收到请求之后创建servlet实例,或者在容器启动后立即创建(需要配置load-on-startup属性,值越小优先级越高)
- 初始化:
- 创建完servlet实例之后,立即调用该实例的init方法,init方法只会执行一次
- 初始化参数:配置、读取初始化参数
- 就绪(调用)
- 容器收到请求之后,会调用servlet的service方法来处理
- 销毁
- 容器在删除servlet实例之前,会调用该实例的destroy方法。该方法只会执行一次
- 相关的接口与类
- servlet接口:init(ServletConfig config);service(ServletRequest req,ServletResponse res);
- GenericService抽象类:实现了Servlet接口中的部分方法(init,destroy)
- HttpServlet抽象类:继承了GenericServlet,实现了service方法
servlet线程安全问题
- 存在的原因:容器对于某个servlet,只会创建一个实例;容器每收到一个请求,都会启动一个线程处理请求
- 解决方案:加锁解决,比如使用synchronized来加锁方法或代码块
servlet上下文
- 定义:容器启动后,会为每个web应用创建唯一的一个符合ServletContext接口要求的对象,该对象被成为Servlet上下文
- 唯一性:一个web应用只能对应一个Servlet上下文
- 持久性:只要容器没有被关闭,应用没有被卸载,Servlet上下文就会一直存在
- 如何获取:HttpSession、GenericService提供了getServletContext方法来获得上下文
- 作用:request、session、Servlet上下文都提供了绑定数据相关的方法
状态管理
- 定义:将浏览器与web服务器之间多次交互当作一个整体来处理,并且将多次交互所涉及的数据(即状态)保存下来。保存在浏览器端,叫做cookie,保存在服务器端的,叫做session。
过滤器与监听器
- 过滤器
- 定义:servlet规范中定义的一种特殊的组件,用来拦截Servlet容器调用的过程。容器收到请求之后,会先调用过滤器,再调用后续的组件,比如调用Servlet
- 编写过滤器的步骤:
- 写一个java类,实现Fiter接口
- 在doFilter方法当中,实现拦截器处理逻辑
- 配置过滤器(web.xml)
- 过滤器的优先级:当有多个过滤器都满足拦截要求时,则容器依据
<filer-mapping>配置的先后顺序来执行 - 初始化参数
- 配置初始化参数:使用
<init-param>配置 - 读取初始化参数值:String illegal = config.getInitParameter("Illegal");
- 配置初始化参数:使用
- 优点:
- 可以在不修改原有代码的基础上,为应用添加新的功能
- 可以将多个组件相同的功能集中写在一个过滤器中,方便代码的维护
- 监听器
- 定义:servlet规范中定义的一种特殊的组件,用于监听servlet容器产生的事件并进行相应的处理
- 编写监听器的步骤:
- 写一个java类,实现监听器接口(比如要监听session对象的创建和销毁,需要实现HttpSessionListener接口)
- 在监听器接口方法中,实现监听处理逻辑
- 配置监听器(Web.xml)
- 容器产生的事件:
- 生命周期相关的事件:比如request、session、servlet上下文的创建、销毁等事件
- 绑定数据相关的事件:调用了request,session,servlet上下文的setAttribute,removeAttribute方法产生的事件
典型案例
- 用户管理
- 登陆(session验证)
- 登陆成功之后,在session对象上绑定一些数据。例如:session.setAttribute("user",user);
- 当用户访问需要保护的资源时,进行session验证。例如:
Object obj = session.getAttribute("user");
if(obj == null){
//没有登陆
response.sendRedirect("login.jsp");
servlet3.0新特性
servlet4.0新特性
代码演示
开发环境
- java: JDK8
- IDE: Intellij IDEA
- tomcat: tomcat-9.0.14
使用@WebServlet创建Servlet
@WebServlet(
name = "annotationServlet", //servlet的名称,没有指定,则默认使用类的全限定名
//value = "",等价于urlPatterns,不能同时使用
urlPatterns = "/annotation", //一组servlet的url匹配模式
loadOnStartup = -1, //servlet的加载顺序,默认为-1
asyncSupported = false, //是否支持异步操作方式,默认为false
description = "这是注解版本的servlet",//描述信息
displayName = "annotationServlet", //servlet的显示名,通常配合工具使用
initParams = { @WebInitParam(name = "userName", value = "zhangsan"), @WebInitParam(name = "age", value = "18") } //初始化参数
)
public class AnnotationServletDemo extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = this.getInitParameter("userName");
String age = this.getInitParameter("age");
PrintWriter writer = resp.getWriter();
writer.println("hello servlet!!"+userName+","+age);
writer.flush();
writer.close();
}
}
在这里我们使用默认的端口,默认的index.jsp页面,context-path默认与应用名servletDemo相同,因此,我们只需在浏览器输入如下的url,即可访问我们创建的这个servlet:
http://localhost:8080/servletDemo/annotation
这时候,出现如下的结果,说明servlet配置成功了:
hello servlet!!zhangsan,18
使用@WebFilter创建过滤器
@WebFilter(
filterName = "filterDemo", //过滤器名称,没有指定的话,默认使用全限制类名
urlPatterns = "/*", //url匹配
initParams = {@WebInitParam(name = "className",value = "filer")}, //初始化参数
dispatcherTypes = DispatcherType.REQUEST //转发器类型,默认REQUEST
)
public class AnnotationFilterDemo implements Filter {
FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String className = filterConfig.getInitParameter("className");
System.out.println("go to filer!"+className);
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("out of filer!");
}
}
Servlet3.0对异步处理的支持
由于servlet处理业务逻辑时,一般只创建了一个servlet对象,但是如果同时有多个请求,就可能一个servlet对象同时被分配了多个线程。在servlet2.5中,如果某个逻辑操作超时,则后续的请求都会被阻塞。servlet3.0支持异步处理,即单独开启线程处理业务逻辑。
@WebServlet(urlPatterns = "/demo",asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("进入Servlet的时间:"+new Date()+".");
out.flush();
//在子线程中执行业务调用,并由其负责输出响应,主线程退出
//配置request属性,使其支持异步处理
req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
AsyncContext asyncContext = req.startAsync();
new Thread(new Executor(asyncContext)).start();
out.println("结束Servlet的时间:"+new Date()+".");
out.flush();
}
}
class Executor implements Runnable {
private AsyncContext ctx = null;
public Executor (AsyncContext ctx ){
this.ctx = ctx;
}
@Override
public void run() {
try {
//等待10秒,模拟业务方法的执行
Thread.sleep(10000);
PrintWriter out = ctx.getResponse().getWriter();
out.println("业务处理完毕的时间:"+ new Date()+".");
out.flush();
ctx.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
重启服务,这时候只要在浏览器输入http://localhost:8080/servletDemo/demo,就可以看到页面立刻打印进入servlet和结束servlet的时间,主线程已经退出了。10秒之后,再次打印业务处理完成的时间。
Servlet3.0对文件上传的支持
servlet2.5无法独立完成文件上传的功能,需要这两个jar包的支持:
而servlet3.0已经独立提供了文件上传的支持:
客户端:
- 表单的提交方式必须是post
- 必须有一个文件上传组件
<input type="file",name="file"/> - 必须设置表单的
enctype="multipart/form-data"
服务器:
- 在servlet上添加注解 @MulipartConfig
- Part getPart(String name) 或者 Collection getParts()
代码演示如下:
@WebServlet(urlPatterns = "/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到上传文件的目录
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
File file = new File(savePath);
if(!file.exists() && !file.isDirectory()){
System.out.println("目录不存在,需要创建");
file.mkdirs();
}
//获取文件
Part img = req.getPart("file");
//文件全路径
String filePath = file.getPath() + File.separator + img.getSubmittedFileName();
//写入文件
img.write(filePath);
//在使用PrintWriter对象打印中文字符之前,先设置utf-8编码,否者会乱码
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html; charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.println("File upload:"+filePath);
}
}
简单的上传页面upload.jsp如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<h1>Upload File</h1>
<%--这里的action对应sevlet的url,这里的upload对应着sevlet的 /upload --%>
<form action="upload" method="post" enctype="multipart/form-data">
File : <input type="file" name="file"/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
重启服务,浏览器输入http://localhost:8080/servletDemo/upload.jsp,即可看到如下页面:
选择文件,点击上传,通过文件上传的servlet处理,页面就会打印文件的上传路径,说明文件上传配置成功了。
Servlet4.0对http2协议的支持
开启http2所需要的软件
- apache-tomcat-9.0.14-windows-x64.zip
- tomcat-native-1.2.24-openssl-1.1.1g-win32-bin.zip
- Win64OpenSSL-1_1_1g.exe
- 私钥生成命令.txt
开启http2所需要的步骤
- 启动startup.bat,测试tomcat是否运行正常
- 修改tomcat的server.xml,先注释端口为8080的Connector
<!--
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
- 取消端口为8443的Connector的注释,并且删除证书链的配置,在这里我们并不需要
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
- 上面的certificateKeyFile和certificateFile分为指定私钥和证书的路径,所以我们按照默认的路径,来分别生成私钥和证书,这时候直接重启tomcat会报错或者一闪而过是正常的,因为还没有生成私钥和证书。解压缩tomcat-native,并且执行里面的bin目录下的openssl.exe文件,在打开的窗口输入如下命令
OpenSSL> genrsa -out localhost-rsa-key.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.................+++++
...................+++++
e is 65537 (0x010001)
OpenSSL>
出现如上类似的提示,说明私钥生成成功,在openssl.exe文件所在的目录下,可以看到localhost-rsa-key.pem文件,也就是刚才生成的私钥文件 5. 接着生成证书
OpenSSL> req -new -x509 -key localhost-rsa-key.pem -out localhost-rsa-cert.pem -days 3650
Can't load ./.rnd into RNG
2516:error:2406F079:random number generator:RAND_load_file:Cannot open file:crypto\rand\randfile.c:98:Filename=./.rnd
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [AU]:
- 上面生成证书失败,有可能是以前安装配置过openSSL,配置冲突了,所以这里我重新安装了OpenSSL软件,并且把安装目录下的bin下的cnf里面的openssl.cnf文件拷贝到C:\Program Files\Common Files\SSL目录下。
- 重新执行tomcat-native的openssl.exe文件,重新执行上面的两个命令
OpenSSL> genrsa -out localhost-rsa-key.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.......................+++++
................................................................+++++
e is 65537 (0x010001)
OpenSSL> req -new -x509 -key localhost-rsa-key.pem -out localhost-rsa-cert.pem -days 3650
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
OpenSSL>
上面的需要你输入的信息直接回车即可,如上,说明密钥和证书已经生成成功了。在同级目录下,可以看到生成的localhost-rsa-cert.pem和localhost-rsa-key.pem,将它们拷贝到tomcat9的conf目录下。 8. 复制tomcat-native下面的bin目录下的x64下面的2个文件拷贝到jdk1.8的bin目录下 9. 在tomcat9下,点击启动startup.bat,启动完成,打开浏览器,输入https://localhost:8443,测试是否正常访问 10. 打开IDEA,进行相关配置之后,打开浏览器,输入https://localhost:8443/servletDemo/demo,能够正常响应,说明我们的web应用配置ttp2也成功了。
Servlet4.0进行服务器推送
所谓的服务器推送,其实指的是服务器预先将资源发给浏览器进行缓存,从而加快页面的访问速度。
服务器推送步骤
- 设置推送资源
PushBuilder pb = request.newPushBuilder();
pb.path("1.jpg");
- 进行推送
pb.push();
代码演示
首先,我们修改index.jsp文件,加了一个图片的展示,其中1.jpg这个图片与index.jsp同目录。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<img src="1.jpg"/>
</body>
</html>
接着,我们新建一个servlet,推送这个图片
@WebServlet(urlPatterns = "/server")
public class ServerPushServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取到PushBuilder对象
PushBuilder pushBuilder = req.newPushBuilder();
//指定推送的资源,并且进行推送
pushBuilder.path("1.jpg").push();
}
}
我们可以执行这个servlet,先推送图片到浏览器缓存,到访问对应的页面,这时候页面的访问速度就会快很多。
Servlet4.0之HttpServletMapping的使用
Servlet4.0之HttpFilter的使用
HttpFilter其实是Filter的实现类,增加了对http协议的支持, 详见 servlet4.0社区文档