一、Servlet基本概念
所谓Servlte,就是Service Applet,即运行在服务器端的应用,功能在于处理请求的业务逻辑。侠义的Servlet就是指Servlet接口,而通常Servlet都是指的广义的Servlet,即实现了Servlet接口的类。Servlet容器与Web服务器一同组成了Tomcat。
二、Servlet的工作原理
Servlet的工作主要分为三个部分
- 接收请求
- 处理请求
- 响应请求
任何一个应用程序,必然包括这三个步骤。
但处理请求的逻辑是不同的。没关系,抽取出来做成Servlet,交给程序员自己编写。
此部分参考bravo1988关于“servlet的本质是什么,它是如何工作的?”的回答
三、Servlet的接口与实现类解析
3.1、概述
想要使用Servlet,就必须实现Servlet接口,或继承实现了Servlet接口的类,在响应Http请求时通常使用的是继承HttpServlet的方式,Servlet的相关接口与实现类的继承方式如下图所示
Request对象中含有请求的内容,Response中含有响应的内容,这两个对象都是封装好的,可以直接进行使用。除了Servlet对象本身,Servlet容器还会创建一个ServletContext对象。这个对象中封装了上下文(应用程序)的环境详情。每个应用程序只有一个ServletContext。每个Servlet对象也都有一个封装Servlet配置的ServletConfig对象。下面就对上述四种接口/实现类加上前文提到的Config分别进行阐述。
Servlet及其实现类
3.2、Servlet接口及其实现类
3.2.1、Servlet接口
在Servlet接口中定义了四个抽象方法
import java.io.IOException;
public interface Servlet {
//Servlet的初始化方法
void init(ServletConfig var1) throws ServletException;
//返回的ServletConfig对象包含了Servlet的初始化参数
ServletConfig getServletConfig();
//Servlet处理请求方法
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
//获取Servlet信息
String getServletInfo();
//Servlet的销毁方法
void destroy();
}
除了获取信息的方法外,其他三个方法的调用时机也说明了Servlet的生命周期
- 在客户端发出第一次请求后,init方法被调用,Servlet对象被创建,这一对象也被后续的请求所使用,也就是说在Servlet被销毁之前,Servlet实例化对象只会被创建一次。
- service是Servlet处理请求的方法,每一次请求都会被调用
- destory是Servlet的销毁方法,当Servlet容器关闭或应用程序被卸载时,该方法会被调用。
3.2.2、GenericServlet抽象类
首先给出GenericServlet抽象类的源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}
其实现了Servlet, ServletConfig, Serializable三个接口,并重写了其中的抽象方法,这样做的好处在于,提供了抽象方法的默认实现,在使用时只需要按照需求进行方法的重写就可以了。
值得注意的是,init方法首先将ServletConfig对象保存,然后调用了一个空参的init方法,这样做的目的在于,ServletConfig由Generic方法来进行维护,而程序员要对init进行重写时,只需要重写空参的就好了,免去了重写整个方法还需要重新维护ServletConfig对象的麻烦。
3.2.3、HttpServlet抽象类
同样的,首先给出HttpServlet抽象类的源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.servlet.http;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public abstract class HttpServlet extends GenericServlet {
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
public HttpServlet() {
}
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(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected long getLastModified(HttpServletRequest req) {
return -1L;
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
NoBodyResponse response = new NoBodyResponse(resp);
this.doGet(req, response);
response.setContentLength();
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {
Class<?> clazz = c;
Method[] allMethods;
for(allMethods = null; !clazz.equals(HttpServlet.class); clazz = clazz.getSuperclass()) {
Method[] thisMethods = clazz.getDeclaredMethods();
if (allMethods != null && allMethods.length > 0) {
Method[] subClassMethods = allMethods;
allMethods = new Method[thisMethods.length + allMethods.length];
System.arraycopy(thisMethods, 0, allMethods, 0, thisMethods.length);
System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length, subClassMethods.length);
} else {
allMethods = thisMethods;
}
}
return allMethods != null ? allMethods : new Method[0];
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Method[] methods = this.getAllDeclaredMethods(this.getClass());
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;
for(int i = 0; i < methods.length; ++i) {
String methodName = methods[i].getName();
if (methodName.equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
} else if (methodName.equals("doPost")) {
ALLOW_POST = true;
} else if (methodName.equals("doPut")) {
ALLOW_PUT = true;
} else if (methodName.equals("doDelete")) {
ALLOW_DELETE = true;
}
}
StringBuilder allow = new StringBuilder();
if (ALLOW_GET) {
allow.append("GET");
}
if (ALLOW_HEAD) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append("HEAD");
}
if (ALLOW_POST) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append("POST");
}
if (ALLOW_PUT) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append("PUT");
}
if (ALLOW_DELETE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append("DELETE");
}
if (ALLOW_TRACE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append("TRACE");
}
if (ALLOW_OPTIONS) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append("OPTIONS");
}
resp.setHeader("Allow", allow.toString());
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String CRLF = "\r\n";
StringBuilder buffer = (new StringBuilder("TRACE ")).append(req.getRequestURI()).append(" ").append(req.getProtocol());
Enumeration reqHeaderEnum = req.getHeaderNames();
while(reqHeaderEnum.hasMoreElements()) {
String headerName = (String)reqHeaderEnum.nextElement();
buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));
}
buffer.append(CRLF);
int responseLength = buffer.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
out.print(buffer.toString());
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
if (!resp.containsHeader("Last-Modified")) {
if (lastModified >= 0L) {
resp.setDateHeader("Last-Modified", lastModified);
}
}
}
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
}
HttpServlet继承自Generic抽象类,重写了其中的service方法,Service首先进行了request和response对象的转换,得到HttpServletRequest和HttpServletResponse,而后将其传递给另一个重载的service方法。service方法通过不同的方法参数调用不同的处理方法,因此,继承HttpServlet的类只要重写这些方法就好了,最常用的是重写doGet和doPost方法。
3.3、ServletRequest接口及其拓展接口
.3.3.1、ServletRequest接口
ServletRequest接口的源码如下,这些抽象方法为其实现类提供了约束
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.servlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
public interface ServletRequest {
Object getAttribute(String var1);
Enumeration<String> getAttributeNames();
String getCharacterEncoding();
void setCharacterEncoding(String var1) throws UnsupportedEncodingException;
int getContentLength();
long getContentLengthLong();
String getContentType();
ServletInputStream getInputStream() throws IOException;
String getParameter(String var1);
Enumeration<String> getParameterNames();
String[] getParameterValues(String var1);
Map<String, String[]> getParameterMap();
String getProtocol();
String getScheme();
String getServerName();
int getServerPort();
BufferedReader getReader() throws IOException;
String getRemoteAddr();
String getRemoteHost();
void setAttribute(String var1, Object var2);
void removeAttribute(String var1);
Locale getLocale();
Enumeration<Locale> getLocales();
boolean isSecure();
RequestDispatcher getRequestDispatcher(String var1);
/** @deprecated */
String getRealPath(String var1);
int getRemotePort();
String getLocalName();
String getLocalAddr();
int getLocalPort();
ServletContext getServletContext();
AsyncContext startAsync() throws IllegalStateException;
AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;
boolean isAsyncStarted();
boolean isAsyncSupported();
AsyncContext getAsyncContext();
DispatcherType getDispatcherType();
}
其中最常用的是getParameter方法,请求端的url为http://localgost:80/index?operation=list;就可以通过
request.getParameter("operation");
的方法得到String类型的value值为list。
3.3.2、HttpServletRequest接口
HttpServletRequest接口拓展自ServletRequest接口,下面给出HttpServletRequest接口 的源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.servlet.http;
import java.io.IOException;
import java.security.Principal;
import java.util.Collection;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
public interface HttpServletRequest extends ServletRequest {
String BASIC_AUTH = "BASIC";
String FORM_AUTH = "FORM";
String CLIENT_CERT_AUTH = "CLIENT_CERT";
String DIGEST_AUTH = "DIGEST";
String getAuthType();
Cookie[] getCookies();
long getDateHeader(String var1);
String getHeader(String var1);
Enumeration<String> getHeaders(String var1);
Enumeration<String> getHeaderNames();
int getIntHeader(String var1);
String getMethod();
String getPathInfo();
String getPathTranslated();
String getContextPath();
String getQueryString();
String getRemoteUser();
boolean isUserInRole(String var1);
Principal getUserPrincipal();
String getRequestedSessionId();
String getRequestURI();
StringBuffer getRequestURL();
String getServletPath();
HttpSession getSession(boolean var1);
HttpSession getSession();
String changeSessionId();
boolean isRequestedSessionIdValid();
boolean isRequestedSessionIdFromCookie();
boolean isRequestedSessionIdFromURL();
/** @deprecated */
boolean isRequestedSessionIdFromUrl();
boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;
void login(String var1, String var2) throws ServletException;
void logout() throws ServletException;
Collection<Part> getParts() throws IOException, ServletException;
Part getPart(String var1) throws IOException, ServletException;
<T extends HttpUpgradeHandler> T upgrade(Class<T> var1) throws IOException, ServletException;
}
根据Http请求的结构(请求行、请求头、请求体),可以把上述方法分成三类,获取请求行相关内容方法、获取请求头相关方法和获取请求体相关内容方法。
(1)HttpServletRequest获取请求行相关方法
//获取客户端请求方式,GET、POST等
String getMethod();
//获取请求的URI
String getRequestURI();
//获取请求的URL
StringBuffer getRequestURL();
//获取站点根路径
String getContextPath();
//获取参数字符串
String getQueryString();
(2)HttpServletRequest获取请求头相关方法
long getDateHeader(String name);
String getHeader(String name);
Enumeration getHeaderNames();
Enumeration getHeaders(String name);
int getIntHeader(String name);
(3)HttpServletRequest获取请求体相关方法
//根据key值获取请求体中的value
String getParameter(String name);
//根据key值获取Value的字符串数组
String[] getParameterValues(String name);
//获取请求体key的枚举
Enumeration getParameterNames();
//获取所有的key-value并以map的形式返回
Map<String,String[]> getParameterMap();
3.4、ServletResponse接口及其拓展接口
3.4.1、ServletResponse接口
首先给出ServletResponse的源码
String getCharacterEncoding();
String getContentType();
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
void setBufferSize(int var1);
int getBufferSize();
void flushBuffer() throws IOException;
void resetBuffer();
boolean isCommitted();
void reset();
void setLocale(Locale var1);
Locale getLocale();
ServletResponse作为响应,最主要的功能自然是向客户端发出响应,这种响应大多数是通过向客户端发送HTML来实现的,在发送HTML之前,都应该设置响应数据的类型,类型如何设置可以在tomcat的配置文件中找到。这里给出了一个Response的实例,向前端返回JSON数据,可以看到,在向二进制流对象写数据之前,设置了HTML的格式为JSON。
除了向客户端响应数据的ServletOutputStream getOutputStream()方法外,还有一个PrintWriter getWriter()方法也可以向客户端响应数据,但注意二者不可同时使用,否则会报错。
protected void returnData(HttpServletResponse resp,Result result) throws IOException {
//使用响应流之前一定要注意两个点,第一是设置输出的格式,第二是设置编码的格式
resp.setContentType("application/json;charset=UTF-8");
//返回响应的Json数据
JSON.writeJSONString(resp.getOutputStream(),result);
}
3.4.2、HttpServletResponse接口
给出源码如下
package javax.servlet.http;
import java.io.IOException;
import java.util.Collection;
import javax.servlet.ServletResponse;
public interface HttpServletResponse extends ServletResponse {
int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;
int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;
int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;
int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;
int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
void addCookie(Cookie var1);
boolean containsHeader(String var1);
String encodeURL(String var1);
String encodeRedirectURL(String var1);
/** @deprecated */
String encodeUrl(String var1);
/** @deprecated */
String encodeRedirectUrl(String var1);
void sendError(int var1, String var2) throws IOException;
void sendError(int var1) throws IOException;
void sendRedirect(String var1) throws IOException;
void setDateHeader(String var1, long var2);
void addDateHeader(String var1, long var2);
void setHeader(String var1, String var2);
void addHeader(String var1, String var2);
void setIntHeader(String var1, int var2);
void addIntHeader(String var1, int var2);
void setStatus(int var1);
/** @deprecated */
void setStatus(int var1, String var2);
int getStatus();
String getHeader(String var1);
Collection<String> getHeaders(String var1);
Collection<String> getHeaderNames();
}
HttpServletResponse接口中设置了一些响应的状态码,也有与HttpServletRequest相类似的获取响应行、响应头、响应体相关内容的方法,这里 不再赘述。
3.5、ServletConfig接口
Servlet容器的init方法执行时,会有一个ServletConfig对象传入,ServletConfig的源码如下,方法的功能也已经以注释的方式给出
package javax.servlet;
import java.util.Enumeration;
public interface ServletConfig {
//获取Servlet名称
String getServletName();
//获取ServletContext对象
ServletContext getServletContext();
//根据名称获取初始化参数
String getInitParameter(String var1);
//列举所有的初始化参数名称
Enumeration<String> getInitParameterNames();
}
3.6、ServletContext对象
ServletContext对象可以看作是一个公共的空间,可以被所有的客户端所访问(关于域四大对象可以参考相对应的文章),Servlet容器关闭或应用程序卸载时其被销毁,域其他的域对象的方法一样,ServletContext对象也有响应的getAttribute和setAttribute的属性处理方法如下
//根据名称获取属性
Object getAttribute(String var1);
//获取所有的属性名称
Enumeration<String> getAttributeNames();
//设置属性值
void setAttribute(String var1, Object var2);
//根据名称删除属性
void removeAttribute(String var1);