JavaWeb开发中,Servlet是重要的一环,常用于处理逻辑事物。现在比较习惯用编辑器直接新建一个Servlet出来,但是对于初学者,这样的方式并不能理解到它从最原始的形态到现在的演变,本文就此做一个简单的推演,从Servlet的生命周期开始,到现在比较常用的做法,搭建一个桥梁,让初学者可以很容易看懂为什么要这么做。
新建一个Maven工程,引入必要依赖:
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency>
在web.xml中配置好<Servlet/>以及<Servlet-mapping/>
<servlet> <servlet-name>OriginalServlet</servlet-name> <servlet-class>com.test.OriginalServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>OriginalServlet</servlet-name> <url-pattern>/OriginalServlet</url-pattern> </servlet-mapping>
并在类中,实现Servlet接口,实现其中的几个方法:
import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * Servlet implementation class OriginalServlet */ public class OriginalServlet implements Servlet { /** * Default constructor. */ public OriginalServlet() { // TODO Auto-generated constructor stub } /** * @see Servlet#init(ServletConfig) */ public void init(ServletConfig config) throws ServletException { // TODO Auto-generated method stub } /** * @see Servlet#destroy() */ public void destroy() { // TODO Auto-generated method stub } /** * @see Servlet#service(ServletRequest request, ServletResponse response) */ public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub } /** * @see Servlet#getServletConfig() */ public ServletConfig getServletConfig() { // TODO Auto-generated method stub return null; } /** * @see Servlet#getServletInfo() */ public String getServletInfo() { // TODO Auto-generated method stub return null; } }
诠释Servlet生命周期时,一定离不开三个重要的方法:
- 一个init(ServletConfig),表示初始化方法。默认情况下,是第一次调用该Servlet之前执行,如果需要它在服务器启动时执行,只需要在web.xml中配置<load-on-startup/>参数,其中的数字越小,被加载的优先级越高;
ServletConfig表示servlet配置信息的封装对象,其中有几个比较重要的方法:
getServletName() -- 获得<servlet/><servlet-name/>的名称
getInitParameter(name) -- 获得初始化参数
getInitParameterNames() -- 获得所有的初始化参数(迭代器前身)
getServletContext() -- 详细内容见后部分
- 第二个是service(ServletRequest,ServletResponse)表示servlet执行方法,每一次请求将会执行一次;
tomcat作为服务器时,每次访问到该Servlet时,就会调用该service方法,控制台输出该request与response参数,可以发现输出如下:
org.apache.catalina.connector.RequestFacade@5caf2b5
org.apache.catalina.connector.ResponseFacade@624641db
翻看源码,可以看出,这两个对象所对应的类,其实所实现的是HttpServletRequest与HttpServletResponse接口,所以:
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
- 第三个是destroy()表示销毁方法,会在服务器正常关闭时执行一次。
------------------------------------------------------------------------------------->
有了上述的接口的几个方法,我们会想到,如何在真正使用的时候,将它最简化。对于init(ServletConfig),destroy()等不常用到的方法,我们可以用一个基础类实现Servlet接口,之后所有的servlet类继承该基础类,需要用到某个方法时,覆写实现即可。
以下几点都是基于如何将后续常用的Servlet最简化而进行的基础工作:
- 将service方法用abstract修饰,使这个方法成为子类必须实现的方法
- 基础类中的init(ServletConfig)方法拥有的配置信息对象参数,要用一种简洁方式提供给子类,所以用私有属性获得该参数,并提供get方法返回;进一步地,如果子类中需要用到该参数,那么会getServletConfig().getServletName()获取,于是我们让基础类实现ServletConfig这个接口,提供几个获取重要参数的方法,并在方法体中返回getServletConfig().getXXX(),这样,子类中就可以直接调用这些方法获取到相应的参数
- 避免出现子类的init(ServletConfig)方法忘记执行super(ServletConfig)而出现NullPointerException,所以在基础类中的init(ServletConfig)方法调用一个本地的init()方法,子类如果需要,就覆盖init()方法,保证初始化顺利地进行
package javax.servlet; import java.io.IOException; import java.util.Enumeration; import java.util.ResourceBundle; public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { private static final String LSTRING_FILE = "javax.servlet.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE); private transient ServletConfig config; public GenericServlet() { } public void destroy() { } public String getInitParameter(String name) { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getInitParameter(name); } public Enumeration getInitParameterNames() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getInitParameterNames(); } public ServletConfig getServletConfig() { return config; } public ServletContext getServletContext() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } 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) { getServletContext().log(getServletName() + ": "+ msg); } public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); } public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletName() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getServletName(); } }
------------------------------------------------------------------------------------->
实现http协议
之前已经看到,在service方法中可以转为HttpServletRequest与HttpServletResponse两个接口,从而支持http相关的服务。但每次都做这种强转还是相当麻烦的,所以,我们做如下处理:
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
所以,我们需要一个符合http,使用起来相对简便的基础类来作为常用servlet的父类,首先做如下改造:
public class MyHttpServlet extends GenericServlet { private static final long serialVersionUID = 1L; @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) req; HttpServletResponse httpServletResponse = (HttpServletResponse) res; service(httpServletRequest,httpServletResponse); } public void service(HttpServletRequest request, HttpServletResponse response) { } }
这样在基类中可以比较方便地使用改造之后的方法,其参数就直接是HttpServletRequest与HttpServletResponse,不用做额外地转换。在service中,可以根据request的访问方法(GET,POST,PUT,DELETE...)对其进行相应的处理,从而衍生出doGet(HttpServletRequest request, HttpServletResponse response),doPost(HttpServletRequest request, HttpServletResponse response)等常用方法。这里可以看看官方给出的HttpServlet源码:
package javax.servlet.http; import java.io.IOException; import java.io.PrintWriter; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.Enumeration; import java.util.Locale; 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 implements java.io.Serializable { 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(LSTRING_FILE); 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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } protected long getLastModified(HttpServletRequest req) { return -1; } protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { NoBodyResponse response = new NoBodyResponse(resp); 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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, 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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, 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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } private Method[] getAllDeclaredMethods(Class c) { if (c.equals(javax.servlet.http.HttpServlet.class)) { return null; } Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass()); Method[] thisMethods = c.getDeclaredMethods(); if ((parentMethods != null) && (parentMethods.length > 0)) { Method[] allMethods = new Method[parentMethods.length + thisMethods.length]; System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length); System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length); thisMethods = allMethods; } return thisMethods; } protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method[] methods = 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++) { Method m = methods[i]; if (m.getName().equals("doGet")) { ALLOW_GET = true; ALLOW_HEAD = true; } if (m.getName().equals("doPost")) ALLOW_POST = true; if (m.getName().equals("doPut")) ALLOW_PUT = true; if (m.getName().equals("doDelete")) ALLOW_DELETE = true; } String allow = null; if (ALLOW_GET) if (allow==null) allow=METHOD_GET; if (ALLOW_HEAD) if (allow==null) allow=METHOD_HEAD; else allow += ", " + METHOD_HEAD; if (ALLOW_POST) if (allow==null) allow=METHOD_POST; else allow += ", " + METHOD_POST; if (ALLOW_PUT) if (allow==null) allow=METHOD_PUT; else allow += ", " + METHOD_PUT; if (ALLOW_DELETE) if (allow==null) allow=METHOD_DELETE; else allow += ", " + METHOD_DELETE; if (ALLOW_TRACE) if (allow==null) allow=METHOD_TRACE; else allow += ", " + METHOD_TRACE; if (ALLOW_OPTIONS) if (allow==null) allow=METHOD_OPTIONS; else allow += ", " + METHOD_OPTIONS; resp.setHeader("Allow", allow); } protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int responseLength; String CRLF = "\r\n"; String responseString = "TRACE "+ req.getRequestURI()+ " " + req.getProtocol(); Enumeration reqHeaderEnum = req.getHeaderNames(); while( reqHeaderEnum.hasMoreElements() ) { String headerName = (String)reqHeaderEnum.nextElement(); responseString += CRLF + headerName + ": " + req.getHeader(headerName); } responseString += CRLF; responseLength = responseString.length(); resp.setContentType("message/http"); resp.setContentLength(responseLength); ServletOutputStream out = resp.getOutputStream(); out.print(responseString); out.close(); return; } 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 / 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 { 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); } } private void maybeSetLastModified(HttpServletResponse resp, long lastModified) { if (resp.containsHeader(HEADER_LASTMOD)) return; if (lastModified >= 0) resp.setDateHeader(HEADER_LASTMOD, lastModified); } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); } } class NoBodyResponse implements HttpServletResponse { private HttpServletResponse resp; private NoBodyOutputStream noBody; private PrintWriter writer; private boolean didSetContentLength; // file private NoBodyResponse(HttpServletResponse r) { resp = r; noBody = new NoBodyOutputStream(); } // file private void setContentLength() { if (!didSetContentLength) resp.setContentLength(noBody.getContentLength()); } public void setContentLength(int len) { resp.setContentLength(len); didSetContentLength = true; } public void setCharacterEncoding(String charset) { resp.setCharacterEncoding(charset); } public void setContentType(String type) { resp.setContentType(type); } public String getContentType() { return resp.getContentType(); } public ServletOutputStream getOutputStream() throws IOException { return noBody; } public String getCharacterEncoding() { return resp.getCharacterEncoding(); } public PrintWriter getWriter() throws UnsupportedEncodingException { if (writer == null) { OutputStreamWriter w; w = new OutputStreamWriter(noBody, getCharacterEncoding()); writer = new PrintWriter(w); } return writer; } public void setBufferSize(int size) throws IllegalStateException { resp.setBufferSize(size); } public int getBufferSize() { return resp.getBufferSize(); } public void reset() throws IllegalStateException { resp.reset(); } public void resetBuffer() throws IllegalStateException { resp.resetBuffer(); } public boolean isCommitted() { return resp.isCommitted(); } public void flushBuffer() throws IOException { resp.flushBuffer(); } public void setLocale(Locale loc) { resp.setLocale(loc); } public Locale getLocale() { return resp.getLocale(); } public void addCookie(Cookie cookie) { resp.addCookie(cookie); } public boolean containsHeader(String name) { return resp.containsHeader(name); } /** @deprecated */ public void setStatus(int sc, String sm) { resp.setStatus(sc, sm); } public void setStatus(int sc) { resp.setStatus(sc); } public void setHeader(String name, String value) { resp.setHeader(name, value); } public void setIntHeader(String name, int value) { resp.setIntHeader(name, value); } public void setDateHeader(String name, long date) { resp.setDateHeader(name, date); } public void sendError(int sc, String msg) throws IOException { resp.sendError(sc, msg); } public void sendError(int sc) throws IOException { resp.sendError(sc); } public void sendRedirect(String location) throws IOException { resp.sendRedirect(location); } public String encodeURL(String url) { return resp.encodeURL(url); } public String encodeRedirectURL(String url) { return resp.encodeRedirectURL(url); } public void addHeader(String name, String value) { resp.addHeader(name, value); } public void addDateHeader(String name, long value) { resp.addDateHeader(name, value); } public void addIntHeader(String name, int value) { resp.addIntHeader(name, value); } public String encodeUrl(String url) { return this.encodeURL(url); } public String encodeRedirectUrl(String url) { return this.encodeRedirectURL(url); } } class NoBodyOutputStream extends ServletOutputStream { private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE); private int contentLength = 0; // file private NoBodyOutputStream() {} // file private int getContentLength() { return contentLength; } public void write(int b) { contentLength++; } public void write(byte buf[], int offset, int len) throws IOException { if (len >= 0) { contentLength += len; } else { // XXX // isn't this really an IllegalArgumentException? String msg = lStrings.getString("err.io.negativelength"); throw new IOException("negative length"); } } }
------------------------------------------------------------------------------------->
最后再说说ServletContext的作用:
它是一个接口javax.servlet.ServletContext,tomcat等web容器在启动时会创建一个实例,服务器关闭时销毁。主要作用是在一个web项目中共享数据、管理web项目资源,为整个web配置公共信息等。
api:
web项目中共享数据:提供属性操作 --xxxAttribute
setAttribute(String name,Object obj ) 给web项目公共区域存放内容。(任意内容)
getAttribute(String name) 通过指定名称获得内容
removeAttribute(String name) 通过指定名称移除内容。
如果不想使用数据,要么移除,要么关闭服务器。
整个web项目初始化参数
getInitParameter(java.lang.String name) 获得指定名称,初始化参数的值
getInitParameterNames() 获得所有的初始化参数的名称
web.xml 配置 整个web项目的初始化
<context-param><param-name><param-value>
spring 在web项目中配置<context-param><param-name>contextConfigLocation<param-value>classpath:applicationContext.xml
获得web项目资源
String getRealPath(java.lang.String path) 获得发布到tomcat下的指定资源路径
例如:getRealPath("/WEB-INF/web.xml")
获得实际路径:D:\java\tomcat\apache-tomcat-7.0.53\webapps\TestServlet\WEB-INF\web.xml
InputStream getResourceAsStream(java.lang.String path) 与getRealPath使用相同,返回不同
getResourcePaths(java.lang.String path) 指定路径下的所有内容。
例如:getResourcePaths("/WEB-INF/")