六、Servlet
您的导航
- 六、Servlet
- 6.1 Servlet概述
- 6.2 Servlet准备
- 6.3 Hello,Servlet!
- 6.3.2 编写HelloServlet代码
- 6.3.3 Servlet部署
- 6.3.4 添加tomcat配置
- 6.4 HelloServlet原理
- 6.5 ServletContext
- 6.5.1概述
- 6.5.2 共享数据
- 6.5.3 转发请求
- 6.5.4 转发和重定向
- 6.5.5 获取资源
- 6.6 HttpServletResponse对象
- 6.6.1 一些玩法
- 6.6.2 重定向
- 6.7 HttpServletRequest对象
- 6.7.1 请求转发
6.1 Servlet概述
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
Servlet是一个接口,任何实现了servlet接口的类也是servlet,
servlet运行在支持Java应用的服务器中,原理上可以响应任何类型的请求,但大多数情况下只用来扩展基于HTTP协议的web服务器。
6.2 Servlet准备
要继承实现了servlet的类或是直接实现servlet接口,都得有servlet的信息,这些信息得导入servlet的包。
在pom.xml中添加servlet依赖:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
(导入后记得刷新)
查看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();
}
不难看出,这就是servlet的生命周期:
- Servlet 初始化后调用 init () 方法。
- Servlet 调用 service() 方法来处理客户端的请求。
- Servlet 销毁前调用 destroy() 方法。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
init和destroy初学无需关心,最重要的是service处理请求的阶段。
如何处理请求呢?
6.3 Hello,Servlet!
一般情况下,我们用servlet来处理http请求,我们不需要自己实现service()方法,HttpServlet提供了处理各种http请求的方法。
6.3.2 编写HelloServlet代码
下面我们就继承这个类写出我们第一个servlet程序
public class HelloServlet extends HttpServlet {
// 重写了父类的doGet方法,顾名思义,doGet方法就是用来处理get请求的
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
PrintWriter writer = resp.getWriter();
writer.println("Hello Servlet");
}
}
6.3.3 Servlet部署
当我们编写完Servlet程序后,我们得告诉服务器,到哪找我们的servlet,如何找我们的servlet,所以我们得去web.xml文件中注册一下:
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>vip.yangsf.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
servlet-name随便取,一个servlet要对应一个servlet-mapping,servlet-class填Servlet程序所在的地方,url-pattern填访问路径。
6.3.4 添加tomcat配置
- 右上角添加配置->编辑配置
- 添加本地tomcat服务器
- 添加工件,选择对应的war包
因为我的程序在ServletTest模块下,所以我选择这个war包
应用程序上下文就表示访问路径,可以更改。
- 在确认这几个参数都没问题后,就可以启动服务
- 启动后,在url后面加上servlet的访问地址(例如刚刚的/hello)即可看到HelloServlet
6.4 HelloServlet原理
浏览器向web服务器发送http请求,web服务器调用servlet的service方法处理请求,处理完成后,web服务器给浏览器发送经过处理后的http响应。
6.5 ServletContext
6.5.1概述
- ServletContext是一个全局的存储信息的空间,服务器开始就存在,服务器关闭后才释放。
可以把ServletContext看作是一个公共的空间,每一个Servlet可以通过ServletContext进行通信,我们可以通过getServletContext()方法来获取ServletContext对象。
6.5.2 共享数据
做点小测试:
// 在context中添加一个属性
public class setContextAttr extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
ServletContext context = this.getServletContext();
context.setAttribute("username","root");
context.setAttribute("password","123456");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
doGet(req, resp);
}
}
// 获取context中的属性
public class getContextAttr extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ServletContext context = this.getServletContext();
String name = (String) context.getAttribute("username");
String pw = (String) context.getAttribute("password");
// 设置编码
resp.setContentType("text/html");
resp.setCharacterEncoding("utf8");
PrintWriter writer = resp.getWriter();
writer.println("用户名:" + name + "<br>" + "密码:" + pw);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
别忘了在web.xml中注册servlet
<servlet>
<servlet-name>setArr</servlet-name>
<servlet-class>vip.liuqian.setContextAttr</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>setArr</servlet-name>
<url-pattern>/setArr</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>getArr</servlet-name>
<servlet-class>vip.liuqian.getContextAttr</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getArr</servlet-name>
<url-pattern>/getArr</url-pattern>
</servlet-mapping>
启动服务,先后访问set、get,效果如下:
6.5.3 转发请求
就是字面意思,把Http请求转发到另一个地方,可用getRequestDispatcher()方法来转发请求。
写代码测试:
// 先写一个页面
public class DispatcherTarget extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 防止乱码
resp.setContentType("text/html");
resp.setCharacterEncoding("utf8");
resp.getWriter().println("这是B的页面");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
注册:
<servlet>
<servlet-name>distarget</servlet-name>
<servlet-class>vip.liuqian.DispatcherTarget</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>distarget</servlet-name>
<url-pattern>/B</url-pattern>
</servlet-mapping>
效果:
再用getRequestDispatcher()方法试试能不能转发
public class RequestDispatcher extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
context.getRequestDispatcher("/B").forward(req, resp);
resp.setContentType("text/html");
resp.setCharacterEncoding("utf8");
resp.getWriter().println("这是A的页面");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
注册:
<servlet>
<servlet-name>dispatch</servlet-name>
<servlet-class>vip.liuqian.RequestDispatcher</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatch</servlet-name>
<url-pattern>/A</url-pattern>
</servlet-mapping>
按道理,如果没有转发,当我们访问路径为"/A"时,页面上应该输出"这是A的页面",但实际上是这样的
并且状态码为200,说明没有跳转
6.5.4 转发和重定向
刚刚的案例,如果是重定向而不是转发,那么url最后就应该是B而不是A,说明了转发和重定向还是有区别的。
转发:
从头至尾,浏览器和B没有直接请求或响应。
重定向:
重定向是,访问A,然后实际上是访问B。
6.5.5 获取资源
我们来试试用Servlet获取properties文件的内容。
先创建一个data.properties文件在resources目录下(必须是),然后data中本来有一些数据(手动输入)。
可以用ServletContext配合getResourceAsStream()方法来获取文件的流。
写代码试试:
public class getResource extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 为什么是这个路径?打开target目录看看就明白了
InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/data.properties");
Properties prop = new Properties();
prop.load(is);
resp.setContentType("text/html");
resp.setCharacterEncoding("utf8");
resp.getWriter().println("用户名:" + prop.getProperty("username") + "<br>" + "密码:" + prop.getProperty("password"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
写完就去注册:
<servlet>
<servlet-name>getResource</servlet-name>
<servlet-class>vip.liuqian.getResource</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getResource</servlet-name>
<url-pattern>/getResource</url-pattern>
</servlet-mapping>
成功获取资源:
resources目录的文件在maven编译后,会在target目录的WEB-INF路径下的classes下找到这个文件。
6.6 HttpServletResponse对象
还记得Servlet怎么处理请求的吗?
Web Server接到Http请求后,把这个请求转换成了一个HttpServletRequest对象,Servlet就可以处理这个请求对象,然后返回一个HttpServletResponse对象,我们现在要了解的,就是这个HttpServletResponse对象。
我们知道Http请求和响应都是一些参数,现在我们拿到了响应对象,就可以设置一下这些参数。
做点小测试:
6.6.1 一些玩法
- 先来利用response下载个文件吧~
首先,我们要有一个文件。我这里的文件是1.png,放到resources目录下(约定)。
然后就可以开始敲代码了
// Response文件下载
public class Test01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 通过文件获取根路径,为什么这里填这个路径?和6.5.5是一样的道理
String realPath = this.getServletContext().getRealPath("/WEB-INF/classes/1.png");
// 通过根路径获取文件名,最后一个'\'之后的就是文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
// 设置响应头,就是响应的一些参数,下载文件就需要这样设置
resp.setHeader("Content-disposition", "attachment;filename=" + fileName);
// IO操作
FileInputStream fileInputStream = new FileInputStream(realPath);
ServletOutputStream outputStream = resp.getOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = fileInputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
}
fileInputStream.close();
outputStream.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
写完就去注册:
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>vip.yangsf.Test01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>download</servlet-name>
<url-pattern>/download</url-pattern>
</servlet-mapping>
马上启动服务去试试:
访问 http://localhost:8080/download 就让我下载文件。
- 再搞个自动生成验证码(随机数)的页面
// 验证码
public class Test02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 首先要有随机数
Random random = new Random();
// 生成六位随机数
int a = random.nextInt(1000000);
// 不足六位时用0填充 如 000252 不能是252
String s = String.format("%06d", a);
// 输出到网页上
resp.getWriter().println(s);
// 3秒刷新一次
resp.setHeader("refresh", "3");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
还是注册注册注册
<servlet>
<servlet-name>random</servlet-name>
<servlet-class>vip.yangsf.Test02</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>random</servlet-name>
<url-pattern>/random</url-pattern>
</servlet-mapping>
访问 http://localhost:8080/random , 成功每三秒刷新一次页面换一个验证码。
6.6.2 重定向
访问A,A告诉浏览器应该访问B。
之前在6.5.3试了servlet的请求转发,又在6.5.4讲了转发和重定向的区别,现在我们用response做一个重定向,用sendRedirect方法。
简单实现一下:
public class Test03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.sendRedirect("./random");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
注册:
<servlet>
<servlet-name>redirect</servlet-name>
<servlet-class>vip.yangsf.Test03</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>redirect</servlet-name>
<url-pattern>/re</url-pattern>
</servlet-mapping>
然后我们访问 http://localhost:8080/re 就会跳转到 http://localhost:8080/random ,并且状态码多了一个302。
大概知道了重定向长啥样,我们就用重定向来实现一个简单的登录效果:
先来一个网页:
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/login1", method="post">
用户名:<input type="text" name="username">
<br>
密码:<input type="text" name="password">
<br>
<input type="checkbox" name="hobbies" value="rap"> rap
<input type="checkbox" name="hobbies" value="篮球"> 篮球
<input type="checkbox" name="hobbies" value="唱"> 唱
<input type="checkbox" name="hobbies" value="跳"> 跳
<input type="submit" value="登录">
</form>
</body>
</html>
servlet:
// 登录 重定向
public class Test04 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println(username + ":" + password);
resp.sendRedirect("./random");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
注册:
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>vip.yangsf.Test04</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
然后我们输入用户名和密码之后,控制台就会打印对应的用户名和密码,并且跳转到刚才生成随机数的页面。
6.7 HttpServletRequest对象
既然有HttpServletResponse对象,那必然是有HttpServletRequest对象的,我们向webServer发送请求,然后服务器把这个Http请求转化为HttpServletRequest对象传给servlet,我们就可以获取客户端的信息。
同样的,我们也可以设置一些请求的信息。
6.7.1 请求转发
假如还是刚才那个例子。
public class Test05 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码防止乱码
req.setCharacterEncoding("utf8");
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbies = req.getParameterValues("hobbies");
System.out.println(username);
System.out.println(password);
System.out.println(Arrays.toString(hobbies));
req.getRequestDispatcher("/random").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
注册:
<servlet>
<servlet-name>login1</servlet-name>
<servlet-class>vip.yangsf.Test05</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login1</servlet-name>
<url-pattern>/login1</url-pattern>
</servlet-mapping>
提交表单后:
可以看到url不变,却是/random的response
控制台输出: