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的时候尽量不要去定义成员变量或者使用共享变量