Servlet
什么是Servlet
Servlet是JavaWeb三大组件之一,它属于动态资源。接收浏览器请求,并作出响应。
Servlet由我们自己编写,必须实现javax.servlet.Servlet接口
public interface Servlet {
/** Called by the servlet container to indicate to a servlet that the servlet is being placed into service. 生命周期方法,调用一次,注意传入的参数 ServletConfig,我们可以在init中将其保存下来。*/
void init(ServletConfig var1) throws ServletException;
/** Returns a ServletConfig object, which contains initialization and startup parameters for this servlet. */
ServletConfig getServletConfig();
/** Called by the servlet container to allow the servlet to respond to a request. 具体的处理浏览器的请求的方法。 */
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
/** Returns information about the servlet, such as author, version, and copyright. */
String getServletInfo();
/** Called by the servlet container to indicate to a servlet that the servlet is being taken out of service. 生命周期方法,销毁时调用,可做一些资源释放操作。*/
void destroy();
}
Servlet实现
完全手动实现
-
新建AServlet实现Servlet interface
public class AServlet implements Servlet { /** * 生命周期之内,只会执行一次。此方法并不是服务器启动即开始调用,默认只有在第一次访问时创建。 * 但可以通过在web.xml中配置达到一启动服务就加载此Servlet的方法 */ @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("Init success"); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { PrintWriter writer = servletResponse.getWriter(); writer.println("<h1> Hello My Servlet <h1>"); } @Override public String getServletInfo() { return null; } /** * 临死之前,执行一次。可以用来做一些资源释放的操作。 */ @Override public void destroy() { System.out.println("I am done."); } }
-
在web.xml中注册
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>AServlet</servlet-name> <servlet-class>cn.edu.ustc.AServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AServlet</servlet-name> <url-pattern>/AServlet</url-pattern> </servlet-mapping> </web-app>
-
访问
Servlet的生命周期
- 出生:一个Servlet类型,服务器只创建一个实例对象。 例如在我们首次访问时,服务器通过“/AServlet”找到了绑定的Servlet名称为AServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器才会通过反射来创建AServlet的实例。当我们再次访问时,服务器就不会再次创建AServlet实例了,而是直接使用上次创建的实例
- 服务:服务器接收到一次请求,就会调用service() 方法一次
- 销毁:Servlet是不会轻易离去的,通常都是在服务器关闭时Servlet才会离去!在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法
ServletConfig
ServletConfig对应web.xml中的<servlet>元素,由服务器创建。我们可以在init方法中,像GenericServlet那样保存下来。
public interface ServletConfig { /** Returns the name of this servlet instance. */ String getServletName(); /** Returns a reference to the ServletContext in which the caller is executing. */ ServletContext getServletContext(); /** Gets the value of the initialization parameter with the given name. */ String getInitParameter(String var1); /** * Returns the names of the servlet's initialization parameters as an Enumeration * of String objects, or an empty Enumeration if the servlet has no initialization * parameters. */ Enumeration<String> getInitParameterNames(); }
如果在web.xml的servlet标签下面我们添加了额外的参数,我们就可以通过getInitParameter()方法获得
<servlet> <servlet-name>AServlet</servlet-name> <servlet-class>cn.edu.ustc.AServlet</servlet-class> <init-param> <param-name>param-name</param-name> <param-value>param-value</param-value> </init-param> </servlet>
JavaEE自带实现
GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
/**
* 保存ServletConfig
*/
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
/**
* 子类继承时如果需要重写init方法,最好覆盖此方法。覆盖上面init也可以,但一定要有super,否则成员变量
* config不会被赋值,其他的config相关方法也无法正确使用。
*/
public void init() throws ServletException {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public String getServletName() {
return this.config.getServletName();
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public ServletConfig getServletConfig() {
return this.config;
}
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;
}
通过分析代码可以发现,GenericServlet主要做了两件事
- 保存系统生成的ServletConfig,并实现ServletConfig接口,子类无需再获取ServletConfig对象后再调用其中方法,直接调用GenericServlet中的ServletConfig实现接口方法即可。
- 增加了日志方法。注意:ServletContext.log()方法,是根据tomcat目录下conf中logging.properties写入本地文档,一般来讲可以在tomcat下的logs中找到。但是如果是使用的idea,我们配置的tomcat目录并不会被直接使用,而是复制了一份放在了~/.intelliJIdea/system/tomcat中,所以如果调用了servletContext.log()或者GenericServlet中的log方法,需要注意日志所在的目录。
HttpServlet
HttpServlet是GenericServlet的子类,提供了对Http协议的特殊支持,而这正是我们BS所需要的!所以我们一般会通过继承HttpServlet来完成自定义的Servlet。
public abstract class HttpServlet extends GenericServlet {
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException(lStrings.getString("http.non_http"));
}
this.service(request, response);
}
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;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
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);
}
}
}
由以上代码可知,HttpServlet核心操作
- 重写service方法,首先判断是否是Http层的请求,是则进入本类中的service方法,否则抛出异常
- 本类中的service方法,对Http请求方式判别后,调用相应的处理方法
主要关心下get方式所作的工作,可以通过一张流程图理解
ServletContext
一个项目只能有一个ServletContext,不管使用的什么方法获取的ServletContext都是同一个。
作用:在整个Web应用的动态资源中传递数据。
- ServletContext在项目启动时创建
- 在项目关闭时销毁
ServletContext获取
- ServletConfig # getServletContext();
- GenericServlet#getServletContext();
- HttpSession#getServletContext();
- ServletContextEvent#getServletContext();
域对象
域对象就是在多个Servlet中传递数据,ServletContext即为域对象之一。
四个域对象
- PageContext;
- ServletRequest;
- HttpSession;
- ServletContext;
存取方法:
- void setAttribute(String name, Object value)
- Object getAttribute(String name)
- void removeAttribute(String name)
- Enumeration getAttributeNames():获取所有域属性的名称
在Servlet中获取资源的方法
在获取指定资源之前,一定要搞清楚资源所在位置,以及资源相对于用来获取资源的对象之间的相对位置关系。
-
获取真实路径
getServletContext().getRealPath("/b.txt"); getServletContext().getRealPath("/WEB-INF/a.text");
-
获取资源流
InputStream in = servletContext.getResourceAsStream(“/a.txt”); InputStream in = servletContext.getResourceAsStream(“/WEB-INF/b.txt”);
-
获取指定目录下所有资源路径
/** 注意此方法必须以斜杠开头 */ Set set = context.getResourcePaths("/WEB-INF");
-
通过ClassLoad获取资源(注意与通过class获取的区别)
ClassLoader classLoader = this.getClass().getClassLoader(); // 以斜杠开头:斜杠表示classes目录 // 若想过去b.txt a.txt classLoader.getResourceAsStream("/../../b.txtt"); classLoader.getResourceAsStream("/../a.txt");
Servlet总结
-
Servlet是线程安全的吗?
一个类型的Servlet只会有一个实例对象,那么必然会出现一个Servlet对象处理多个请求,请求可能来自多个线程,那就面临并发修改问题。
所以:不应该在Servlet中便宜创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。那为什么不做成线程安全的?因为效率问题。
-
让服务器在启动时就创建Servlet
默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。
<servlet> <servlet-name>AServlet</servlet-name> <servlet-class>cn.edu.ustc.AServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet> <servlet-name>BServlet</servlet-name> <servlet-class>cn.edu.ustc.BServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
在<servlet>元素中配置<load-on-startup>元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是大于等于0整数,它的使用是服务器启动时创建Servlet的顺序。上例中,根据<load-on-startup>的值可以得知服务器创建Servlet的顺序为AServlet、bServlet。
-
url-pattern