目录
前置知识
什么是Servlet
Servlet 是一种实现动态页面的技术. 是一组 Tomcat 提供给程序员的 API, 帮助程序员简单高效的开发一个 web app
基于Servlet开发
1.初始化,允许程序员注册一个类到Tomcat中,让这个类和HTTP中的一个特定的请求相关联
2.进入循环,循环的处理很多请求
(1)读取HTTP请求,Servlet解析这个请求字符串,生成一个HttpServletRequest对象
(2)根据请求对象生成一个HttpServletResponse对象,根据请求生成响应,这个过程,就是初始化阶段注册的类里面的代码完成的
(3)把HttpServletResponse对象转换成HTTP响应,返回给浏览器
我们程序员,只需要关注2-(2)这个环节(业务逻辑),其他环节Tomcat/Servlet已经帮助我们实现好了
第一个Servlet程序
1.创建项目
创建一个maven项目
2.引入依赖
在pom.xml里引入ServletAPI的依赖包,然后刷新maven
3.创建目录
依次创建这三个目录
并且在web.xml里写一些代码
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> </web-app>
4.编写代码
先写一个简单的helloworld
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也是Servlet里的注解,功能是把类和HTTP特定的请求进行关联,是根据URL请求的路径来关联的 //如果Tomcat收到了一个路径为/hello的请求,就会调用到HelloServlet的代码 //如果这个请求是GET请求,就会调用到HelloServlet的doGet的方法 @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //这个调用父类代码的操作,是直接构造了一个错误的响应(状态码为405的响应) //super.doGet(req, resp); //这个操作,就是往HTTP响应的body中,写了一个helloworld的字符串 resp.getWriter().write("helloworld"); } }
5.打包程序
之后会出现
而Tomcat里面需要的是.war的文件,所以我们需要先配置pom.xml
然后再刷新maven,再双击package
我们看到这个名字是artfact id + version,名字很复杂,我们需要换一个名字
先删除原文件,再配置pom.xml
刷新maven,双击package
6.部署程序
把war包拷贝到Tomcat中webapps目录就行
在WEB-INF目录中,就包含了web.xml以及编译后生成的.class文件
META-INF目录中,包含了当前webapp中以来的一些第三方jar包
7.验证程序
更方便的部署方式
安装Smart Tomcat插件
配置Smart Tomcat插件
1.开始配置
2.更改名字并且设置路径(记住这里的ContextPath为/servlet)
3.此时IDEA上面会出现Tomcat的图标
4.可以看到运行成功了
5.测试一下
访问出错怎么办
出现404
1.URL写错了
2.web.xml写错了
在IDEA终端可以查看到
出现405
请求的方法和代码中重写的方法对不上号
因为我这里改成了doPost方法
出现500
1.代码抛异常了
因为我这里写了一个空指针异常
2.没注释掉super
我这里super调用了父类代码
出现空白页面
1.代码里没往body里写东西
出现无法访问此网站
2.Tomcat启动失败了或者没启动
Servlet运行原理
Tomcat拿到HTTP请求后,就会对请求进行解析,生成一个HttpServletRequest对象,调用Servlet类,执行程序员写好的代码
Servlet在初始化的时候,会先调用一次init方法,可以自己重写init,就可以在初始化阶段做一些事情了
Servlet在销毁之前,会调用一次destroy方法,也可以重写destroy方法,做一些善后工作(但实际上,不一定能真的执行到destroy方法,如果直接kill到tomcat进程,此时destroy都来不及执行)
ServletAPI详解
我们写 Servlet 代码的时候,首先第一步就是先创建类,继承自 HttpServlet,并重写其中的某些方法,重写的目的是为了能够把程序员定义的逻辑插入到Tomcat这个框架中,好让Tomcat可以调用
HttpServlet
核心方法
说一下servlet的生命周期
1.Servlet在实例化之后调用一次init
2.Servlet每次收到请求,调用一次service
3.Servlet在销毁之前,调用一次destroy
处理GET/POST请求
我们需要写两个文件
首先重写doGet和doPost方法
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("/method") public class MethodServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("test/html;charset=utf-8"); resp.getWriter().write("GET 响应"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("test/html;charset=utf-8"); resp.getWriter().write("POST 响应"); } }
在一个html文件中,新增一个按钮,和对应的点击事件处理函数
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <button onclick="sendGet()">发送GET请求</button> <button onclick="sendPost()">发送POST请求</button> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script> function sendGet() { $.ajax({ type: "get", url: "method", success: function (data, status) { console.log(data); } }) } function sendPost() { $.ajax({ type: "post", url: "method", data: "request body", success: function (data, status) { console.log(data); } }) } </script> </body> </html>
然后我们就可以测试一下了
PUT请求也是同理,需要注意的是
HttpServletRequest
核心方法
一些方法的解释
URL和URI的区别
L location 资源的位置
I Id 资源的标识符
含义类似,只要能唯一标识资源的就是URI,在URI的基础上给出其资源的访问方式就是URL
打印请求信息
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.util.Enumeration; @WebServlet("/showRequest") public class ShowRequestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); //把生成的响应body放到respBody中 StringBuilder respBody = new StringBuilder(); respBody.append(req.getProtocol()); respBody.append("<br>"); respBody.append(req.getMethod()); respBody.append("<br>"); respBody.append(req.getRequestURI()); respBody.append("<br>"); respBody.append(req.getContextPath()); respBody.append("<br>"); respBody.append(req.getQueryString()); respBody.append("<br>"); respBody.append("<h3>headers:</h3>"); Enumeration<String> headerNames = req.getHeaderNames(); while(headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); respBody.append(headerName+": "); respBody.append(req.getHeader(headerName)); respBody.append("<br>"); } resp.getWriter().write(respBody.toString()); } }
效果
获取GET请求中的参数
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("/getParameter") public class GetParameterServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); String userId = req.getParameter("userId"); String classId = req.getParameter("classId"); /* if(userId==null||userId.equals("")){ //参数不存在 //处理参数不存在的情况 } */ resp.getWriter().write(String.format("userId: %s; classId: %s <br>",userId,classId)); } }
效果
获取POST请求中的参数
1.application/x-www-form-urlencoded
这种格式类似于query string格式,所以还是用原来getParameter的方法进行获取
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("/postParameter") public class PostParameterServlet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); String userId = req.getParameter("userId"); String classId = req.getParameter("classId"); resp.getWriter().write(String.format("userId: %s; classId: %s <br>",userId,classId)); } }
testPost.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>testPost</title> </head> <body> <!--使用ajax/form表单构造POST请求--> <form action="postParameter" method="POST"> <input type="text" name="userId"> <input type="text" name="classId"> <input type="submit" name="提交"> </form> </body> </html>
效果
2.mutlipart/form-data
这种格式比较复杂,主要是用来提交文件的
3.application/json
先把整个body读取出来,再使用json库解析
import com.fasterxml.jackson.databind.ObjectMapper; 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; //通过这个类来表示解析后的结果 class JsonData{ public int userId; public int classId; } @WebServlet("/postParameterJson") public class PostParameterJson extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //先把整个body读出来 String body = readBody(req); //使用jackson来解析 //先创建一个jackson的核心对象ObjectMapper ObjectMapper objectMapper = new ObjectMapper(); JsonData jsonData = objectMapper.readValue(body,JsonData.class); //以上就完成了Json格式的字符串到Java对象的解析过程 resp.getWriter().write(String.format("userId: %s; classId: %s <br>",jsonData.userId,jsonData.classId)); } private String readBody(HttpServletRequest req) throws IOException{ //读取body需要根据req.getInputStream得到一个流对象,从这个流对象中读取 InputStream inputStream = req.getInputStream(); //通过contentLength拿到请求中的body字节数 int contentLength = req.getContentLength(); byte[] buffer = new byte[contentLength]; inputStream.read(buffer); return new String(buffer,"utf-8"); } }
如果body的值是{"userId":100,"classId":1}
1.先把JSON格式的字符串转换成类似于HashMap的键值对结构
userId:100 classId:1
2.根据类对象,获得到要转换结果的类,都有哪些属性,获得到属性的名字
此处是通过JsonData获取到里面的属性,有两个userId和classId(通过反射机制)
3.拿这JsonData这里的每个属性的名字,在上面的第一步构造出的哈希表里去查,如果查到了,就把查询的值赋值到JsonData对应的属性里面
testPost2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>testPost2</title> </head> <body> <!--构造body为json格式的数据就只能使用ajax的方式来实现--> <button onclick="sendJson()">发送请求</button> <script src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js"></script> <script> function sendJson(){ let body = { userId : 100, classId : 1 }; $.ajax({ type: "post", url: "postParameter", contentType : "application/json;charset=utf-8", data: JSON.stringify(body), success:function(data,status){ console.log(body); } }) } </script> </body> </html>
效果
一般用form或者ajax来构造请求,这样很麻烦,所以我们可以用第三方工具postman来构造请求
HttpServletResponse
核心方法
HttpServletRequest里面的内容,是客户端构造的,服务器需要做的就是获取到这里的内容,尤其是程序员自己定义的数据
HttpServletResponse里面的内容,是服务器构造的,要返回给客户端的
注意: 对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前. 否则可能设置失效.
设置状态码
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("/status") public class StatusServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); //让用户传入一个请求 //请求在query string中带一个参数,就表示响应的状态码 //根据用户的输入,返回不同的状态码的响应 String statusString = req.getParameter("status"); if(statusString==null||statusString.equals("")){ resp.getWriter().write("当前请求的参数status缺失"); return; } resp.setStatus(Integer.parseInt(statusString)); resp.getWriter().write("status: "+statusString); } }
效果
Fiddler抓包
自动刷新
HTTP响应中可以设置一个header,Refresh的值就是刷新的间隔时间(s)
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("/autoRefresh") public class AutoRefreshServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf-8"); //设置一个毫秒级时间戳 resp.setHeader("Refresh","1"); long timeStamp = System.currentTimeMillis(); resp.getWriter().write("time: " + timeStamp); } }
效果
重定向
重定向就是呼叫转移
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("/redirect") public class RedirectServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.sendRedirect("https://www.baidu.com/"); } }
效果
Cookie和Session
Cookie是服务器通过HTTP响应的Set-Cookie字段来设置的,返回给浏览器的。
Cookie是浏览器通过域名/地址存储的,在下次请求中,会自动被添加到请求里,发送给服务器。
Cookie是键值对存储的,这个键值对由程序员自己定义
Session是服务器存储的,像一个简单的hash表,key就是sessionId,value就是程序员自己定义的存储用户身份信息的数据
核心方法
HttpServletRequest类
HttpSession getSession()功能:
尝试根据当前请求中的sessionId获取到当前的session
1.如果存在就返回;
2.如果不存在就创建:
创建一个HttpSession对象,作为value
创建一个sessionId作为key
把key和value插入到hash表中
同时把这个sessionId通过Set-Cookie字段返回给浏览器
HttpServletResponse类
HttpSession类中的相关方法
一个HttpSession对象里面包含多个键值对
Cookie类中的相关方法
每个Cookie对象就是一个键值对
实现用户登录
IndexServlet.java
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.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/index") public class IndexServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //根据用户请求中的sessionId,获取到用户信息并显示在页面上 resp.setContentType("text/html;charset=utf-8"); //1.判断当前用户是否已经登录 HttpSession httpSession = req.getSession(false); if(httpSession==null){ //如果当前会话不存在,则说明用户尚未登录,重定向到login.html resp.sendRedirect("login.html"); return; } //2.如果用户已经登录,就可以从HttpSession中拿到用户信息了 String username = (String) httpSession.getAttribute("username"); Integer loginCount = (Integer) httpSession.getAttribute("loginCount"); loginCount=loginCount+1; httpSession.setAttribute("loginCount",loginCount); //3.返回一个html页面 StringBuilder html = new StringBuilder(); html.append("<div>用户:"+username+"</div>"); html.append("<div>访问次数"+loginCount+"</div>"); resp.getWriter().write(html.toString()); } }
LoginServlet.java
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.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); //1.先从请求的body中读取用户名和密码 String username = req.getParameter("username"); String password = req.getParameter("password"); //2.判定用户名和密码是否正确 if(!"root".equals(username)||!"123456".equals(password)){ //登录失败 resp.getWriter().write("登陆失败"); return; } System.out.println("登录成功"); //3.登录成功则创建会话 //会话是根据请求中的sessionId查的,sessionId是在cookie中的 //因为此处是首次登录,请求中还没有cookie,所以就会走”新建会话“这样的流程 //同时进行操作,创建出一个HttpSession对象作为value,创建出一个sessionId作为key //把key和value存入到哈希表中 //同时把生成的sessionId作为set-cookie字段返回给浏览器 HttpSession httpSession = req.getSession(true); //此时存入程序员自定义的数据 httpSession.setAttribute("username","root"); httpSession.setAttribute("loginCount",0); //4.让页面跳转至主页,用重定向的方式实现 resp.sendRedirect("index"); } }
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>登录页面</title> </head> <body> <form action="login" method="post"> 用户名:<input type="text" name="username"> <br> 密码:<input type="password" name="password"> <br> <input type="submit" value="登录"> </form> </body> </html>
抓包查看
login请求
login响应
index请求
index响应
上传文件
核心方法
HttpServletRequest类
Part类
实现提交图片
UploadServlet
import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.IOException; @MultipartConfig @WebServlet("/upload") public class UploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); //1.从req对象中读取Part对象 Part part = req.getPart("MyImage"); //2.读取Part对象中的一些参数 System.out.println(part.getSubmittedFileName());//上传文件的真实文件名 System.out.println(part.getContentType());//文件类型 System.out.println(part.getSize());//文件大小 //3.把文件列入到指定目录中 part.write("C:/files/MyImage.jpg"); //4.返回一个响应 resp.getWriter().write("upload ok"); } }
upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>上传图片</title> </head> <body> <form action="upload" method="POST" enctype="multipart/form-data"> <input type="file" name="MyImage"> <input type="submit" value="提交图片"> </form> </body> </html>
响应
IDEA控制台
请求