一、Servlet 概述
1.1、什么是 Servlet?
- Servlet 是 JavaWeb 的三大组件之一,它属于动态资源,Servlet 的作用是处理请求,服务器会把接收到的请求给 Servlet 处理,在 Servlet 中需要:
- 接收请求数据
- 处理请求
- 完成响应
每个 Servlet 是唯一的,它们处理的请求时不同的。
二、实现 Servlet 的方式
实现 Servlet 有三种方式:
- 实现 java.servlet.Servlet 接口
- 继承 javax.servlet.GenericServlet 类
- 继承 javax.servletHttpServlet 类
2.1 实现 java.servlet.Servlet 接口
import javax.servlet.*;
import java.io.IOException;
/**
* Servlet 中的方法大多由 Tomcat 调用,Servlet 的对象也是有 Tomcat 生成
*
* */
public class Aservlet implements Servlet {
/**
* 它是生命周期方法
* 它会在 Servlet 对象创建之后马上执行,并执行一次(出生之后)
* */
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init");
}
/**
* 可以获取 Servlet 的配置信息
* */
@Override
public ServletConfig getServletConfig() {
System.out.println("getServletConfig");
return null;
}
/**
* 它是声明周期方法
* 它会被调用多次,每次请求都是调用这个方法
* */
@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 "Hello World";
}
/**
* 它是声明周期方法
* 它会在 Servlet 被销毁之前调用,并且只会调用一次
* */
@Override
public void destroy() {
System.out.println("destroy");
}
}
2.2 继承 javax.servlet.GenericServlet 类
查看源码,我们发现 GenericServlet 是实现了 Servlet,ServletConfig, Serializable 的接口
import javax.servlet.*;
import java.io.IOException;
public class Bservlet extends GenericServlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 编写处理的方法
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
2.3 继承 javax.servletHttpServlet 类
查看源码,我们发现 HttpServlet 是继承了 GenericServlet 类。
所以我们一般在 JavaEE 项目中创建的 Servlet 都是直接继承于 HttpServlet
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "TestServlet")
public class TestServlet extends HttpServlet {
// 监听 post 请求
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
// 监听 get 请求
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter writer = response.getWriter();
writer.println("Hello World");
writer.close();
}
// 参数已经是 Http协议相关的,使用起来更方便
/**
* 通过 request 得到当前请求的请求方式,例如 GET 和 POST
* 根据请求方式调用 doGet() 或 doPost()
*
* */
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.service(request, response);
}
// 强制两个参数为 http 协议相关的类型
// 调用本类的 service(HttpServletRequest, HttpServletResponse)
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
super.service(req, res);
}
}
三、Servlet 生命周期
这里是基于第一种实现 Servlet 的方式来编写的
3.1 如何让游览器访问 Servlet?
- 给Servlet指定一 个Servlet路径! (让Servlet与一 个路径绑定在一起)
- 游览器访问Servlet路径
3.2 给 Servlet 配置 Servlet 路径(这里在 web.xml 中配置)
这个 Servlet 一定要是实现了 Servlet 接口
<?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>xxx</servlet-name> <!-- Servlet 别名 -->
<servlet-class>cn.ajax.study.Aservlet</servlet-class> <!-- Servlet 的路径 -->
</servlet>
<servlet-mapping>
<servlet-name>xxx</servlet-name> <!-- 和上面一致 -->
<url-pattern>/Aservlet</url-pattern> <!-- 路径第一个必须为斜杠 / -->
</servlet-mapping>
</web-app>
3.3 部署 Tomcat,并运行
3.4 访问游览器
我们发现没有 404, 显示成功。
然后在控制台看到了两个方法执行了!! 然后我们多刷新几次网页,可以看到 service 被执行了。
然后关闭 Tomcat 服务器,我们发现 destory 执行了
3.5 结论(Servlet 生命周期方法)
生命周期方法
- void init(ServletConfig servletConfig); 执行之后(1次)
- void service(ServletRequest servletRequest, ServletResponse servletResponse); (每次处理请求都会调用)
- void destroy(); 临死之前(1次)
特性
- servlet 是单例的,一个类只有一个对象,当然可能存在多个 Servlet 类
- 线程不安全的,所以效率是最高的
Servlet 由我们写,对象由服务器创建,并由服务器调用相应的方法
四、Servlet 细节
不要创建 Servlet 成员变量,创建局部变量
- 可以创建无状态成员
- 可以创建有状态成员,但状态必须只读
4.1 Servlet 与线程安全
- 因为一个类型的 Servlet 只有一个实例对象 ,那么就有可能会现时出-个servlet,同时处理多个请求,那么Servlet是否为线程安全的呢?
答案是:“不是线程安全的”。这说明Servlet的工作效率很高,
- 但也存在线程安全问题 !
所以我们不应该在Servlet 中便宜创建成员变量,因为可能会存在一个线程 对这个成员变量进行 写操作,另一个线程对这个成员变量进行读操作。。
4.2 在服务器启动时创建 Servlet
- 服务器一般在某个 Servlet 第一次收到请求时创建它
- 也可以在 web.xml 中进行配置,使服务器启动时就创建 Servlet
<?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>xxx</servlet-name>
<servlet-class>cn.ajax.study.Aservlet</servlet-class>
<load-on-startup>0</load-on-startup>
<!-- 这个数字代表创建的顺序 -->
</servlet>
<servlet-mapping>
<servlet-name>xxx</servlet-name>
<url-pattern>/Aservlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>yyy</servlet-name>
<servlet-class>cn.ajax.study.Bservlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>yyy</servlet-name>
<url-pattern>/Bservlet</url-pattern>
</servlet-mapping>
</web-app>
4.3 url-pattern
<servlet-mapping>
<servlet-name>yyy</servlet-name>
<url-pattern>/Bservlet</url-pattern>
<url-pattern>/Bservlet1</url-pattern>
</servlet-mapping>
还可以在
缀或后缀,使用通配符可以命名一个 Servlet绑定一组URL,例如:
/senvlet/ : /servlet/a、 /servlet/b, 都匹配/servlet/ ; 。.do : /abc/def/ghi.do、 /a.do, 都匹配.do; +/* :匹配所有URL;
注意:
- 通配符要么为前缀,要么为后缀
- 不能出现在中间位置
- 也不能只有 通配符
五、Servlet 补充
5.1 ServletConfig介绍
5.1.1 功能介绍
在 web.xml 中,我会填写如下信息<servlet> <servlet-name>xxx</servlet-name> <!-- Servlet 别名 --> <servlet-class>cn.ajax.study.Aservlet</servlet-class> <!-- Servlet 的路径 --> </servlet>
ServletConfig 的作用就是读取 web.xml 这段内容到内存中。1个 ServletConfig对象,对应一般 web.xml 中 Servlet 配置信息!
5.1.2 内置 API
API | 功能 |
---|---|
String getServletName() | 获取 |
ServletContext getServletContext() | 获取 Servlet 上下文对象 |
String getInitParameter(String name) | 通过名称获取指定初始化参数的值 |
Enumeration getInitParameterNames0 | 获取所有初始化参数的名称 |
5.1.3 demo1
在 web.xml 中设置如下参数
<servlet>
<servlet-name>xxx</servlet-name>
<servlet-class>cn.ajax.study.Aservlet</servlet-class>
<init-param>
<param-name>p1</param-name>
<param-value>v1</param-value>
</init-param>
<init-param>
<param-name>p2</param-name>
<param-value>v2</param-value>
</init-param>
</servlet>
然后在 servlet 中获取参数
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init");
// 获取初始化参数
System.out.println(servletConfig.getServletName()); // xxx
System.out.println(servletConfig.getInitParameter("p1")); //v1
System.out.println(servletConfig.getInitParameter("p2")); //v2
// 获取素有初始化参数的名称
Enumeration e = servletConfig.getInitParameterNames();
while (e.hasMoreElements()) {
System.out.println(e.nextElement());
}
}
然后访问 Servlet, xml 中的值都显示出来了
5.2 ServletContext (重点)
- 一个项目只有一个 ServletContext 对象(application)
- 我们可以在 N 多个 servlet 中获取这个唯一对象,使用它可以给多个 servlet 传递数据!
这个对象在 Tomcat 启动时创建,在 tomcat 关闭时才死去
5.2.1 ServletContext 概述
服务器为每一个应用创建 ServletContext 对象;
ServletContext 对象的创建是在服务器启动时完成的
- ServletContext 对象的销毁是在服务器关闭时完成的
SevletContext.对象的作用是在整个Web应用的动态资源之间共享数据! 例如在AServlet中向
ServletContext对象中保存一个值, 然后在BSerlet中就可以获取这个值,这就是共享数据了。
5.2.2 获取 ServletContext
- ServletConfig getServletContext();
- GenericServlet getServletContext();
- HttpSession getSerletContext();
- ServletContextEvent getServletContext();
- 在Servlet中获取ServletContext对象:
- 在void init(ServletConfig config)中 : ServletContext context = config.getServetContext(); ServletConfig,类的getServletContext()方法可以用来获取ServletContext对象,
- 在Genericervlet或HittpServlet中获取servletContext对象:GenericServlet类有getServletContext()方法, 所以可以直接使用this.getSservletContext()来获取;
5.2.3 域对象的功能
域对象的作用是在多个 Servlet 中传递数据 (不完整表达)
域对象必须要要有 存对象 和 取对象 的功能
5.2.4 四大域对象
ServletContext 是 JavaWeb 四大域对象之一
- PageContent()
- ServletRequest()
- HTTPSession()
- ServletContext()
所有域都具有存取数据的功能,因为域对象内部有一个 Map,用来存储数据。接下来介绍 ServletContext 对象用来操作数据的方法
5.2.5 域对象演示(ServletContext() )
接下来就演示一下 设置属性以及获取属性
- 我们编写一个 TsetServlet1 ```java import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
// 这里使用了注解的方式,可以直接通过注解访问路由 @WebServlet(“/TestServlet1”) public class TestServlet1 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 1. 获取 ServletContext 对象
* 2. 调用其 setAttribute() 方法完成存储数据
* */
ServletContext application = this.getServletContext();
application.setAttribute("a", "123");
}
}
2. 然后编写 UseServlet1
```java
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/UseServlet1")
public class UseServlet1 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 1. 获取 ServletContext 对象
* 2. 调用其 getAttribute() 方法完成存储数据
* */
ServletContext application = this.getServletContext();
String a = (String)application.getAttribute("a");
System.out.println(a);
}
}
然后打开游览器,分别访问
然后我们就在控制台看到了打印的值
5.2.6 获取公共的初始化参数
- 在 web.xml 中加入如下内容
<context-param>
<param-name>context-name</param-name>
<param-value>context-value</param-value>
</context-param>
- 在 Servlet 中获取初始化的参数
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 演示 ServletContext 获取公共的初始化参数
* */
@WebServlet("/TestServlet3")
public class TestServlet3 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 得到 ServletContext
// 2. 得到初始化的参数: getInitParameter
ServletContext app = this.getServletContext();
String val = (String)app.getInitParameter("context-name");
// 获取输入流,将结果打印到游览器中
PrintWriter writer = response.getWriter();
writer.write("<h1>"+val+"</h1>");
writer.close();
}
}
- 结果正常显示
5.2.7 ServletContext 获取资源相关方法
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
// 使用 ServletContext 获取资源路径
@WebServlet("/TestServlet4")
public class TestServlet4 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 得到的时候又盘符的路径(绝对路径)
String path = this.getServletContext().getRealPath("/index.jsp");
System.out.println(path);
// 获取资源路径后,再创建输入流对象
InputStream input = this.getServletContext().getResourceAsStream("/index.jsp");
// 获取当前路径下所有资源的路径
Set<String> paths = this.getServletContext().getResourcePaths("/WEB-INF");
System.out.println(paths);
}
}
六、练习
6.1 网站访问量统计
只要服务器不关,这个就可以一直显示
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.Response;
import java.io.IOException;
import java.io.PrintWriter;
// 统计网站人数
@WebServlet("/CountServlet")
public class CountServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("gbk");
/**
* 1. 获取 ServletContext 对象
* 2. 从 ServletConte对象中获取名为 count 的属性
* 3. 如果存在,访问量 +1,然后保存回去
* 4. 如果不存在,说明是第一次访问,问 ServletContext中保存名为 count 的属性,值为1
* */
ServletContext app = this.getServletContext();
Integer count = (Integer)app.getAttribute("count");
if (count == null) {
app.setAttribute("count",1);
} else {
app.setAttribute("count",count+1);
}
// 向游览器输出,需要响应对象
PrintWriter writer = response.getWriter();
writer.print("<h1>你是第"+count + "个人</h1>");
}
}
六、BaseServlet
- 解决一个问题:一个 Servlet 中可以有多个请求处理方法
- 客户端多给一个参数,这个参数表示要执行的方法
- 请求处理方法的签名,必须与 service 相同,即返回值,参数,都相同
- 客户端必须传 名为 method 的参数
BaseServlet
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 多个请求的处理方法
* 请求处理方法:除了名字以外,都与 service 方法一致
* */
@WebServlet("/AServlet")
public class AServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
/**
* 1. 获取参数,用来识别用户想请求的方法
* 2. 然后判断是哪一个方法
* */
String methodName = req.getParameter("method");
// if (methodName.equals("addUser")) {
// this.addUser(req, resp);
// } else if (methodName.equals("editUser")) {
// this.editUser(req, resp);
// } else if (methodName.equals("deleteUser")) {
// this.deleteUser(req, resp);
// }
if (methodName == null || methodName.trim().isEmpty()) {
throw new RuntimeException("你没有传递 method 参数,无法确定您要调用的方法");
}
/**
* 但是如果有需求,要增加一个方法,那就得加一个 if 判断
* 得到方法名称,通过反射调用方法
* 1. 得到方法名,通过方法名在得到 Method 类的对象
* 需要得到 Class,然后调用它的方法进行查询,得到 method
* 我们要查询的是当前类的方法,所以我们需要得到当前类的 Class
* */
Class c = this.getClass(); // 得到当前类的 class 对象
Method method = null;
try {
method = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
} catch (Exception e) {
throw new RuntimeException("您要调用的方法:"+methodName +",他不存在!");
}
/**
* 调用 method 表示的方法
* */
try {
method.invoke(this,req,resp); // this.addUser(req,resp), method(this.req, resp);
} catch (Exception e) {
System.out.println("您调用的方法:"+methodName +",它内部抛出了异常");
throw new RuntimeException(e);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
public void editUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("添加用户");
}
public void addUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("添加用户");
}
public void deleteUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("删除用户");
}
}
我们传入的参数也执行了对应的方法。