本文共 8981 字,大约阅读时间需要 29 分钟。
Servlet 说白了就是个类,是个接口
然后它的作用是处理服务器接收到的请求
在Servlet4.0的中是这么描述的:
A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.
版本说明:Servlet4.0、Tomcat9.0
我们通常创建的Servlet 是这样的
public class XXX extends HttpServlet
继承了HttpServlet这个抽象类
在这个抽象类里,就有我们熟悉的doGet()、doPost()等方法
也正是因为这个继承关系,我们则可以重写doGet()、doPost()他们
由此来处理服务器收到的请求
而在HttpServlet之上,还有一个抽象类—GenericServlet
public abstract class HttpServlet extends GenericServlet
public abstract class GenericServletextends java.lang.Objectimplements Servlet, ServletConfig, java.io.Serializable
1.GenericServlet 了实现Servlet接口的init()、destroy()、service()等方法
init()方法其实没那么神秘
在GenericServlet中有两个init(),一个代参,一个不带参
代参:
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }
不带参:
public void init() throws ServletException { // NOOP by default }
真的没了,就是这样
除了代参的那个方法做了一下赋值
一样的,destroy()也是
public void destroy() { // NOOP by default }
就是空的
init()和destroy()会由Servlet的容器调用
你倒是可以在这里面做一个log一些信息
而service()方法在GenericServlet 中就是个抽象方法,啥代码逻辑都没有
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
处理请求,还得看HttpServlet
它的service()方法就是处理请求的主战场了
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; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // 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); } }
当有请求时,这个方法会由Servlet的容器调用
然后service()里面又会调用doGet()方法
2.GenericServlet 实现了ServletConfig接口的getServletContext()、getServletName()等方法
(这个不是特别重要,具体我就不展开了)
3.GenericServlet 实现了java.io.Serializable接口
这个接口是一个marker interface
其中并不含什么方法
其作用是标记这个类是Serializable
而Serializable的意思就是:
这个对象可以转化成字节序列
可以借助 ObjectOutputStream写入到一个文件里,又可以用ObjectInputStream再读出来
照应主题,其实只要一个类实现了Servlet接口,那么就可以说他是一个Servlet了
除此之外,你也可以选择继承已经实现Servlet接口的类
例如上面提到的GenericServlet 和HttpServlet
继承过后,又是一个Servlet~
Servlet写好了,直接把他放到webapps下就可以了吗?
当然不行。。
这里面多少还是有些讲究的
这里以Tomcat为例
Tomcat对应用的部署有一定的要求
目录结构就是其中之一
我们写好的Servlet应该被编译成.class文件,然后根据packages的关系,放到WEB-INF/classes下其中的编译工作,Tomcat不会帮我们做
大家其实也可以看到,目录结构中并没有哪个地方可以放我们写出来的.java文件
一个方法是我们手动用 javac+所需的jar包进行编译出.class文件
javac -classpath servlet-api.jar MyTestServlet.java
注 意 , 编 译 S e r v l e t 是 需 要 额 外 的 j a r 包 的 \color{blue}{注意,编译Servlet是需要额外的jar包的} 注意,编译Servlet是需要额外的jar包的
而且,不同的服务器需要的jar包也可能不同
Tomcat需要的是servlet-api.jar
而对于Glassfish和JBoss则需要javaee.jar
第二个方法就是借助像Eclipse这样的IDE帮我们了
相信大家都是利用IDE进行开发的
IDE可以帮我们完成很多工作,包括java文件编译和构建上面所说的目录结构
IDE会帮我们把编译出来的.class文件放到WEB-INF/classes下正确的位置
访 问 S e r v l e t \color{blue}{访问Servlet} 访问Servlet
除了编译Servlet外,我们还差一步就可以完成Servlet的部署了
那就是告诉服务器知道,哪些.class文件是Servlet
一种方法是在web.xml文件中声明
sonoojaiswal DemoServlet
然后,web.xml还允许我们做映射
sonoojaiswal /welcome
我们可以通过一些通配符来将好多不同的url映射到一个Servlet上
sonoojaiswal /*.map
上面这个url-pattern 可以理解为映射的规则
/*.map的意思就是在/目录下,所有后缀为.map的请求都会被对应到名为sonoojaiswal的Servlet
举例:
http://localhost:8080/EElearn/1.map
http://localhost:8080/EElearn/2.map http://localhost:8080/EElearn/abc.map
上面三个URL会对应到同一个Servlet----sonoojaiswal
注 意 , w e b . x m l 中 说 的 / 目 录 指 的 是 当 前 应 用 的 根 目 录 \color{blue}{注意,web.xml中说的/目录指的是当前应用的根目录} 注意,web.xml中说的/目录指的是当前应用的根目录
回到前面那个关于目录结构的图
/目录就是对应web-app/
对应上面的例子就是EElearn/
要与之区分对是另外一个/目录
假设在一个超链接中
go
注意,上面这个链接是无法正确访问Servlet的
原因是这里的/目录对应的是服务器的根目录
也就是 http://localhost:8080/
所以正确的超链接应该为:
go
希望大家一定要区分清楚,不然这一个矮凳子会让你找Bug找到崩溃
除了通过web.xml声明,还可以利用注解
@WebServlet("/FirstServlet")public class xxx extends HttpServlet
即声明了这个是名为FirstServlet的Servlet
注解当然也可以设置映射
@WebServlet(name="TestServlet", urlPatterns={ "/path", "/alt"})
我们自己写的Servlet默认是,有请求访问时才开始加载的
然后,我们也可以通过web.xml的配置来让Servlet预先加载
<load-on-startup>标签放到<servlet>标签下
sonoojaiswal DemoServlet 0
标签之间可以放入整数
负数表示不会预加载这个Servlet
大于或等于0的整数则是按自然数顺序加载
S e r v l e t 的 创 建 \color{blue}{Servlet的创建} Servlet的创建
接下来细讲
版本说明:Servlet4.0 Tomcat9.0
这里从Tomcat启动开始讲起
org.apache.catalina.core.StandardContext会实现org.apache.catalina.Context接口
这个接口相当于javax.servlet.Servlet接口中的ServletContext
可能听着会有点迷糊。。我再解释解释
在Servlet4.0的标准中有一个接口叫ServletContext
这个接口的描述是这样的
Defines a set of methods that a servlet uses to communicate with its servlet container
而Tomcat选择用它自己的org.apache.catalina.Context接口去代替ServletContext接口
public interface Contextextends Container, ContextBind
A Context is a Container that represents a servlet context
而StandardContext则是对Tomcat的Context接口的实现
好的,接下来继续
ServletContext或根据web.xml和注解中的配置信息(例如:@WebServlet("/xx"))
来统一管理其中的servlet、filter、listener
然后,对于servlet
在org.apache.catalina下有一个Wrapper接口
这个接口可以用来管理Servlet的生命周期的
Tomcat的API文档有这么一段描述
Implementations of Wrapper are responsible for managing the servlet life cycle for their underlying servlet class, including calling init() and destroy() at appropriate times, as well as respecting the existence of the SingleThreadModel declaration on the servlet class itself.
然后,StandardWrapper是Wrapper接口的一个实现
public class StandardWrapperextends ContainerBaseimplements ServletConfig, Wrapper, NotificationEmitter
实际上Servlet的加载和初始化是由这个类调用其load()方法实现的
(具体load()的代码我这就不全部展开了,大家有兴趣可以自行看看源码)
简单的说,就是Servlet的实例化:
servlet = (Servlet) instanceManager.newInstance(servletClass);
其中的servletClass:
protected String servletClass = null;
是一个字符串,也就是我们web.xml中配置的<servlet-class>标签中的内容
(包含了package的class name,如:com.test.Servlet1,即为Servlet1的servletClass)
而我们自己写的Servlet中的init()
只是在StandardWrapper完成Servlet的加载和初始化后才调用的
需要补充的是,servlets 和 filters 是会被加载进内存,并被所有的请求共享
我在这其实踩了坑。。。
看源码的时候,看到有一个StandardWrapper中有一个instancePool
然后看着看着觉得很奇怪,卡了很久
后来才知道,这个东西在Servlet2.4的时候就被弃用了
javax.servlet
Interface SingleThreadModel Deprecated. As of Java Servlet API 2.4, with no direct replacement.
为啥弃用了,源码里还有(小声bb,哭了)
S e r v l e t 的 业 务 处 理 \color{blue}{Servlet的业务处理} Servlet的业务处理
即service()
当服务器收到请求后,会创建新的 HttpServletRequest and HttpServletResponse 对象
(注意,是新的对象)
然后把他们传到FilterChain,执行一些列doFilter()后
到Servlet实例,执行service()方法
FilterChain是Servlet4.0中的接口
在Tomcat中,由ApplicationFilterChain来实现FilterChain接口
S e r v l e t 生 命 周 期 的 结 束 \color{blue}{Servlet生命周期的结束} Servlet生命周期的结束
别被destroy()给骗了
这个方法和init()一样,实际上没有代码逻辑的
真正处理Servlet的仍然是StandardWrapper
当关闭Tomcat时,才会对Servlet进行销毁
StandardWrapper调用unload()来结束一个Servlet
下面是大概的流程:
...instance.destroy();...instance = null;...
(instance就是一个Servlet的实例)
This is done so that as a server side developer you can focus on what to do with the HTTP request and responses and not bother about dealing with code that deals with networking etc. The container will take care of things like wrapping the whole thing in a HTTP response object and send it over to the client (say a browser).
转载地址:http://uliyb.baihongyu.com/