文章目录
- 前言
- Servlet打印Hello Servlet
- Servlet生命周期
- HttpServletRequest对象
- 常用api方法
- 请求乱码问题
- 请求转发
- request域对象
- HttpServletResponse对象
- 响应数据
- 响应乱码问题
- 请求重定向
- 请求转发与重定向区别
- Cookie对象
- Cookie的创建与获取
- Cookie设置到期时间
- Cookie注意点
- Cookie的路径
- HttpSession对象
- Session对象获取和常用Api
- 标识符JSESSIONID
- session域对象
- session对象的销毁
- ServletContext对象
- 常用aip
- ServletContext域对象
- Servlet三大域对象总结
- Servlet文件上传
- 前端页面实现
- 后台代码实现
- Servlet文件下载
- 超链接下载
- 后台代码下载
前言
虽然Servlet现在已经不会直接用于开发了,但是作为SpringMVC的前置知识,还是需要全面了解一下的,故做此文,用于总结。
Servlet打印Hello Servlet
1、前置配置
下载Tomcat :
Tomcat下载连接
idea2022 创建Servlet项目流程:
public class Servlet01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//打印内容在控制台
System.out.println("hello servlet");
//通过流输出到浏览器
resp.getWriter().write("Hello Servlet!!!");
}
}
运行效果:
浏览器:
控制台:
Servlet生命周期
生命周期简单来说,分为3步,
初始化(init()) ==> 调用方法(service()) ==>销毁方法(destroy())
放到代码中来看:
@WebServlet("/ser02")
public class servlet02 extends HttpServlet {
private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 系统方法,服务器自动调用,只执行一次
* @throws ServletException
*/
@Override
public void init() throws ServletException {
System.out.println("Servlet被创建了..." + formatter.format(new Date(System.currentTimeMillis())));
}
/**
* 每次调用都会执行
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet被调用了..." + formatter.format(new Date(System.currentTimeMillis())));
}
/**
* 系统方法,服务器自动调用,只执行一次
*/
@Override
public void destroy() {
System.out.println("Servlet被销毁了..." + formatter.format(new Date(System.currentTimeMillis())));
}
}
运行结果:
从图中可以看出,不管浏览器访问调用多少次,初始化和销毁方法只会执行一次,但是Service在每次访问都会执行。
HttpServletRequest对象
简单来讲,HttpServletRequest就是封装用户传过来的请求信息,对象由Tomcat封装好传过来,同时该对象是一个接口。service() 方法中传递的HttpServletRequest对象是该接口的实例化对象。
常用api方法
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 完整的请求url
String url = req.getRequestURL().toString();
System.out.println("获取客户端请求的完整url: " + url);
// 2. 部分url(站点名开始,到?前面结束)
String uri = req.getRequestURI();
System.out.println("客户端请求的部分url: " + uri);
// 3. 获取请求行中的参数部分
String queryString = req.getQueryString();
System.out.println("获取请求行中的参数部分: " + queryString);
// 4. 获取客户端请求方式
String method = req.getMethod();
System.out.println("获取客户端的请求方式: " + method);
// 5. 获取http版本号
String protocol = req.getProtocol();
System.out.println("http版本号为: " + protocol);
// 6. 获取webapp名字
String contextPath = req.getContextPath();
System.out.println("webapp名字为: " + contextPath);
// 7. 获取请求参数
// (1)、获取指定名称参数
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
System.out.println("username: " + username + " pwd: " + pwd);
// (2)、获取指定名称参数的所有值
String[] hobbys = req.getParameterValues("hobby");
System.out.println("hobbys: " + Arrays.toString(hobbys));
if(hobbys != null && hobbys.length > 0) {
for (String hobby : hobbys) {
System.out.println("爱好: " + hobby);
}
}
}
}
请求url:
http://localhost:8080/s01/ser01?username=suxuchao&pwd=123456&hobby=sing&hobby=dance&hobby=computer
运行结果:
获取客户端请求的完整url: http://localhost:8080/s01/ser01
客户端请求的部分url: /s01/ser01
获取请求行中的参数部分: username=suxuchao&pwd=123456&hobby=sing&hobby=dance&hobby=computer
获取客户端的请求方式: GET
http版本号为: HTTP/1.1
webapp名字为: /s01
username: suxuchao pwd: 123456
hobbys: [sing, dance, computer]
爱好: sing
爱好: dance
爱好: computer
请求乱码问题
在Tomcat8以上,GET请求有中文的话不会出现乱码,但是使用POST请求就会出现乱码,如下:
代码:
@WebServlet("/ser03")
public class Servlet03 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取客户端传递的参数
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
String method = req.getMethod();
System.out.println("请求方法为: " + method);
System.out.println("username: " + username);
System.out.println("pwd: " + pwd);
}
}
浏览器输入:
运行结果:
请求方法为: POST
username: å¼ ä¸‰
pwd: å¼ ä¸‰
解决方法:
首先我们需要知道,乱码是因为在解析过程中,request默认使用的编码是ISO-8859-1 (此编码不支持中文),所以解析一定会有乱码
//设置编码
req.setCharacterEncoding("UTF-8");
将该代码放在方法一开始,即可解决中文乱码问题
更正后结果:
请求方法为: POST
username: 张三
pwd: 张三
请求转发
请求转发有几个特点
- 服务端行为
- 地址栏的url不发生变化
- 从始至终只有一个请求
- request数据可以共享
- 代码中getRequestDispatcher只能转发一次
请求url:
http://localhost:8080/s01/ser03?username=张三
Servlet3代码:
String username = req.getParameter("username");
System.out.println("Servlet03: " + username);
//请求转发跳转到Servlet04
req.getRequestDispatcher("ser04").forward(req, resp);
Servlet4代码:
// 接收客户端请求的参数
String username = req.getParameter("username");
System.out.println("Servlet04: " + username);
运行结果:
Servlet03: 张三
Servlet04: 张三
同时,重定向可以重定向.html/.jsp文件等页面:
req.getRequestDispatcher("login.jsp").forward(req, resp);
request域对象
request域对象中的数据在一次请求中有效,经过请求转发,request域中的数据仍然存在,在请求转发过程中可以通过request来传输/共享数据。
Servlet05代码:
System.out.println("Servlet05...");
//设置域对象内容
req.setAttribute("name", "admin");
req.setAttribute("age", 18);
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
req.setAttribute("list", list);
req.getRequestDispatcher("ser06").forward(req, resp);
Servlet06代码:
System.out.println("Servlet06...");
//获取域对象内容
String name = (String) req.getAttribute("name");
Integer age = (Integer) req.getAttribute("age");
List<String> list = (List<String>) req.getAttribute("list");
System.out.println("name: " + name);
System.out.println("age: " + age);
System.out.println("list: " + Arrays.toString(list.toArray()));
请求url:
http://localhost:8080/s01/ser05
访问结果:
Servlet05...
Servlet06...
name: admin
age: 18
list: [aaa, bbb]
HttpServletResponse对象
简单来讲,HttpServletResponse就是封装服务器处理后,要响应给客户端的数据,封装的数据有 (发送数据,发送响应头,发送响应状态码) 的方法。service() 方法中传递的HttpServletResponse对象是该接口的实例化对象
响应数据
使用字符流:
// 获取字符输出流
PrintWriter writer = resp.getWriter();
// 输出数据
writer.write("Hello");
使用字节流:
//字节输出流
ServletOutputStream outputStream = resp.getOutputStream();
outputStream.write("Hi".getBytes());
注意:
两个流只能选择一个来用,否则就会报错。
响应乱码问题
s02代码:
PrintWriter writer = resp.getWriter();
writer.write("<h2>你好</h2>");
s03代码:
ServletOutputStream os = resp.getOutputStream();
os.write("<h2>你好</h2>".getBytes("UTF-8"));
运行结果:
<h2>??</h2>
<h2>浣犲ソ</h2>
解决方式:
在代码最开头添加一行这个代码,类比request的解决方法
resp.setCharacterEncoding("UTF-8");
但是我们发现此时两种输出流打印的结果最后都变成了相同的乱码,如下:
<h2>浣犲ソ</h2>
这是因为我们只设置了服务端的编码,request请求数据只在服务端之间传递,因此这样设置可以解决问题,但是response格式的话,我们不但要设置服务端,还有设置客户端的编码,解决方法如下:
// 设置服务端的编码
resp.setCharacterEncoding("UTF-8");
// 设置客户端的编码
resp.setHeader("content-type", "text/html;charset=UTF-8");
或者更简单的解决方法
//同时设置编码
resp.setContentType("text/html;charset=UTF-8");
这样就可以解决乱码和无法识别html的问题了。
请求重定向
- 重定向是客户端行为,由服务端指导
- 地址栏url会发生改变
- 存在两次请求
- 两次请求的数据不共享
Servlet04代码:
System.out.println("Servlet04....");
resp.setContentType("text/html;charset=UTF-8");
String username = req.getParameter("username");
System.out.println("username: " + username);
resp.sendRedirect("s05");
Servlet05代码:
System.out.println("Servlet05....");
resp.setContentType("text/html;charset=UTF-8");
String username = req.getParameter("username");
System.out.println("username: " + username);
第一次请求url:
http://localhost:8080/s01/s04?username=zhangsan
重定向后的url:
http://localhost:8080/s01/s05
打印结果:
Servlet04....
username: zhangsan
Servlet05....
username: null
请求转发与重定向区别
Cookie对象
Cookie就是通过浏览器的一些程序,将只在客户端进行操作的数据,存在cookie中,以此来减轻服务器的压力,例如:记住密码的操作。
cookie就是简单的键值对形式存储,key和value之间用"="连接,不同kv之间用“;” 来分割。
Cookie的创建与获取
创建Cookie对象
// Cookie的创建
Cookie cookie = new Cookie("username", "zhangsan");
// 发送(响应)Cookie对象
resp.addCookie(cookie);
获取Cookie对象:
// 获取Cookie数组
Cookie[] cookies = req.getCookies();
// 判断Cookie是否为空
if(cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println("name: " + name);
System.out.println("value: " + value);
}
}
运行结果:
name: username
value: zhangsan
Cookie设置到期时间
如下:
// 到期时间为负数(浏览器关闭就失效)
Cookie cookie1 = new Cookie("username01", "zhangsan");
cookie1.setMaxAge(-1);
res.addCookie(cookie1);
// 到期时间为整数(cookie存在指定时间)
Cookie cookie2 = new Cookie("username02", "lisi");
cookie2.setMaxAge(30);
res.addCookie(cookie2);
// 到期时间为0(删除cookie)
Cookie cookie3 = new Cookie("username03", "wangwu1");
cookie3.setMaxAge(0);
res.addCookie(cookie3);
Cookie注意点
-
Cookie只在当前浏览器有用,Cookie无法跨浏览器,且只保存在当前电脑
-
Cookie存中文问题
// 使用URLEncoder进行编码
String name = "姓名";
String value = "张三";
name = URLEncoder.encode(name);
value = URLEncoder.encode(value);
// 创建Cookie对象
Cookie cookie1 = new Cookie(name, value);
resp.addCookie(cookie1);
//获取Cookie
Cookie[] cookies = req.getCookies();
if(cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
String dname = URLDecoder.decode(cookie.getName());
String dvalue = URLDecoder.decode(cookie.getValue());
System.out.println("dname: " + dname);
System.out.println("dvalue: " + dvalue);
}
}
存储效果:
- 同名Cookie问题
如果服务端发送重复的Cookie,那么会覆盖原有的Cookie.
- 浏览器存放Cookie的数量
浏览器对于Cookie的存放数量也是有上限的,Cookie存放在浏览器,而且一般由服务端创建和设定,后期结合Session来进行会话追踪。
Cookie的路径
简单总结,如果访问路径中带上了Cookie设置的路径,就可以访问到Cookie,否则就访问不到。
http://localhost:8080/s01/cookie01
对于上述url,
如果Cookie设置如下就可以访问到
cookie.setPath("/");
cookie.setPath("/s01");
cookie.setPath("/s01/cookie01");
如果设置如下就访问不到
cookie.setPath("/s02");
cookie.setPath("/s01/cookie02");
HttpSession对象
- Session 数据本身只存储在服务端
- 客户端仅持有用于查找 Session 的 ID(相当于钥匙)
- 若客户端禁用 Cookie,需通过 URL 参数传递 Session ID
- 每一个Session只在当前浏览器保存,开新浏览器就无法获得当前Session
Session对象获取和常用Api
// 获取Session对象
HttpSession session = req.getSession();
// 获取Session的会话标识符
String id = session.getId();
System.out.println(id);
// 获取Session的创建时间
System.out.println(session.getCreationTime());
// 获取最后一次访问时间
System.out.println(session.getLastAccessedTime());
// 判断是否是新的Session对象
System.out.println(session.isNew());
运行结果:
A07655B90CBD6033B4D2F4FCC3C5FBE3
1745488955622
1745488955622
true
标识符JSESSIONID
服务器每次会先检查从客户端传过来数据中有没有JSESSIONID标识符,该标识符存放于cookie,服务器收到JSESSIONID之后会先查看是否含有该值的Session对象,如果没有,则认为这是一次新对话,创建一个新Session对象,如果由,就认为是标志过的会话,返回该Session对象,实现数据共享。
session域对象
整体与request的域对象的api是一样的,但是与request域对象的区别是,Session域对象只要你这次会话没有结束,即浏览器没有和服务器断开,无论开多少个页面都能访问到Session中的数据,request则是只要换了一次请求,就无法共享域对象数据。
// 获取Session域对象
HttpSession session = req.getSession();
// 设置域对象
session.setAttribute("username", "zhangsan");
session.setAttribute("pwd", "123456");
//移除域对象
session.removeAttribute("pwd");
//获取域对象
String username = (String) session.getAttribute("username");
session对象的销毁
- 默认销毁时间
Tomcat/config/web.xml,在该文件下,默认session在不操作的情况下,存活时间为30min。
<session-config>
<session-timeout>30</session-timeout>
</session-config>
- 自己设定销毁时间
HttpSession session = req.getSession();
session.setMaxInactiveInterval(15); // 15秒过期
- 立即销毁
session.invalidate();// 立即销毁
- 关闭浏览器失效
Session 底层依赖Cookie来实现,而Cookie存在浏览器内存中,因此关闭浏览器导致Cookie失效,也会导致Session失效。
ServletContext对象
- 每个web程序,有且仅有一个Servlet Context对象,又称Application对象,在WEB程序启动后,一般会创建一个对应的ServletContext对象。
- 对象中保存了当前服务器的信息还有getRealPath获取资源真实路径等。
常用aip
代码:
// 获取servletContext对象
// (1)、通过request对象获取
ServletContext servletContext1 = req.getServletContext();
// (2)、通过Session对象获取
ServletContext servletContext2 = req.getSession().getServletContext();
// (3)、通过ServletConfig对象获取
ServletConfig servletConfig = getServletConfig();
ServletContext servletContext3 = servletConfig.getServletContext();
// (4)、直接获取
ServletContext servletContext4 = getServletContext();
//常用方法
//1. 获取当前服务器的版本信息
String serverInfo = req.getServletContext().getServerInfo();
System.out.println("获取当前服务器的版本信息: " + serverInfo);
//2. 获取当前项目真实路径
String realPath = req.getServletContext().getRealPath("/");
System.out.println(" 获取当前服务器的真实路径: " + realPath);
运行结果:
获取当前服务器的版本信息: Apache Tomcat/8.5.47
获取当前服务器的真实路径: D:\code_study\servlet01\target\servlet01-1.0-SNAPSHOT\
ServletContext域对象
该域对象数据不建议存过多数据,因为如果不手动移除,服务器启动后会一直存在。
ServletContext servletContext = req.getServletContext();
// 设置域对象
servletContext.setAttribute("name", "zhangsan");
// 获取域对象
String name = (String) servletContext.getAttribute("name");
// 移除域对象
System.out.println("name = " + name);
servletContext.removeAttribute("name");
Servlet三大域对象总结
- request域对象
在一次request请求中生效。 - Session域对象
在一次会话中生效。 - ServletContext域对象
在服务器启动期间一直生效,除非重启服务器。
Servlet文件上传
前端页面实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<!--
文件上传
1.准备表单
2.设置表单的提交类型
3.设置表单类型问文件上传表单 enctype="multipart/form-data"
4.设置文件提交的地址
5.准备表单元素
1. 普通的表单项 type="text"
2. 文件项 type="file"
-->
<form method="post" enctype="multipart/form-data" action="uploadServlet">
姓名: <input type="text" name="username"> <br>
文件: <input type="file" name="myfile"> <br>
<button>提交</button>
</form>
</body>
</html>
后台代码实现
@WebServlet("/uploadServlet")
@MultipartConfig //文件上传表单一定要加这个注释
public class uploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件上传....");
// 设置请求编码格式
req.setCharacterEncoding("UTF-8");
// 获取普通表单项
String username = req.getParameter("username");
System.out.println("username: " + username);
// 获取Part对象(Serlet将mutipart/form-data的POST请求封装成Part对象)
Part part = req.getPart("myfile");
// 通过part对象得到上传的文件名
String fileName = part.getSubmittedFileName();
System.out.println("上传文件名: " + fileName);
String realPath = req.getServletContext().getRealPath("/");
System.out.println("文件存放路径: " + realPath);
// 上传文件到指定目录
part.write(realPath + "/" + fileName);
}
}
最后文件上传的路径可以改成你想要指定的。
注意:上传文件一定要加@MultipartConfig 注解,否则文件无法上传成功
Servlet文件下载
就是将浏览器上的文件下载到本地
超链接下载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件下载</title>
</head>
<body>
<!--
浏览器可识别的文件可以直接浏览器中查看,
不可识别的文件需要先浏览器下载。
-->
<!--浏览器可以识别的资源-->
<a href="download/hello.txt">文本文件</a>
<a href="download/ai.jpg">图片文件</a>
<!--浏览器不可以识别的资源-->
<a href="download/yasuo.zip">压缩文件</a>
<hr>
<!--添加了download属性,可以直接在浏览器中下载对应文件-->
<a href="download/hello.txt" download>文本文件</a>
<a href="download/ai.jpg" download="ai.jpg">图片文件</a>
</body>
</html>
后台代码下载
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件下载开始....");
// 1. 设置请求的编码
req.setCharacterEncoding("UTF-8");
// 2. 获取参数
String fileName = req.getParameter("fileName");
// 3. 参数的非空判断
if(fileName == null || "".equals(fileName.trim())) {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write("请输入要下载的文件名");
resp.getWriter().close();
return;
}
// 得到图片存放的路径
String path = req.getServletContext().getRealPath("/download/");
// 通过路径得到file对象
File file = new File(path + fileName);
// 判断文件对象是否存在
if(file.exists() && file.isFile()) {
// 设置相应类型(浏览器无法处理或激活某个程序来处理的MIME类型)
resp.setContentType("application/x-msdownload");
// 设置响应头
resp.setHeader("content-Disposition", "attachment;filename" + fileName);
// 得到file文件输入流
InputStream is = new FileInputStream(file);
// 得到字节输出流
ServletOutputStream os = resp.getOutputStream();
// 定义字节数组
byte[] bytes = new byte[1024];
//定义长度
int len = 0;
// 循环输出
while((len = is.read(bytes)) != -1) {
// 输出
os.write(bytes, 0, len);
}
// 关闭资源
os.close();
is.close();
} else {
resp.getWriter().write("文件不存在,请重试!");
resp.getWriter().close();
}
}
}
至此,Servlet基本流程就学习结束。