1.HttpServlet
我们写 Servlet 代码的时候,⾸先第⼀步就是先创建类,继承⾃ HttpServlet,并重写其中的某些⽅法。
1.1.核心方法
实际开发的时候主要重写 doXXX ⽅法,很少会重写 init / destory / service。
这些⽅法的调⽤时机,就称为 "Servlet ⽣命周期"。(也就是描述了⼀个 Servlet 实例从⽣到死的过程)
注意:HttpServlet 的实例只是在程序启动时创建⼀次,⽽不是每次收到 HTTP 请求都重新创建实例。
PS:所有方法
1.2.处理 GET 请求
@WebServlet("/hello") // http://ip:port/hello -> url映射到当前类
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//返回的类型和编码格式
resp.setContentType("text/html; charset=utf-8"); //返回的不是html静态页面,返回的是html里的内容
//返回的数据
resp.getWriter().println("<h1>你好,Servlet.</h1>"); //打印器:打印数据给前端,此处println也可使用write来代替
}
}
PS:关于中文乱码问题
- 如果在响应代码中写⼊中⽂,此时在浏览器访问的时候,会看到 "乱码" 的情况。因为如果自己不设置编码格式,会默认使用Content-Type:text/html; charset=ISO-8859-1;
- 关于 "乱码":中⽂的编码⽅式有很多种,其中最常⻅的就是 utf-8。如果没有显式的指定编码⽅式,则浏览器不能正确识别编码,就会出现乱码的情况。
- 可以在代码中,通过 resp.setContentType("text/html; charset=utf-8"); 显式的指定编码⽅式。
1.3.处理POST请求
PS:数据提交的两种方式——form表单提交&ajax提交
(GET形式通过浏览器访问URL传参提交。)
通过前端页面提交:
- form 表单提交
- ajax 提交
⼀个 Servlet 程序中可以同时部署静态⽂件,静态⽂件就放到 webapp ⽬录中即可。
①form表单提交
定义交互规范:
Request:
- URL地址:xxx/login
- Parmams请求参数:username=xxx&password=yyy
Response:
- 结果信息:如果前端输入的用户名和密码都等于admin,就认为登录成功,否则登录失败。
在webapp目录下创建前端登录页面login.html:
(在Idea中修改HTML代码不需要重启Tomcat,直接刷新浏览器页面即可;而修改Servlet代码需要重启Tomcat。)
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录系统</title> </head> <body> <form action="login" method="post"> <div style="margin-top:50px;margin-left:40%;"> <h1 style="padding-left:50px;">用户登录</h1> 姓名:<input type="text" name="username"> <p> 密码:<input type="password" name="password"> <p> <div style="padding-left:50px;"> <input type="submit" value=" 提 交 "> <input type="reset" value=" 重 置 "> </div> </div> </form> </body> </html>
在java源文件目录下创建后端类LoginServlet:
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("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String result = "未知错误"; //1.得到参数 String username = req.getParameter("username"); String password = req.getParameter("password"); //2.业务处理 //非空效验 if(null != username && null != password && !username.equals("") && !password.equals("")) { if(username.equals("admin") && password.equals("admin")) { result = "恭喜:登录成功!"; } else { result = "登录失败,用户名或密码输入错误,请检查!"; } }else { result = "非法参数!"; } //3.将结果返回给前端 //为防止中文乱码,需要设置 resp.setContentType("text/html; charset=utf-8"); resp.getWriter().write(result); } }
POST方式提交存在的问题:
- 不支持浏览器访问。
- 且每一次提交都会重新发送请求。
form表单提交存在的问题:
- 提交数据后,整个页面都会提交,整个页面没了:由login.html->login。
②ajax提交
Ajax 即 Asynchronous Javascript And XML(异步JavaScript和XML),使⽤ Ajax 不需要刷新整个⻚⾯,也叫局部提交,在当前页面得到响应结果,这使得程序能够更快地回应⽤户的操作。
ajax使用方式有2种:
- 原生的ajax方式。
- jQuery方式。
不使用原生的写法,原因:
- 太麻烦。
- 在不同浏览器的写法不同。
那么->使用jQuery方式。
在webapp目录下创建前端登录页面login-ajax.html:(在VSCode中打开写代码,支持更好~)
在webapp目录下创建js目录,将jquery复制进来。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录系统</title> <script src="js/jquery-1.9.1.min.js"></script> </head> <body> <div style="margin-top:50px;margin-left:40%;"> <h1 style="padding-left:50px;">用户登录</h1> 姓名:<input id="username" type="text"><p> 密码:<input id="password" type="password"><p> <div style="padding-left:50px;"> <input type="button" value=" 提 交 " onclick="mySubmit()"> <input type="reset" value=" 重 置 "> </div> </div> <script> //ajax请求登录 function mySubmit() { //非空效验,在前端就屏蔽掉,消耗浏览器(客户端)的资源,不能浪费宝贵的服务器端资源 var username = jQuery("#username"); var password = jQuery("#password"); if("" == jQuery.trim(username.val())) { //jQuery.trim()是去空格 alert("请先输入用户名!"); username.focus(); //将光标回置到username上 return; //js的方法没有返回的类型,此处可以返回值,也可不返回值。 } if("" == jQuery.trim(password.val())) { alert("请先输入密码!"); password.focus(); return; } jQuery.ajax({ url:"login", //设置请求地址,"login"为⼀个相对路径, 最终真实发送的请求的 URL 路径为"servlet-demo/login" type:"POST", //设置请求方法类型,要大写 contentType:"application/x-www-form-urlencoded; charset=utf-8", //设置请求类型,防止中文乱码 dataType:"", //设置响应类型及其编码格式,通常情况下不需要在前端设置,在后端会设置 data:{"username":username.val(),"password":password.val()}, //设置请求参数,一定要和后端的key值保持一致,否则会出现“非法参数”的错误 success:function(data) { //回调函数 alert(data); } }); } </script> </body> </html>
不管前端框架怎么变,后端代码不用变。
当没有设置Content-Type时,默认是表单类型:
Content-Type: application/x-www-form-urlencoded; charset=UTF-8";
通过类似的⽅式还可以验证 doPut、doDelete 等⽅法,此处不再⼀⼀演示。
2.HttpServletRequest
2.1.核心方法
目前对于Servlet的请求,最常见的ContentType有3种:
- ①默认的表单提交方式(最常用):原生的form表单提交、ajax提交(没有设置ContentType)。
- ②json数据传输方式:可以跨业务和语言的数据格式。
- ③multipart/form-data文件提交方式。
- 用哪个要看这个接口是自己去用还是公共的。
通过这些⽅法可以获取到⼀个请求中的各个⽅⾯的信息。
注意:请求对象是服务器收到的内容,不应该修改。因此上⾯的⽅法也都只是 "读" ⽅法,⽽不是 "写" ⽅法。
2.2.核心方法示例
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("/myreq")
public class MyReqServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使得当前的类既支持GET方式,又支持POST方式
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
StringBuilder builder = new StringBuilder();
//得到请求的协议和版本号
builder.append("协议:" + req.getProtocol() + "<br>");
//得到请求的类型
builder.append("Method:" + req.getMethod() + "<br>");
//得到URI-是URL除去IP+端口号,直接是资源地址
builder.append("URI:" + req.getRequestURI() + "<br>");
//得到URL-统一资源定位符,是全的地址
builder.append("URL:" + req.getRequestURL() + "<br>");
//得到访问URL中当前网站的地址,URI的前半部分
builder.append("ContextPath:" + req.getContextPath() + "<br>");
//得到所有的参数,不是body里面的参数,是URL后面跟的参数
builder.append("QueryString:" + req.getQueryString() + "<br>");
builder.append("<hr><h2>请求头</h2>");
//得到所有的请求类型(headerNames)和请求值
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()) { //判断还有没有head name,判断之后光标不会往下移动
String headName = headerNames.nextElement(); //光标下置
//得到head value
String headValue = req.getHeader(headName);
builder.append(headName + ": " + headValue + "<br>");
}
builder.append("<hr>");
builder.append("参数:" + req.getParameter("name") + "<br>");
resp.getWriter().write(builder.toString());
}
}
PS:Postman
- 安装时直接点下一步。
- Postman是做模拟请求的。前后端配合时,主动权在后端,模拟前端进行数据的提交,后端就不需要等前端的页面了,只需要规定好key值即可。
- 测接口时也会用。有时页面是有限制的,比如输入了js,不让用户名为空,此时就可以通过其他手段,模拟请求,绕过js,不输入用户名,就能把请求发给后端。
在Header里设置请求头:
2.3.获取JSON格式数据
使⽤之前 getParameter 的⽅式是获取不了 JSON 格式的数据的。会得到null。
2.3.1.使用 InputStream 获取 JSON 参数
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
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("/postjson")
public class JsonPostServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp); //有GET请求会直接转发到POST上
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置返回类型和编码
resp.setContentType("text/html; charset=utf-8");
//1.得到数据流
ServletInputStream inputStream = req.getInputStream();
//2.使用数组(容器)接收流信息
byte[] bytes = new byte[req.getContentLength()];
inputStream.read(bytes);
//3.将数组转换成字符串(或对象)
String result = new String(bytes, "utf-8");
System.out.println(result);
resp.getWriter().println(result);
}
}
2.3.2.使用 Jackon 库进行 JSON 解析
--->前置知识:
--->PS:Java中JSON对象操作
- 阿里巴巴(国内):fast json(号称最快,但漏洞多)/ fast json2(性能是fast json的1.65倍)
- 阿帕奇(Java 领域最大的国际社区):jackson(用的最多的 json 转换工具,被 Spring 内置,问题少,性能适中)
- 将对象转换成 json 字符串(字符串可以保存在磁盘上)(这个过程也叫序列化)。
- 将 json 字符串转换成对象(这个过程也叫反序列化)。
在中央仓库中搜索 Jackson,选择 JackSon Databind,将 Jackon 依赖添加到项目的 pom.xml 文件中,点击 reload 按钮。
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.1</version>
</dependency>
在java源文件夹下创建普通类App:
Lombok可在Maven中央仓库搜索依赖,添加到项目的pom.xml中。
也可以网页版安装:
Lombok网页安装https://plugins.jetbrains.com/plugin/6317-lombok
也可以直接通过Idea添加Lombok依赖:
Lombok中的@Data注解就会自动给每个属性添加get方法和set方法(@Getter注解和@Setter注解)以及其他的一些方法(如toString方法)。
这只是给项目添加了Lombok,要想给Idea添加Lombok,使得打点有对应的提示,需要添加Lombok插件:
添加toString方法(打印对象的):
或者直接使用Lombok中的@ToString注解(被@Data注解包含着):
语义更明确:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
public class App {
public static void main(String[] args) throws JsonProcessingException {
//1.创建一个json操作对象
ObjectMapper objectMapper = new ObjectMapper();
//2.1.将对象转换成json字符串/序列化
Student student = new Student();
student.setId(1);
student.setName("Java");
student.setPassword("123");
String result = objectMapper.writeValueAsString(student);
System.out.println(result);
//2.2.将json字符串转换成对象
String jsonStr = "{\"id\":2,\"name\":\"lisi\",\"password\":\"456\"}"; //直接复制上面在控制台打印的内容,修改即可
Student lisi = objectMapper.readValue(jsonStr, Student.class); //参数:(字符串,要转换的目标类型)
System.out.println(lisi);
}
}
@Setter
@Getter
@ToString
class Student {
private int id;
private String name;
private String password;
//...
}
--->PS:若觉得定义Student类太麻烦,还有更简单的写法——可以转换成HashMap(不推荐)。
- 因为json都是key-value形式,HashMap也是key-value形式。
- 但HashMap是语义不明确的,企业中大部分团队不允许使用。
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.util.HashMap; import java.util.Map; public class App { public static void main(String[] args) throws JsonProcessingException { //1.创建一个json操作对象 ObjectMapper objectMapper = new ObjectMapper(); //2.1.将对象转换成json字符串/序列化 Student student = new Student(); student.setId(1); student.setName("Java"); student.setPassword("123"); String result = objectMapper.writeValueAsString(student); System.out.println(result); //2.2.将json字符串转换成对象 String jsonStr = "{\"id\":2,\"name\":\"lisi\",\"password\":\"456\"}"; //直接复制上面在控制台打印的内容,修改即可 HashMap<String, Object> map = objectMapper.readValue(jsonStr, HashMap.class); for(Map.Entry<String, Object> item : map.entrySet()) { System.out.println(item.getKey() + ":" + item.getValue()); } } } @Setter @Getter @ToString class Student { private int id; private String name; private String password; //... }
--->回到项目:
JSON后端读取——创建类JsonPostServlet:
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
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.HashMap;
@WebServlet("/postjson")
public class JsonPostServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp); //有GET请求会直接转发到POST上
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置返回类型和编码
resp.setContentType("text/html; charset=utf-8");
//1.得到数据流
ServletInputStream inputStream = req.getInputStream();
//2.使用数组(容器)接收流信息
byte[] bytes = new byte[req.getContentLength()];
inputStream.read(bytes);
//3.将数组转换成字符串(或对象)
String result = new String(bytes, "utf-8");
System.out.println(result);
//4.将字符串转换成对象(或字典)
ObjectMapper objectMapper = new ObjectMapper();
HashMap<String, String> map = objectMapper.readValue(result, HashMap.class);
System.out.println("用户名:" + map.get("username"));
System.out.println("密码:" + map.get("password"));
resp.getWriter().println("用户名:" + map.get("username") + "| 密码:" + map.get("password"));
}
}
到了JavaEE只需要加一个注解即可简单解决!
JSON前端请求——创建页面login-ajax-json.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录系统</title>
<script src="js/jquery-1.9.1.min.js"></script>
</head>
<body>
<div style="margin-top:50px;margin-left:40%;">
<h1 style="padding-left:50px;">用户登录</h1>
姓名:<input id="username" type="text"><p>
密码:<input id="password" type="password"><p>
<div style="padding-left:50px;">
<input type="button" value=" 提 交 " onclick="mySubmit()">
<input type="reset" value=" 重 置 ">
</div>
</div>
<script>
//ajax请求登录
function mySubmit() {
//非空效验,在前端就屏蔽掉,消耗浏览器(客户端)的资源,不能浪费宝贵的服务器端资源
var username = jQuery("#username");
var password = jQuery("#password");
if("" == jQuery.trim(username.val())) { //jQuery.trim()是去空格
alert("请先输入用户名!");
username.focus(); //将光标回置到username上
return; //js的方法没有返回的类型,此处可以返回值,也可不返回值。
}
if("" == jQuery.trim(password.val())) {
alert("请先输入密码!");
password.focus();
return;
}
jQuery.ajax({
url:"postjson", //设置请求地址
type:"POST", //设置请求方法类型,要大写
contentType:"application/json; charset=utf-8", //设置请求类型,防止中文乱码
dataType:"", //设置响应类型及其编码格式,通常情况下不需要设置
//JSON.stringify() -> 将对象转换成JSON字符串
data:JSON.stringify({"username":username.val(),"password":password.val()}), //设置请求参数,一定要和后端的key值保持一致,否则会出现“非法参数”的错误
success:function(data) { //回调函数
alert(data);
}
});
}
</script>
</body>
</html>
3.HttpServletResponse
3.1.核心方法
注意:响应对象是服务器要返回给浏览器的内容,这⾥的重要信息都是程序猿设置的。因此上⾯的⽅法都 是 "写" ⽅法。
注意:对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前,否则可能设置失效。
3.2.代码示例:设置状态码
实现⼀个程序,⽤户在浏览器通过参数指定要返回响应的状态码。
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("/state")
public class StateServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//GET是不能发JSON的,因为JSON的数据是在body里的
int state = Integer.valueOf(req.getParameter("state"));
if(state > 0) {
resp.setStatus(state);
} else {
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().println("<h2>无效参数</h2>");
}
}
}
变换不同的 status 的值,就可以看到不同的响应结果。
3.3.代码示例:自动刷新
实现⼀个程序,让浏览器每秒钟⾃动刷新⼀次(HTML页面本身自己就能刷新)。并显示当前的时间戳。
PS:配置Servlet的JDK版本
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.time.LocalDateTime;
@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Refresh", "1");
resp.getWriter().println("" + LocalDateTime.now());
}
}
3.4.代码示例:重定向
实现⼀个程序,返回⼀个重定向 HTTP 响应,⾃动跳转到另外⼀个⻚⾯。
创建工具类——用来判null和判空:
public class StringUtils {
/**
* 用来判null和判空的,如果为null或空,返回true
* @param str
* @return
*/
public static boolean hasLength(String str) {
return str == null || str.length() == 0;
}
}
import utils.StringUtils;
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("/main")
public class MainServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String token = req.getParameter("token");
if(!StringUtils.hasLength(token) && "admin".equals(token)) {
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8"); //可以分开写,也可以写在一起,都可以
resp.getWriter().println("<h1>欢迎您:系统管理员!</h1>");
} else {
//未授权,跳转到登录页面
resp.sendRedirect("http://localhost:8080/servlet-demo/login.html");
}
}
}