一、Servlet概述
一个类型的Servlet只有一个实例对象,就会现时出一个Servlet同时处理多个请求。多个线程公用一个Servlet对象,所以Servlet是线程不安全的。但Servlet的工作效率很高
所以不应该在Servlet中创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。
特性:
单例(第一次访问时,被服务器创建),一个类只有一个对象;当然可能存在多个Servlet类,线程不安全的,效率是高的!
Servlet实现类由用户实现,但对象由服务器来创建,并且由服务器来调用相应的方法。
Servlet介绍
Servlet是JavaWeb的三大组件之一,它属于动态资源。Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:
接收请求数据;处理请求;完成响应。
例如客户端发出登录请求,这些请求都由Servlet来完成处理。Servlet需要用户自己来编写,每个Servlet必须实现javax.servlet.Servlet接口。
Servlet实现方式
实现Servlet有三种方式:
- 实现javax.servlet.Servlet接口;
- 继承javax.servlet.GenericServlet类;
- 继承javax.servlet.http.HttpServlet类;
通常我们继承HttpServlet类来实现Servlet
package javax.servlet;import java.io.IOException;public interface Servlet {public void init(ServletConfig config) throws ServletException;public ServletConfig getServletConfig();public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;public String getServletInfo();public void destroy();}
1.实现Servlet接口
public class DemoServlet implements Servlet {public void init(ServletConfig config) throws ServletException {}public ServletConfig getServletConfig() {return null;}public void destroy() {}public String getServletInfo() {return null;}//用来处理请求的方法public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException {System.out.println("hello servlet!");}}
配置web.xml
将访问路径与Servlet绑定到一起。即将访问路径:/demoworld与com.masterlu.servlet.DemoServlet绑定
<servlet><servlet-name>helloServlet</servlet-name><servlet-class>com.masterlu.servlet.DemoServlet</servlet-class></servlet><servlet-mapping><servlet-name>demo</servlet-name><!-- 指定Servlet的访问路径(必须以/开头),可以配置多个,多个路径指向同一个Servlet。也可以使用通配符配置路径(通配符不能出现在中间,只能作为前缀或后缀)。--><url-pattern>/demoworld</url-pattern><url-pattern>/demoworld2</url-pattern><url-pattern>/servlet/*<url-patter><url-pattern>*.do</url-pattern></servlet-mapping>
2.继承GenericServlet
GenericServlet是Servlet接口的实现类,也可以通过继承GenericServlet来编写自己的Servlet。
package javax.servlet;import java.io.IOException;import java.util.Enumeration;import java.util.ResourceBundle;public abstract class GenericServletimplements 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() { }@Overridepublic void destroy() {}@Overridepublic String getInitParameter(String name) {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getInitParameter(name);}@Overridepublic Enumeration<String> getInitParameterNames() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getInitParameterNames();}@Overridepublic ServletConfig getServletConfig() {return config;}@Overridepublic ServletContext getServletContext() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getServletContext();}@Overridepublic String getServletInfo() {return "";}@Overridepublic 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);}@Overridepublic abstract void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;@Overridepublic String getServletName() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getServletName();}}
GenericServlet的init()方法
在GenericServlet中,定义ServletConfig config实例变量,并在init(ServletConfig)方法中把参数ServletConfig赋给了实例变量。然后该类的很多方法使用实例变量config。则子类需要完成一些初始化操作,只能覆盖GenericServlet提供的init()方法。它是没有参数的init()方法,它会在init(ServletConfig)方法中被调用。
如果子类覆盖GenericServlet的init(StringConfig)方法,那么this.config=config这一条语句就会被覆盖了,则config的值为null,那么所有依赖config的方法都不能使用。
实现了ServletConfig接口
GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getServletContext()等ServletConfig的方法。
3.继承HttpServlet(常用方式)
HttpServlet类是GenericServlet的子类,提供了对HTTP请求的特殊支持,通常都会通过继承HttpServlet来完成自定义的Servlet。(SpringMVC的Servlet也是继承HttpServlet)
package javax.servlet.http;import javax.servlet.*;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(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<? extends HttpServlet> c) {Class<?> clazz = c;Method[] allMethods = null;while (!clazz.equals(HttpServlet.class)) {Method[] thisMethods = clazz.getDeclaredMethods();if (allMethods != null && allMethods.length > 0) {Method[] subClassMethods = allMethods;allMethods =new Method[thisMethods.length + subClassMethods.length];System.arraycopy(thisMethods, 0, allMethods, 0,thisMethods.length);System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length,subClassMethods.length);} else {allMethods = thisMethods;}clazz = clazz.getSuperclass();}return ((allMethods != null) ? allMethods : new Method[0]);}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++) {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;}}// we know "allow" is not null as ALLOW_OPTIONS = true// when this method is invokedStringBuilder allow = new StringBuilder();if (ALLOW_GET) {allow.append(METHOD_GET);}if (ALLOW_HEAD) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_HEAD);}if (ALLOW_POST) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_POST);}if (ALLOW_PUT) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_PUT);}if (ALLOW_DELETE) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_DELETE);}if (ALLOW_TRACE) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_TRACE);}if (ALLOW_OPTIONS) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_OPTIONS);}resp.setHeader("Allow", allow.toString());}protected void doTrace(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{int responseLength;String CRLF = "\r\n";StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI()).append(" ").append(req.getProtocol());Enumeration<String> reqHeaderEnum = req.getHeaderNames();while( reqHeaderEnum.hasMoreElements() ) {String headerName = reqHeaderEnum.nextElement();buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));}buffer.append(CRLF);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();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 logicdoGet(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 lessmaybeSetLastModified(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);}}/*** Dispatches client requests to the protected* <code>service</code> method. There's no need to* override this method.** @param req the {@link HttpServletRequest} object that* contains the request the client made of* the servlet** @param res the {@link HttpServletResponse} object that* contains the response the servlet returns* to the client** @throws IOException if an input or output error occurs* while the servlet is handling the* HTTP request** @throws ServletException if the HTTP request cannot* be handled or if either parameter is not* an instance of its respective {@link HttpServletRequest}* or {@link HttpServletResponse} counterparts.** @see javax.servlet.Servlet#service*/@Overridepublic 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);}}
HttpServlet#service(HttpServletRequest,HttpServletResponse)方法,判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法。用户在子类中去覆盖doGet()或doPost()方法即可
二、Servlet生命周期
生命周期相关方法
void init(ServletConfig);
void service(ServletRequest,ServletResponse);
void destroy();
1.Servlet初始化方法
默认情况下,Servlet实例,在Servlet第一次被访问时,由服务器创建(或者在web.xml文件中配置,使得在服务器启动时创建Servlet实例)。
一个Servlet类型,服务器只创建一个实例对象,例如在首次访问http://localhost:8080/appName/demoworld时,服务器通过“/helloworld”找到绑定的Servlet名称为com.masterlu.servlet.DemoServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器会通过反射来创建DemoServlet的实例。再次访问http://localhost:8080/helloservlet/helloworld时,服务器就不会再次创建DemoServlet实例了,而是直接使用上次创建的实例。
在Servlet被创建后,服务器会立刻调用Servlet的void init(ServletConfig)方法。init方法只会被调用一次。
利用这个特性,可以把Servlet的初始化工作放到init方法中。
<servlet><servlet-name>helloServlet</servlet-name><servlet-class>com.masterlu.servlet.DemoServlet</servlet-class><!-- 服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是大于等于0的整数,数字越小优先级越高--><load-on-startup>0</load-on-startup></servlet>
2.Servlet服务方法
用户每次请求服务器时,服务器都会调用Servlet#service()方法来处理请求。因为service()方法可以被多次调用,所以我们才需要把处理请求的代码写到service()方法中。
3.Servlet销毁方法
在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,我们可以把Servlet的销毁逻辑放到destroy()方法中,例如对某些资源的释放等代码放到destroy()方法中。
三、Servlet接口用到的对象
ServletConfig
init()方法的参数,表示Servlet配置对象,它对应Servlet的配置信息,即:对应web.xml的
ServletRequest
由服务器创建的请求对象,它封装了所有与请求相关的数据;
ServletResponse
由服务器创建的响应对象,在service()方法中完成对客户端的响应,需要使用这个对象;
ServletRequest和ServletResponse的实例由服务器创建,然后传递给Servlet#service() 。一个是请求对象,一个是响应对象,可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。
如果在service() 方法中使用HTTP相关的功能,可以把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse。
HttpServletRequest方法
String getParameter(String paramName)
获取指定请求参数的值;
String getMethod()
获取请求方法,例如GET或POST;
String getHeader(String name)
获取指定请求头的值;
void setCharacterEncoding(String encoding)
设置请求体的编码!因为GET请求没有请求体,所以这个方法仅对POST请求有效。当调用request.setCharacterEncoding(“utf-8”)之后,再调用getParameter()方法获取参数值时,则参数值都已经转换成UTF-8编码。所以这个方法必须在调用getParameter()方法之前调用!
HttpServletResponse方法
PrintWriter getWriter()
获取字符响应流,使用该流向客户端输出响应信息。例如response.getWriter().print(“
Hello JavaWeb!
”);ServletOutputStream getOutputStream()
获取字节响应流,使用该流向客户端响应字节数据,例如要向客户端响应图片;
void setCharacterEncoding(String encoding)
用来设置字符响应流的编码,例如在调用setCharacterEncoding(“utf-8”);之后,再response.getWriter()获取字符响应流对象,响应流的编码为utf-8,使用response.getWriter()输出的中文都会转换成utf-8编码后发送给客户端;
void setHeader(String name, String value)
向客户端添加响应头信息,例如setHeader(“Refresh”, “3;url=http://www.baidu.com”),表示3秒后自动刷新到http://www.baidu.com;
void setContentType(String contentType)
是setHeader(“content-type”, “xxx”)的简便方法,即用来添加名为content-type响应头的方法。content-type响应头用来设置响应数据的MIME类型,
例如要向客户端响应jpg的图片,那么可以setContentType(“image/jepg”),
如果响应数据为文本类型,且同时设置编码,例如setContentType(“text/html;chartset=utf-8”)表示响应数据类型为文本类型中的html类型,并且该方法会调用setCharacterEncoding(“utf-8”)方法;
void sendError(int code, String errorMsg)
向客户端发送状态码,以及错误消息。例如给客户端发送404:response(404, “您要查找的资源不存在!”)。
ServletConfig
对应web.xml文件中的
String getServletName()
获取Servlet在web.xml文件中的配置名称,即
ServletContext getServletContext()
用来获取ServletContext对象。
String getInitParameter(String name)
用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值;
Enumeration getInitParameterNames()
用来获取在web.xml中配置的所有初始化参数名称;
<servlet><servlet-name>helloServlet</servlet-name><servlet-class>com.masterlu.servlet.DemoServlet</servlet-class></servlet><init-param><param-name>paramName1</param-name><param-value>paramValue1</param-value></init-param><init-param><param-name>paramName2</param-name><param-value>paramValue2</param-value></init-param></servlet>
四、web.xml文件继承
每一个javaWeb应用中都需要有web.xml文件,应用中的web.xml都有一个共同的父文件(tomca的conf/web.xml)。
tomca中conf/web.xml相当于写到项目中web.xml中
<?xml version="1.0" encoding="ISO-8859-1"?><web-app xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0"><!-- 它的优先级最低的Servlet1.当访问路径不存在时,会执行该Servlet,2.在访问index.html时也是在执行这个Servlet。--><servlet><servlet-name>default</servlet-name><servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class><init-param><param-name>debug</param-name><param-value>0</param-value></init-param><init-param><param-name>listings</param-name><param-value>false</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet><servlet-name>jsp</servlet-name><servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class><init-param><param-name>fork</param-name><param-value>false</param-value></init-param><init-param><param-name>xpoweredBy</param-name><param-value>false</param-value></init-param><load-on-startup>3</load-on-startup></servlet><!-- 匹配所有URL,即用户访问的URL路径没有匹配的页面时,那么执行的就是名为default的Servlet,即org.apache.catalina.servlets.DefaultServlet --><servlet-mapping><servlet-name>default</servlet-name><url-pattern>/</url-pattern></servlet-mapping><!-- 任何URL后缀为jsp的访问,都会执行名为jsp的Servlet,即org.apache.jasper.servlet.JspServlet--><servlet-mapping><servlet-name>jsp</servlet-name><url-pattern>*.jsp</url-pattern><url-pattern>*.jspx</url-pattern></servlet-mapping><!-- session的默认超时时间为30分钟 --><session-config><session-timeout>30</session-timeout></session-config><!-- 省略了大多数tomcat配置的MIME类型的定义,这里只给出两种MIME类型的定义 --><mime-mapping><extension>bmp</extension><mime-type>image/bmp</mime-type></mime-mapping><mime-mapping><extension>htm</extension><mime-type>text/html</mime-type></mime-mapping><!-- 设置默认主页为index.html、index.html、index.jsp --><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file></welcome-file-list></web-app>
五、ServletContext
一个项目只有一个ServletContext对象,在整个Web应用的动态资源之间实现共享数据。可以在所有Servlet中来获取这个唯一的对象,使用它可以给多个Servlet传递数据!对象在Tomcat启动时就创建,在Tomcat关闭时销毁!
获取ServletContext
ServletConfig#getServletContext();
GenericServlet#getServletContext();
HttpSession#getServletContext()
ServletContextEvent#getServletContext()
域对象的功能
JavaWeb四大域对象
PageContext、ServletRequest、HttpSession、ServletContext;
所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据。
ServletContext对象用来操作数据的方法:
void setAttribute(String name, Object value)
存储一个域属性。如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值;
Object getAttribute(String name)
获取ServletContext中的数据
void removeAttribute(String name)
用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
Enumeration getAttributeNames()
获取所有域属性的名称;
获取应用初始化参数
配置公共的初始化参数,为所有Servlet而用!需要使用ServletContext才能使用
<web-app ...>
<context-param>
<param-name>paramName1</param-name>
<param-value>paramValue1</param-value>
</context-param>
<context-param>
<param-name>paramName2</param-name>
<param-value>paramValue2</param-value>
</context-param>
</web-app>
ServletContext context = this.getServletContext();
String value1 = context.getInitParameter("paramName1");
String value2 = context.getInitParameter("paramName2");
System.out.println(value1 + ", " + value2);
Enumeration names = context.getInitParameterNames();
while(names.hasMoreElements()) {
System.out.println(names.nextElement());
}
