Servlet
什么是 Servlet
1、Servlet 是 JavaEE 规范之一。规范就是接口
2、Servlet 就 JavaWeb 三大组件之一。三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器。
3、Servlet 是运行在服务器上的一个 java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端。
// Servelet接口中定义的抽象方法
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
HelloServlet
Sun公司对Servlet接口有两个默认的实现类:HttpServlet,GenericServlet,也就是说,关于Servlet接口的父子关系应该如下图:
分析源码可以发现,Servlet接口中有一个service抽象方法,GenericServlet也仅是继承了service接口,继承了GenericServlet才实现了service方法,于是我们要做的就是在自己的类里,重写HttpServlet中定义的方法!
1、创建一个普通的Maven项目,删掉里面的src目录,这个空的工程就是Maven主工程!
2、关于Maven父子工程的理解:
- 父项目
<modules>
<module>Servlet-01</module>
</modules>
- 子项目
<parent>
<artifactId>Servlet</artifactId>
<groupId>com.ayin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
子项目会继承父项目中引入的jar包!
3、Maven环境优化
- 修改web.xml为最新
<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"
metadata-complete="true">
<display-name>Archetype Created Web Application</display-name>
</web-app>
- 将Maven的结构搭建完整(main目录下需要java文件夹和resources文件夹)
4、编写一个Servlet程序
-
编写一个普通类HelloServlet
-
实现Servlet接口,这里我们直接继承HTTPServlet接口
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.print("HelloServlet!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
需要注意的是,由于get或者post只是请求的实现方式不同,可以进行相互调用,业务逻辑大致上是相同的!
5、 编写Servlet的映射
为什么需要映射?因为我们所写的是Java程序,但是要通过浏览器进行访问,而浏览器需要链接web服务器,所以我们要在web服务 器中注册我们写Servlet,而且还需要一个浏览器能够访问的路径!
<!-- 注册servlet-->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.ayin.servlet.HelloServlet</servlet-class>
</servlet>
<!-- servlet的请求路径-->
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
6、 配置Tomcat
7、 启动测试
关于启动测试的可能遇到的问题:
- 500错误:更换Tomcat版本
Servlet原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-td2J0Mt0-1636796898589)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211031155127866.png)]
Mapping问题
1、一个servlet可以指定一个映射路径(指定了/hello路径)
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
2、一个servlet可以指定多个映射路径
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
3、一个servlet可以指定通用映射路径
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
4、默认请求路径
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
5、指定后缀或者前缀
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
注意:可以自定义后缀实现请求映射,但是*号前面不可以加路径映射!并且,只要映射路径为指定后缀,都可以访问到!
6、优先级问题
指定了固有的映射路径优先级最高,如果找不到则会走默认的处理请求!
自定义404页面
<!-- 注册servlet-->
<servlet>
<servlet-name>ErrorServlet</servlet-name>
<servlet-class>com.ayin.servlet.ErrorServlet</servlet-class>
</servlet>
<!-- servlet的请求路径-->
<servlet-mapping>
<servlet-name>ErrorServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
public class ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
PrintWriter respWriter = resp.getWriter();
respWriter.print("<h1>404</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
Servlet 的生命周期
1、执行 Servlet 构造器方法
2、执行 init 初始化方法
- 第一、二步,是在第一次访问,的时候创建 Servlet 程序会调用
3、执行 service 方法
- 第三步,每次访问都会调用
4、执行 destroy 销毁方法
- 第四步,在 web工程停止的时候调用
public class HelloServlet implements Servlet {
public HelloServlet() {
System.out.println("1.执行构造器方法");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("2.执行init构造器方法");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("3.执行service方法,servlet被访问");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("4.执行destroy销毁方法");
}
}
ServletContext
web容器在启动的时候,他会为每个web程序都创建一个对应的ServletContext对象,它代表了当前web应用,并且可以储存数据!web应用下有可以有多个Servlet程序,用于接受浏览器发送过来的请求,也可以通过this调用当前servlet程序所面向的ServeletContext对象!以下代码实例为不同Servlet程序的数据共享!
存放数据至ServletContext:
public class DoSet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// this.getServletContext() servlet的上下文
ServletContext servletContext = this.getServletContext();
String username = "Ayin";
servletContext.setAttribute("username", username);
// 将一个数据保存在ServletContext中,名字为username,值为Ayin
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
从ServletContext取出数据:
public class DoGet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
String name = (String) servletContext.getAttribute("username");
PrintWriter writer = resp.getWriter();
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
writer.print(name);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
HttpServletResponse
web服务器接收客户端的http请求,或者说是需要给客户端响应一些信息,针对这个请求,分别创建一个代表请求的HttpServletResponse对象,代表响应的一个HttpServletResponse!
负责向浏览器发送数据的方法:
ServletOutputStream out = resp.getOutputStream();
PrintWriter printWriter = resp.getWriter();
负责向浏览器发送响应头(response headers)
ServletResponse:
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
HttpServletResponse:
void setDateHeader(String var1, long var2);
void addDateHeader(String var1, long var2);
void setHeader(String var1, String var2);
void addHeader(String var1, String var2);
void setIntHeader(String var1, int var2);
void addIntHeader(String var1, int var2);
响应状态码:
200:请求响应成功
3xx:资源重新定向
4xx:找不到资源(504)
- 资源不存在
5xx:服务器代码错误(502:网关错误)
HttpServletResponse实现下载文件
需求:浏览器下载指定文件!
public class DownloadImg extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取下载的文件路径
String url =
"C:\\Users\\12709\\IdeaProjects\\javase\\Servlet\\Servlet-03\\src\\main\\resources\\德尔塔.jpg";
// 2. 获取下载的文件名
String filename = url.substring(url.lastIndexOf("\\") + 1);
// 3. 使浏览器能够支持我们所需下载的东西
resp.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
// 4. 创建文件输入流
FileInputStream in = new FileInputStream(url);
int len = 0;
// 5. 设置缓冲区
byte[] buffer = new byte[1024];
// 6. 获取OutputStream对象
ServletOutputStream out = resp.getOutputStream();
// 7. 将输出流写入buffer缓冲区,将缓冲区的数据输出到客户端
while((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
in.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>com.ayin.servlet.DownloadImg</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>download</servlet-name>
<url-pattern>/dl</url-pattern>
</servlet-mapping>
**注意:**URLEncoder.encode(filename, “UTF-8”)可以改变字符串的编码样式
HttpServletResponse实现重定向
由上图可知,重定向的意思就是A(客户端)向B(web资源)发送请求,B收到请求后,让A(客户端)去访问C(另一个web资源),这就好比你将一个物品给予一个人,而这个人把物品还给你并让你去将物品给予另一个人。
void sendRedirect(String var1) throws IOException;
具体实现:
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// resp.setHeader("Location", "/hello");
// resp.setStatus(302);
// sendRedirect()方法相当于将以上两行代码进行实现
resp.sendRedirect("/hello");
}
HttpServletRequest
HttpServletRequest代表着客户端的请求,用户通过http协议访问服务器,http请求中的所有信息会被封装在HttpServletRequest,通过
HttpServletRequest方法,获得客户端的所有信息!
获取前端传递的参数
req.getParameter(String s) // String
req.getParameterValues(String s) // String[]
HttpServletRequest实现请求转发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RSVUCEV4-1636796862958)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211104230153423.png)]
请求转发,简单而言就是客户端A想要访问web资源C,于是先访问了web资源B,然后B再访问C,C接受到请求后,将资源通过B返回给A!
代码实例:
ServletContext servletContext = this.getServletContext();
servletContext.getRequestDispatcher("/dispatcher").forward(req, resp);
具体实现:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="${pageContext.request.contextPath}/login" method="post">
用户名:<input type="text" name="username">
密码:<input type="text" name="password">
爱好:
<input type="checkbox" name="hobby" value="唱歌">唱歌
<input type="checkbox" name="hobby" value="跳舞">跳舞
<input type="checkbox" name="hobby" value="游戏">游戏
<input type="checkbox" name="hobby" value="动漫">动漫
<br>
<input type="submit">
</form>
</body>
</html>
req.getRequestDispatcher("/success.jsp").forward(req, resp);
当Tomcat服务器打开时,访问web工程下的/login路径,提交数据后发送请求,web资源会进行请求转发,使其跳转至/success.jsp!
转发和重定向的区别
相同点:
- 页面都会实现跳转
不同点:
- 请求转发的时候,页面进行了跳转,但是页面的路径(url)并不会被改变
- 重定向后,页面进行了跳转,页面的路径(url)也随之改变
- 请求转发的状态码是307,而重定向的状态码是302
Cookie、Session
在计算机术语中,会话是指一个终端用户与交互系统进行通讯的过程,比如从输入账户密码进入操作系统到退出操作系统就是一个会话过程。会话较多用于网络上,TCP的三次握手就创建了一个会话,TCP关闭连接就是关闭会话。
Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
平时访问浏览器时,输入账号密码登录一个网页,即使将网页关闭,将浏览器关闭,但下一次访问同一个网页时,是默认进入登录状态的,这就是日常中我们可看到的Cookie应用。
req.setCharacterEncoding("GBK");
resp.setCharacterEncoding("GBK");
PrintWriter out = resp.getWriter();
Cookie[] cookies = req.getCookies();
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("lastLoginTime")) {
Long lastLoginTime = Long.parseLong(cookie.getValue());
Date date = new Date(lastLoginTime);
out.write(date.toString());
}
}
Cookie cookie = new Cookie("lastLoginTime", System.currentTimeMillis() + "");
resp.addCookie(cookie);
Long.parseLong()方法可以将字符串(cookie.getValue())转成long类型,因为实例化Date需要一个long类型的参数!
通过浏览器的控制台,可以看到cookies的信息,部分浏览器在响应网页的时候会不止有一个cookie,所以请求req.getCookies()返回的是一个Cookies数组!
Session,在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。
这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。
例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。有关使用Session 对象的详细信息,请参阅“ASP应用程序”部分的“管理会话”。
注意会话状态仅在支持cookie的浏览器中保留。
public class SessionTest01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html;Charset=utf-8");
HttpSession session = req.getSession();
session.setAttribute("name", "ayin");
String id = session.getId();
if (session.isNew()) {
resp.getWriter().write("session创建成功:" + id);
} else {
resp.getWriter().write("session已存在:" + id);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
session已存在:703A717FABBAC7F9196FD6BF04500FBE
每个浏览器会在打开的一瞬间就完成创建session对象这一个操作,并且不同的浏览器,session对象也不同!
session.isNew() 可以判断session对象是否为新创建的对象,session.setAttribute() 可以在session中存储一些节点数据,也就是说,同理于ServletContext,我们可以在另一个类中获取session中的数据!
public class SessionTest02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html;Charset=utf-8");
HttpSession session = req.getSession();
String name = (String) session.getAttribute("name");
System.out.println(name);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
当然,也可以往session中存储一个对象:
Person name = (Person) session.getAttribute("name");
在设置文件web.xml中,可以设置session的失效时间:
<session-config>
<!-- 设置session失效时间为15分钟-->
<session-timeout>15</session-timeout>
</session-config>
关于Session和Cookie的区别:
- Cookie是把用户的数据写给用户的浏览器,浏览器保存(可以保存多个)
- Session把用户的数据写到用户独占的Session中,服务器端保存(保存重要的信息,减少服务器资源的浪费)
- Session由服务器对象创建
在一些场景中,例如:保存一个用户的信息,购物车信息,在整个网站中经常会使用的数据等,都会保存在Session中!