Servle详解
文章目录
1. Servlet
Servlet的规范是有javax包下的servlet接口制定的,它提供了几个方法(这几个方法代表了一个servlet的生命周期)。
package javax.servlet;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
1.1 自定义Servlet
Java提供了一个javax.servlet
接口,我们自定义的servlet类需要实现这个接口,并覆写其中的方法。
public class HelloServlet implements Servlet {
//构造方法,第一次访问该servlet时调用的
public HelloServlet(){
System.out.println("constructor");
}
//初始化方法,在构造方法后调用
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init");
}
//获得Servlet配置信息的方法
@Override
public ServletConfig getServletConfig() {
System.out.println("getServletConfig");
return null;
}
//servlet处理前台请求的方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service");
}
////获得Servlet的信息的方法
@Override
public String getServletInfo() {
System.out.println("getServletInfo");
return null;
}
//销毁Servlet对象的方法,正常关闭服务器的时候执行
@Override
public void destroy() {
System.out.println("destroy");
}
}
- web.xml中配置自定义的servlet
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.yogie.lesson.HelloServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
<!--通常在eclipse中,web.xml文件中xml约束添加在web-app标签中,如果要使用注解方式配置servlet,则需要将约束中添加metadata-complete="false"-->
1.2 Servlet生命周期
①先执行构造方法 - 第一次访问
②执行初始化方法(init) - 第一次访问
③执行的服务方法(service) - 第n次访问
④对于tomcat来说,Servlet只有一个(它是单例的,使用的是缓存)
⑤当我们正常关闭tomcat的时候,执行destroy方法
⑥当浏览访问的时候才开始创建Servlet
1.3 ServletConfig接口
ServletConfig只封装了4个方法:
String getInitParameter(String name) 根据web.xml中<init-param>标签下的参数名称获取对应的值。
Enumeration getInitParameterNames() 获取所有的参数名称。
ServletContext getServletContext() 获取servlet的上下文对象。
String getServletName() 获取servlet的名称(配置的servlet名称,不是类名)
- Enumeration类:类似于迭代器,用于迭代获取到的参数名称。
boolean hasMoreElements() 测试此枚举类是否还有下一个元素
E nextElement() 返回此枚举的下一个元素。
1.4 钩子方法的设计
钩子方法的设计主要是模板方法设计模式的使用。模板方法设计模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。更进一步来说,模板方式模式就是通过不变的行为搬移到超类,去除子类中的重复代码来体现它的优势。
在应用方面上,当功能的内部一部分实现是确定的,但同时也有一些是不确定的,这时就可以将不确定的部分暴露出去,让子类去实现。例如HttpServlet中service方法中确定的功能就是要将ServletRequest
强制转换成HttpServletRequest
,而不确定的功能就是对请求的处理。
- HttpServlet类的部分源码:
/**
* public abstract class GenericServlet implements Servlet, ServletConfig, Serializable
* 抽象类GenericServlet实现了Servlet、ServletConfig、Serializable接口
*/
public abstract class HttpServlet extends GenericServlet {
//对方法的参数进行强转
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");
}
}
//提供给子类覆写的方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取前台form表单的提交方式
String method = req.getMethod();
long lastModified;
//如果前台form表单以get方式提交,则调用doGet方法
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);
}
}
doGet()...doPost()...doDelete()...
//doGet方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取http协议和版本,例如:HTTP/1.1
String protocol = req.getProtocol();
//定义异常信息
String msg = lStrings.getString("http.method_get_not_supported");
//如果是http1.1版本
if (protocol.endsWith("1.1")) {
//抛出405异常
resp.sendError(405, msg);
} else {
//抛出400异常
resp.sendError(400, msg);
}
}
}
- 我们定义的子类仅仅需要覆写前台提交对应的方法即可。
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doDelete(req, resp);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
}
代码的执行流程分析:
当浏览器访问到当前的servlet类时,首先访问的是类中带ServletRequest
和ServletResponse
参数的service方法,但是此时子类没有,就执行从父类(HttpServlet)
继承来的service方法,此时HttpServlet
中的service方法将参数强转为HttpServletRequest
与HttpServletResponse
,并且调用重载一个带HttpServletRequest
与HttpServletResponse
参数类型的service方法。在这个重载的方法内部会根据前台的请求方法(get/post...)
调用对应的(doGet/doPost...)
方法。此时调用的方法即是子类覆写父类的doXXX()
方法,如果没有覆写对应的方法,那么还是调用从父类继承来的方法,由于父类的方法是直接返回的错误。
2. 处理请求
2.1 HttpServerRequest
接口
该接口继承自ServletRequest
,ServletRequest
对象提供包括浏览器传递的参数名称、参数值、属性和输入流的数据。HttpServletRequest
在ServletRequest
类的基础上提供了包含HTTP协议请求的相关信息。HttpServletRequest
对象封装了客户端提交过来的一切数据。
Map getParameterMap() 以map集合的形式返回请求参数、表单提交的数据(结合beanutils使用)。
String getParameter(String name) 根据请求参数名获取前台传递的参数值。如果参数可能拥有一个以上的值,则使用getParameterValues ()方法。
String[] getParameterValues(String name):根据参数名称,获取该参数的多个值。
Enumeration<String> getParameterNames():获取所有请求参数的名字。
Map<String,String[]> getParameterMap():返回请求参数组成的Map集合。
String getContextPath() 返回上下文路径(<Context path="上下文" ../>)。
String getCharacterEncoding() 返回此请求正文使用的字符编码。
void setAttribute(String name, Object o) 返回指定名称的属性值。
void removeAttribute(String name) 从此请求中移除指定属性。
void setCharacterEncoding(String env) 重写此请求正文中使用的字符编码的名称。必须在使用 getReader() 读取请求参数或读取输入之前调用此方法。
Cookie[] getCookies() 返回包含客户端随此请求一起发送的所有 Cookie 对象的数组。如果没有发送任何 cookie,则此方法返回 null。
String getProtocol() 获取协议及版本号。
String getMethod() 获取请求提交的方法。
String getRequestURI() 返回当前请求的资源名称,上下文路径/资源名
StringBuffer getRequestURL() 返回浏览器地址栏的内容。
String getRemoteAddr():返回请求服务器的客户端的IP
HttpSession getSession() 返回与此请求关联的当前会话,如果该请求没有会话,则创建一个会话。
HttpSession getSession(boolean create) 返回与此请求关联的当前 HttpSession,如果没有当前会话并且 create 为 true,则返回一个新会话。 如果 create 为 false 并且该请求没有有效的 HttpSession,则此方法返回 null。
public RequestDispatcher getRequestDispatcher(String path)
2.2 HttpServletResponse
接口
该接口继承了ServletResponse
接口,ServletResponse
对象提供了将响应发送到浏览器的方法。
void setCharacterEncoding(String charset) 设置响应的编码方式(写在最前面)。
resp.setContentType("text/html;charset=UTF-8"); 甚至的内容类型。
ServletOutputStream getOutputStream() 返回适用于在响应中编写的二进制输出流。
PrintWriter getWriter() 将字符文本返回给浏览器。
void addCookie(Cookie cookie) 将指定的cookie添加到响应。
void sendRedirect(String location) 重定向。
3. Servlet配置细节
3.1 配置多个映射路径
1、在同一个servlet-mapping标签中配置多个url-pattern。
2、同一个name的servlet对应多个servlet-mapping。
3.2 通配符
/* 统配所有路径。
/system/* 权限验证。
*.do或者*.action 通配特有后缀的servlet请求。
3.3 初始化配置
<!--服务器一开启就创建-->
<servlet>
<servlet-name></servlet-name>
<servlet-class>com.yogie.MyServelt</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
4. 请求转发与重定向
4.1 转发
//可以访问静态资源
request.getRequestDispatcher("/jsp/show.jsp").forward(request,response);
//可以访问WEB-INF下的资源(访问路径不用跟项目名)
request.getRequestDispatcher("/WEB-INF/list.jsp").forward(request,response);
//跳转到另一个请求
request.getRequestDispatcher("/forward/a").forward(request,response);
- 由request对象发起。
- 地址栏不变化。
- 浏览器只发起一次请求。
- 转发是同一个请求对象(请求头共享),即请求携带的数据是相同的。
- 按照请求的顺序,最后一个
servlet
中的response
对象才起作用。 forward
不可以跨域访问。- 可以访问到
WEB-INF
中的资源。
4.2 重定向
//访问路径要跟项目名,且不能访问WEB-INF目录下的资源
response.sendRedirect("/projectname/jsp/show.jsp");
//等同于浏览器再一次发起一次请求
response.sendRedirect("/projectname/redirect/a");
- 由response对象发起。
- 地址栏会发生变化。
- 浏览器发起多次请求,每重定向一次就发送一次请求。
- 请求对象不一样,请求头不共享。
- 重定向也是最后一个
servlet
中的response
对象才起作用。 - 重定向可以跨域访问。
- 不能访问到
WEB-INF
下的资源。
4.3 使用
如果我们需要做请求的数据共享使用forward,如果我们需要访问WEB-INF里面只有使用forward,如果我们要跨域,必须使用Redirect,其他情况,随便(多数使用Redirect)。
4.4 请求包含
request.getRequestDispatcher("/include/b").include(req, resp);
//结果会使得两个servlet包含在一起。
5. 四大作用域对象
域对象 | 类 | 作用域 |
---|---|---|
pageContext | PageContext | 当前页面 |
Request | HttpRequest | 一次请求 |
Session | HttpSession | 一次会话 |
Application | ServletContext | 整个应用 |
pageContext
:每次访问的时候创建,可以有多个;Request
:每次请求的时候创建,可以有多个;Session
:每次会话的时候创建,不同人打开不同的浏览器,就创建多个,可以有个多个;Application
:整个应用有且只有一个对象,tomcat启动的创建,关闭的时候销毁;
5.1 PageContext
abstract ServletRequest getRequest() 获取请求对象。
abstract ServletResponse getResponse() 获取响应对象。
abstract ServletConfig getServletConfig() 获取当前请求的配置对象。
abstract ServletContext getServletContext() 获取Application域对象。
abstract HttpSession getSession() 返回session对象。
5.2 HttpRequest
String getContextPath() 返回上下文路径(默认是单斜杠+项目名),实际上是tomcat服务器中的server.xml文件中<Context path="上下文" ../>。
Cookie[] getCookies() 返回所有的cookie对象
String getHeader(String name) 根据请求头名字获取对应的值。
Enumeration getHeaderNames() 获取所有的请求头名字。
Enumeration getHeaders(String name) 根据请求头的名字获取对应的值。
String getMethod() 获取表单提交的方法(POST/GET...)。
String getRequestURI() 以String类型返回前台访问的资源的路径。
StringBuffer getRequestURL() 以StringBuffer类型返回前台访问的资源的路径。
HttpSession getSession() 如果有,返回;如果没有,返回新创建的。
HttpSession getSession(boolean create) 如果有,返回;如果没有,返回null。
5.3 HttpSession
void setAttribute(String name, Object value) 将对象添加到session中。
Object getAttribute(String name) 从session中获取指定名称的对象。
Enumeration getAttributeNames() 返回所有的session属性名称。
long getCreationTime() 以毫秒值返回session创建的时间。
String getId() 获取session对象的id。
ServletContext getServletContext() 获取Application域对象。
void removeAttribute(String name) 移除指定名称的session属性。
5.4 ServletContext
ServletContext
接口:服务器启动的时候,会为托管的每一个web应用程序,创建一个ServletContext
对象,从服务器移除托管,或者是关闭服务器。
Object getAttribute(String name) 根据指定的属性名获取属性值。
void removeAttribute(String name) 移除指定属性。
void setAttribute(String name, Object object) 设置属性。
Enumeration getAttributeNames() 获取所有的属性名。
ServletContext getContext(String uripath) 根据容器中另一个Web应用程序的上下文路径返回Application域对象。
String getServletContextName()返回web.xml中<display-name>标签配置的名字。
String getContextPath() 返回上下文路径(项目的根路径)。
String getRealPath(String path) 根据虚拟路劲返回绝对路径。
Set getResourcePaths(String path) 返回指向Web应用程序中资源的所有路径的类似目录的清单,这些路径中最长的子路径与提供的 path 参数匹配。
URL getResource(String path)返回指向映射到指定路径的资源的URL。
String getInitParameter(String name) 返回web.xml中<context-param>标签配置的信息。
Enumeration getInitParameterNames() 返回web.xml中<context-param>标签配置的信息。
RequestDispatcher getNamedDispatcher(String name) 返回充当指定servlet的包装器的 RequestDispatcher对象。
RequestDispatcher getRequestDispatcher(String path) 请求转发使用
InputStream getResourceAsStream(String path) 以流的形式返回指定路径的资源。
String getServerInfo() 返回正在其上运行 servlet 的 servlet 容器的名称和版本。
6. Servlet线程安全问题
Servlet是线程不安全的,解决方案:
1、在Servlet中不定义字段。
2、在继承HttpServlet的同时,实现SingleThreadModel接口
3、将操作字段的代码提取出来构造一个加锁的静态方法。