Servlet的创建
创建Servlet一共有3种方式:
- 实现Servlet接口
- 继承GenericServlet
- 继承HttpServlet
在Servlet接口中,有5个方法需要我们实现的,而其中有3个方法是生命周期方法。
void init(ServletConfig var1) throws ServletException; //生命周期方法,在创建Servlet的构造方法执行之后,就会执行这一个方法。只会执行1次
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;//生命周期方法,每次调用都会执行这个方法,所以会执行多次
String getServletInfo();
void destroy();//生命周期方法,在销毁Servlet之前,需要执行这一步
所以我们要实现Servlet接口来创建Servlet的时候,那么就需要实现上面的方法。
package bean;
import javax.servlet.*;
import java.io.IOException;
public class MyServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init.....");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service.....");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy......");
}
}
创建完毕之后,在web.xml文件中配置相关的内容(将下面的代码添加到web.xml中),开始tomcat服务器,那么就可以看见控制台中输出对应的信息:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>bean.MyServlet</servlet-class> <!--这个是我们定义的servlet的子类的路径-->
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name> <!--这个名字和上面的servlet标签中servlet-name的值要相同,否则没有办法找到我们自定义的servlet-->
<url-pattern>/MyServlet</url-pattern><!--这个是我们在浏览器中输入的url-->
</servlet-mapping>
测试结果:
而如果我们需要利用继承GenericServlet的方式来创建Servlet,那么我们可以来看以下GenericServlet的方法有哪些:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
//生命周期方法,在销毁Servlet之前执行这一步。只会执行1次
}
public String getInitParameter(String name) {
//获取初始参数name对应的值
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
//获取所有的初始化参数的名字
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
//生命周期方法,当构造方法执行完毕之后,就会执行这一步。只会执行1次
this.config = config;
this.init();
}
public void init() throws ServletException {
//自己定义的init方法,不会有服务器调用,所以不是生命周期方法
}
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() {
//获取Servlet的名字
return this.config.getServletName();
}
}
可以知道GenericServlet实现了Servlet和ServletConfig接口,所以这个类中看可以含有Servlet接口的方法,同时也会含有ServletConfig的方法。
我们主要来看一下GenericServlet中的init(ServletConfig config)方法。
public void init(ServletConfig config) throws ServletException {
//生命周期方法,当构造方法执行完毕之后,就会执行这一步。只会执行1次
this.config = config;
this.init();
}
public void init() throws ServletException {
//自己定义的init方法,不会有服务器调用,所以不是生命周期方法
}
在GenericServlet类中,定义了一个全局变量名为config的ServletConfig对象,然后再这个方法将参数赋值给他,然后再调用自己定义init()。而在其他的方法中,就可以利用全局变量config来获取相关的信息,例如getServletContext().但是为什么还要自定义一个init()呢?
这是因为避免我们子类中将init(ServletConfig config)方法重写,从而导致父类中的config没有被赋值,从而导致config为null,然后再getServletContext()这些方法中由于config为null,直接调用getServletContext()就会发生报错。并且config这个变量是一个私有的,子类虽然可以拥有这个变量,但是不可以直接访问,所以即使在重写,操作也会相对麻烦一些。所以我们为了维护这个config,就定义了init无参方法,我们子类重写的就是这个无参的init方法。
介绍完GenericServlet之后,我们就来学习继承HttpServlet来创建Servlet:
public abstract class HttpServlet extends GenericServlet {
private static final long serialVersionUID = 1L;
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 final 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 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 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);
}
}
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("non-HTTP request or response");
}
this.service(request, response);
}
}
参考了HttpServlet的源码,可以知道HttpServlet继承于GenericServlet,所以HttpServlet拥有GenenricServlet的所有属性和方法。同时HttpServlet的特殊之处在于它有2个Service方法。其中service(ServletRequest,ServletResponse)才是生命周期方法,在service(ServletRequest,ServletResponse)方法,将参数进行类型强制转换成为HttpServletRequest,HttpServletResponse,然后调用service(HttpServletRequest,HttpServletResponse),并且在service(HttpServletRequest,HttpServletResponse)方法中,有request来获取请求的方法,从而判断请求方式,从而执行对应的响应。例如请求的方法为GET,那么就会执行doGet方法。所以我们来继承HttpServlet来创建Servlet的时候,只需要重写doGet,或者doPost方法即可。如果我们请求的是GET,并且我们子类中没有重写doGet方法时,那么就会发生405错误。doPost同理。
public class MyHttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//如果没有重写doGet方法,那么发送的是GET请求的时候,就会发生405错误
//System.out.println("doGet");
super.doGet(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//如果没有重写doGet方法,那么发送的是POST请求的时候,就会发生405错误
System.out.println("doPost");
//super.doPost(req,resp);
}
}
测试结果:
ServletConfig的介绍
ServletConfig接口中,主要含有几个方法:
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
所以GenenricServlet实现了Servlet、ServletConfig,自然会拥有这些方法了。
ServletContext
多个项目中会共享ServletContext。同时ServletContext会和Tomcat共存亡,一旦Tomcat启动,那么ServletContext就会创建,一旦关闭,那么ServletContext也会消亡。
而获取ServletContext对象的方法主要有:
- 实现Servlet接口的时候,可以通过init(ServletConfig config)方法中的config调用getServletContext得到
- GenericServlet实现了ServletConfig接口,所以拥有了getServletContext方法,因此可以直接调用这个方法来返回ServletContext。同样的,因为HttpServlet继承了GenericServlet,所以同样会拥有getServletContext方法。
- HttpSession中的getServletContext方法
- ServletContextEvent中的getServletContext方法
在web-xml中,init-param是只能被一个servlet使用的,不可以被其他的servlet使用的。那么如果我们需要定义初始参数,所有的servlet都可以调用,应该怎样做呢?那么我们需要使用的是即可,然后我们通过ServletContext对象调用getInitParamer(name),就可以获取对应的name参数的值,通过ServletContext对象调用getInitParameterNames(),就可以获取所有的公共初始化参数的名字。
<?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>bean.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/AServlet</url-pattern>
</servlet-mapping>
<!--利用context-param来定义公共的初始参数,从而所有的Servlet都
可以通过ServletContext来访问-->
<context-param>
<param-name>param1</param-name>
<param-value>张三</param-value>
</context-param>
<context-param>
<param-name>param2</param-name>
<param-value>李四</param-value>
</context-param>
</web-app>
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取公共的初始化参数
//1、首先需要获取ServletContext对象
ServletContext servletContext = this.getServletContext();
//2、servletContext对象调用getAttribute方法,从而获取对应的参数
System.out.println("获取所有的公共初始化参数的名字及值");
Enumeration<String> init = servletContext.getInitParameterNames();
while(init.hasMoreElements()){
String name = init.nextElement();
//获取这个初始参数对应的值
String value = servletContext.getInitParameter(name);
System.out.println("公共初始参数名字: " + name + ", 值: " + value);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost....")
}
}
测试结果:
通过ServletContext来获取相关的资源时:
- getRealPath(target):可以获取target文件的真实路径。有盘符的,那么这时候将这个方法的返回值来构建一个文件,是不会发生报错的。
- getResourceAsStream(target):在获取target文件的真实路径的同时,创建一个文件输入流。
- getResourcePaths(target):获取target目录下面的所有资源路径。值得注意的是,这个方法中的参数需要以/开始。如果没有/,那么就会发生500的错误.
<?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>BServlet</servlet-name>
<servlet-class>bean.BServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/BServlet</url-pattern>
</servlet-mapping>
</web-app>
package bean;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Set;
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//需要获取相关文件的真实路径,那么需要通过ServletContext调用getRealPath获得
//1、获取servletContext对象
ServletContext servletContext = this.getServletContext();
//2、调用getRealPath,从而获取真实路径,包括盘符,从而可以新建对应的流对象
String realPath = servletContext.getRealPath("index.jsp");
System.out.println("index.jsp的真实路径为: " + realPath);
FileInputStream in = new FileInputStream(realPath);
BufferedReader bufr = new BufferedReader(new InputStreamReader(in));
String line = null;
while((line = bufr.readLine()) != null){
System.out.println(line);
}
System.out.println("==========================");
//上面可以调用getResourceAsStream,从而在获取真实路径的同时,之后会创建流对象
InputStream rs = servletContext.getResourceAsStream("index.jsp");
BufferedReader bufr2 = new BufferedReader(new InputStreamReader(rs));
String line2 = null;
while((line2 = bufr2.readLine()) != null){
System.out.println(line2);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost....");
}
}
运行结果:
练习: 利用ServletContext来统计访问量:
解题思路:
- 获取ServletContext对象
- servletContext对象调用getAttribute方法,从而获取count这个属性对应的值count,如果值为null,说明是第一次访问,那么将值count置为1,否则,如果值count不为null,说明不是第一次访问,那么就在原来的基础上加1
- servletContext对象调用setAttribute方法,从而将count属性对应的值变成了更新之后的count值。此时为了让count值显示在网页上,我们利用HttpServletRequest对象调用getWriter,获取一个PrintWriter对象,然后这个对象调用println方法,就可以将count显示在网页上面了。
- 由于ServletContext是和服务器同生共死的关系,那么当服务器关闭之后,在重启,那么就会从1重新计数了。
<?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>CServlet</servlet-name>
<servlet-class>bean.CServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CServlet</servlet-name>
<url-pattern>/CServlet</url-pattern>
</servlet-mapping>
</web-app>
public class CServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
Integer count = (Integer)servletContext.getAttribute("count");
if(count == null){
count = 1;
}else{
++count;
}
servletContext.setAttribute("count",count);
//保存之后,同时需要在页面中打印出当前的访问量
PrintWriter writer = resp.getWriter();
writer.println("<h1>" + count + "</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost....");
}
}
测试结果: