Servlet
Servlet是基于Java技术的Web组件,被容器管理,用于生成动态内容。可以被基于Java技术的Web Server动态加载并运行。客户端通过Servlet容器实现的请求/应答模型与Servlet交互。
Servlet容器
Servlet容器是Java Web Server的一部分,它提供接受请求和发送响应的服务,基于MIME编解码请求和响应,Servlet容器同样能够管理Servlet生命周期。
Servlet接口
Servlet生命周期: Servlet生命周期由容器管理。在Servlet规范中规定了Servlet如何被加载、实例化、初始化、处理请求、结束服务。
- 加载、实例化: 加载就是Servlet类的加载,我们可以通过文件、网络流等加载Servlet,容器通过自定义的类加载器可以实现多个Servlet项目在容器中运行,互不影响。在Servlet中加载和实例化是一起的,我们可以配置Servlet使得加载和实例化可以发生在容器启动时,或者发生在该Servlet第一次处理请求时。通过Servlet属性==loadOnStartup==来配置,当loadStartup大于0时会在容器启动时就加载和实例化,默认值为-1(第一次处理请求时加载和实例化)。
初始化: Servlet的处理化通过调用init(ServletConfig)方法进行初始化。SerlvetConfig对象可以访问配置的Servlet InitParam属性,然后我们可以在初始化方法中进行其它的初始化操作。例如在Spring MVC中我们通过配置Dispatcher Servlet Param属性把Spring相关信息注入,然后在init()方法中获取并进行Spring容器的初始化,代码如下:
<!-- 配置DispatcherServlet --> <servlet> <servlet-name>seckill-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置springMVC需要加载的配置文件 spring-dao.xml spring-service.xml spring-web.xml--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-*.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>seckill-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
注意:一个Servlet对应一个ServletConfig,所以我们不应该把全局信息放入ServletConfig,除非只有一个Servlet。
- 处理请求: Servlet通过调用serivce(request,response)方法处理请求。客户端请求信息有ServletRequest及其子类表示,服务器响应信息有ServletResponse及其子类表示,Servlet通过调用service()方法处理请求并回复响应。由于容器可以并发的调用Servlet的service()方法,所以我们在实现service()方法或其子方法,例如doGet()方法时需要注意线程安全问题。
结束服务: 容器调用Servlet的destory()方法进行结束服务,然后释放该Servlet实例以供GC。我们可以在destory()方法中释放其使用的资源或者保持其持久化状态(HttpServlet实现了Serializable接口)。
实例化数量: 一般来讲,Servlet容器会对每个Servelt必须实例化一个对象(有且只有一个,实现了SingleThreadModel接口除外)。在分布式中,容器需要对每个JVM中个每个Servlet实例化一个对象(实现实现了SingleThreadModel接口除外)
处理请求: Servlet通过service()方法处理请求。通常我们继承HttpServlet,其实现的service()方法会自动把Http请求依照协议转发到相关方法上。
- doGet 处理 Http Get请求
- doPost 处理 Http Post请求
- doOptions 处理 Http Options请求
- ....
代码如下:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
// GET请求会有特殊的处理
} 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);
}
}
特别的GET处理(有条件支持): 在Http缓存规范中(可参考这篇文章)有==Last-Modified==和==if-Modified-Since==两个Header来进行Http缓存,Servlet处理GET请求时会通过处理这两个字段,有条件的支持缓存(依赖于Servlet的实现),代码如下:
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 {
// 省略其它处理逻辑
}
}
编写Servlet只需要重写getLastModified()方法即可,如下代码:
@Override
protected long getLastModified(HttpServletRequest req) {
return LocalDateTime.of(2018, 2, 6, 14, 9, 50)
.toInstant(ZoneOffset.of("+8"))
.toEpochMilli();
}
第一次访问Response Header如下:
HTTP/1.1 200
Last-Modified: Tue, 06 Feb 2018 06:09:50 GMT
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 06 Feb 2018 07:06:22 GMT
后面访问Request Header及Response Header如下:
GET /servlet/cacheServlet HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
If-Modified-Since: Tue, 06 Feb 2018 06:09:50 GMT
HTTP/1.1 304
Date: Tue, 06 Feb 2018 07:07:29 GMT
异常处理: Servlet在初始化(调用init()方法)和处理请求(调用service()方法)时,会抛出异常。
- 初始化时异常: 抛出ServletException异常时会停止初始化,且以后也不会初始化;抛出UnavailableExceptioin异常时,容器会在不可用时间过后继续初始化Serlvet。也就是说当请求会被容器分发到其他Servlet中去处理,相当于没有该Servlet。
- 处理请求时异常: 与初始化异常一样都可能抛出两种异常,且异常意义一样。但容器会继续都让该Servlet处理请求,抛出ServletException异常时会返回Http Response 500异常;抛出UnavailableException异常时会返回Http Response 503异常。
ServletContext
ServletContext(Servlet上下文)代表Servlet运行在Web应用的视图,它代表整个当前整个Web应用。ServletContext可以动态添加Servlet、Filter、Listener,可以获取当前Web应用下的所有资源,存放Servlet Attribute、Parameter属性以供所有Servlet访问。
每个部署到容器的Web应用都有一个与之对于的ServletContext,在分布式环境下每个JVM中的每个Web应用都有其对于的ServletContext。ServletContext是线程不安全的,所以所有的Servlet Parameter、Attribute都应该是线程安全的。
动态加载Servlet、Filter、Listener
Servlet3.0以后ServletContext支持动态添加Servlet、Filter、Listener,可以编程式的开发Web应用。其只能在实现ServletContextListener的contexInitialized方法中动态的添加,否则会抛异常,下面是其动态添加方法的API的注释:
@throws IllegalStateException if this ServletContext has already been initialized
@throws IllegalArgumentException if <code>filterName</code> is null or an empty String
@throws UnsupportedOperationException if this ServletContext was
passed to the {@link ServletContextListener#contextInitialized} method
of a {@link ServletContextListener} that was neither declared in
<code>web.xml</code> or <code>web-fragment.xml</code>, nor annotated
with {@link javax.servlet.annotation.WebListener}
具体接口实例如下,其中返回Registartion对象可以配置UrlMapping等属性:
public ServletRegistration.Dynamic addServlet(String servletName, Class <? extends Servlet> servletClass);
public FilterRegistration.Dynamic addFilter(String filterName, Class <? extends Filter> filterClass);
public void addListener(Class <? extends EventListener> listenerClass);
Spring中的ServletRegistrationBean、FilterRegistrationBean等就是通过ServletContext编程式的动态添加Servlet、Filter
获取资源
ServletContext接口提供访问Web应用资源的方法。Web应用资源指的是该Web应用下的所有资源,包括WEB-INF、自定义静态资源文件等等。ServletContext获取资源API如下所示,其中path必须以"/"开头,表示该资源是相对于该Web应用下的资源:
/**
* Returns a URL to the resource that is mapped to the given path.
* <p>The path must begin with a <tt>/</tt> and is interpreted
* as relative to the current context root,
* or relative to the <tt>/META-INF/resources</tt> directory
* of a JAR file inside the web application‘s <tt>/WEB-INF/lib</tt>
* directory.
*/
public URL getResource(String path) throws MalformedURLException;
/**
* path参数与上面方法一样,只是返回的是Inputstream
*/
public InputStream getResourceAsStream(String path);
/**
* path参数与上面方法一样,只是返回的是当前资源下的子资源
*/
public Set<String> getResourcePaths(String path);
/**
* path参数与上面方法一样,只是返回的是当前主机下该资源的真实目录
*/
public String getRealPath(String path);