当前位置:网站首页>闭关修炼(二十一)Servlet生命周期、service方法源码分析、线程安全问题
闭关修炼(二十一)Servlet生命周期、service方法源码分析、线程安全问题
2022-06-29 08:11:00 【likeGhee】
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
Servlet的生命周期
- init–初始化
- service–do、post
- destroy–销毁
例子:
public class DemoServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// 初始化...
System.out.println("init()");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求
System.out.println("doGet");
}
@Override
public void destroy() {
// 销毁
System.out.println("destroy");
}
}
顺便复习一下servlet使用
在web.xml配置servlet映射关系
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<servlet>
<servlet-name>DemoServlet</servlet-name>
<servlet-class>ch21.DemoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DemoServlet</servlet-name>
<url-pattern>/demo</url-pattern>
</servlet-mapping>
</web-app>
启动tomcat,访问/demo接口,查看打印信息
发现访问三次后,初始化方法只调用了一次,并且没有销毁。
说明这个类只能实例化一次,使用了单例的设计模式。
谈到项目中设计模式的运用,HttpServlet就是单例设计模式的运用例子。
当tomcat服务器停止时才使用销毁方法。

Servlet简单源码分析
只是简单的看一下,我也不都明白,有问题请大佬指出。
我们点进HttpServlet类,发现它是继承GenericServlet(通用servlet)的
点进GenericServlet发现它实现了Servlet, ServletConfig, java.io.Serializable三个接口
我们再点进Servlet接口,ALT+7查看方法
我们能发现HttpServlet的源头了,HtppServlet继承GenericServlet,GenericServlet又实现Servlet接口,其中Servlet接口中有init、service、destroy这三个生命周期过程的函数
回到GenericServlet,发现它是一个抽象类,我们可以思考它为什么是一个抽象类,因为这是一种模板方法设计模式,不清楚的可以自己去查阅相关资料。这里简单提一下:
模板方法模式的定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
GenericServlet类中service方法是抽象方法,HttpServlet必须去实现具体的步骤。
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
因此我们就可以跳转到类去看service的实现源码(注意我们找的是标有重写标志的service方法),它将ServletRequest强转为HttpServletRequest,ServletResponse强转为HttpServletResponse,接着调用重载的service方法(这种做法是十分常见的代码简洁优化的方法,《代码简洁之道》中也提到了具体的理论)
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
接着看servic方法的具体实现,可以说这个源码是写的十分优雅了,函数中的所有语句都在同一抽象层级上,一眼就能看明白了,大致来说就是判断一下method,如果方式是get就doGet,如果是head就doHead,如果是POST就doPost等等…最后如果没有判断到是什么类型的method就错误处理。(lastModified就是等于-1,可以先忽略)
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
接着我们去看doGet方法实现,主要是判断protocol是否是1.1结尾,没啥用,因为被我们的类重写了
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
最后我们自己的doGet方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求
System.out.println("doGet");
}
所以service与doGet方法有什么区别?
doGet是在service方法中通过判断method类型,如果method是get类型,再调用doGet的。
我们就可以大致的猜测servlet的执行流程了:
- 读取web.xml文件解析servlet
- 使用java反射机制初始化servlet类
- 执行HttpServlet的service方法判断请求方式
- 调用我们自己的具体实现子类方法
总之看源码,要熟练使用idea的快捷键,如alt+ctrl+左键查看实现、ctrl+shift+alt+U显示继承关系和alt+7查看声明方法等等。
大致思路是先看祖宗类,慢慢到具体方法的实现,是一个逐步递进分析的过程。
Servlet多线程安全问题
我们使用过Servlet都知道Servlet是线程不安全的,如何证明?
(servlet是单例的)
例子:
public class DemoServlet extends HttpServlet {
private int i = 0;
@SneakyThrows
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求
System.out.println("doGet");
resp.getWriter().write(i+"");
Thread.sleep(5000);
i++;
}
}
然后我们用两个不同的浏览器同时去访问接口,我们发现i是一样的,发生线程安全问题
解决办法,加synchronized代码块进行同步,变量加volatile线程可见
@SneakyThrows
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求
System.out.println("doGet");
synchronized (this) {
resp.getWriter().write(i + "");
Thread.sleep(5000);
i++;
}
}
因此,我们在使用servlet的时候尽量不要去定义成员变量或者使用共享变量
边栏推荐
- 324. 摆动排序 II / 剑指 Offer II 102. 加减的目标值
- [microservices openfeign] timeout of openfeign
- js for in循环 for of循环的区别以及用法
- 标准|中国支付清算协会发布首个隐私计算金融规范
- The sixth season of 2022 perfect children's model Qingyuan competition area audition came to a successful conclusion
- NP5 格式化输出(三)
- 批量处理实验接触角数据-MATLAB分析
- 802.11--802.11n协议 PHY
- Speech signal processing - Fundamentals (I): basic acoustic knowledge
- Is it really safe to open a stock account online? Find the answer
猜你喜欢

壁纸小程序源码双端微信抖音小程序

Simple use of vlookup function in Excel -- exact matching or approximate matching data

自注意力机制超级详解(Self-attention)

Résumé des différentes séries (harmoniques, géométriques)

变形金刚Transformer详解

Huawei equipment is configured with small network WLAN basic services

Differences between x86 and x64

New spark in intelligent era: wireless irrigation with Lora wireless transmission technology

Seven common sorts

【Redis】Redis6学习框架思路和细节
随机推荐
2022春夏系列 KOREANO ESSENTIAL重塑时装生命力
Résumé des différentes séries (harmoniques, géométriques)
Matlab usage
通过ELO机制衡量各类对弈活动水平
城通网盘仿蓝奏网盘源码 附带视频教程
[microservices openfeign] timeout of openfeign
Typescript variable declaration - type assertion
P6776-[noi2020] surreal tree
Voice processing tool: Sox
人民链鲍大伟:打破壁垒,建立全域数据治理共享及应用平台
[hcie TAC] question 5-2
消息中间件:pulsar
Message Oriented Middleware: pulsar
Application of mediastreamer2 and GStreamer in embedded field
2022 spring summer collection koreano essential reshapes the vitality of fashion
2022第六季完美童模 海口赛区 选拔赛圆满落幕
P4769-[NOI2018]冒泡排序【组合数学,树状数组】
js for in循环 for of循环的区别以及用法
Déclaration de la variable Typescript - - assertion de type
网上开股票账户真的安全吗?求答案