接:Servlet的日常开发(基于场景)_林纾໌້ᮨ的博客-CSDN博客
目录
观察上个博客中的代码可知,拼接HTML是比较麻烦的(都是字符串操作),需要手写写出所有字符串,可以有两种技术来改善:
1.模板技术(jsp、thymeleaf...)
2.前后端分离(后端只负责提供数据,以JSON格式,前端JS修改DOM树)
一、模板技术
提前把HTML的内容放在一个文件中
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模板</title> </head> <body> <h1>你好 {{ target }}</h1> </body> </html><!--这里写在项目的目录下,不写在webapp目录下-->
//Template:模板
//模板模式:提前写好一个文件(template.html)作为模板
public class WithTemplate {
public static void main(String[] args) throws IOException {
try(FileInputStream is=new FileInputStream("template.html")){
try(Reader reader=new InputStreamReader(is,"UTF-8")){
char[] buf=new char[1024];
int n=reader.read(buf);
String template=new String(buf,0,n);
String s=template.replace("{{ target }}","世界");
System.out.println(s);
}
}
}
}
二、前后端分离
前端通过ajax技术,从后端(通过HTTP协议)读取数据(数据的格式是JSON为主)
eg:对录入成绩前后端分离
静态资源:
<!DOCTYPE html>
<html lang="zh-hans">
<head>
<meta charset="UTF-8">
<title>成绩列表</title>
</head>
<body>
<table>
<tbody>
</tbody>
</table>
<!--前端要去后端去取一定需要一个js-->
<script>
function render(students) {
var tbody = document.querySelector('tbody');
for (var i in students) {
var s = students[i];
var tr = `<tr><td>${s.姓名}</td><td>${s.成绩}</td></tr>`;
tbody.innerHTML = tbody.innerHTML + tr;
}
}
var xhr = new XMLHttpRequest();//构建一个ajax出来
xhr.open("GET", "/data/grade-list.json");//把这个目录下的数据给我//注意下面动态时的路径写的是/grade-list.json
//前端只用读取json数据,读取出来后通过DOM树的修改操作把它加到tbody中
xhr.onload = function() {
// this.responseText 是 JSON 格式的字符串,是我们得到的响应体
// 进行 JSON 的反序列(进行JSON解码)
var students = JSON.parse(this.responseText);
// 进行渲染操作 (渲染:render)(进行DOM树的修改,即渲染操作)
render(students);
}
xhr.send();//发送
</script>
</body>
</html>
/**
* .json下的静态资源改为动态资源
**/
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {
private final HashMap<String,Integer> gradeMap=new HashMap<>();
//初始化
@Override
public void init() throws ServletException {
gradeMap.put("小A",100);
gradeMap.put("小B",90);
gradeMap.put("小C",80);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//输出:json格式
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json");//json格式的输出
PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
//调用getJSON方法进行拼接操作
String json=getJSON();
writer.println(json);
}
//手动写拼接方法
private String getJSON() {
StringBuilder sb=new StringBuilder();
sb.append("[");
for (Map.Entry<String,Integer> entry: gradeMap.entrySet()) {
String name= entry.getKey();
int value=entry.getValue();
sb.append("{")
.append("\"姓名\": ")
.append("\"")
.append(name)
.append("\",")
.append("\"成绩\": ")
.append(value)
.append("},");
}
sb.delete(sb.length()-1,sb.length());
sb.append("]");
return sb.toString();
}
}
上面改动态资源是用拼接json格式自己拼接的,比较麻烦,可以引用别人提供好的方法去做:引入一个第三方包:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.4</version> </dependency>
/**
* .json下的静态资源改为动态资源
**/
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {
// private final HashMap<String,Integer> gradeMap=new HashMap<>();
List<Map<String,Object>> gradeList=new ArrayList<>();//list里面放map
//初始化
@Override
public void init() throws ServletException {
// gradeMap.put("小A",100);
// gradeMap.put("小B",90);
// gradeMap.put("小C",80);
{
Map<String,Object> s=new HashMap<>();
s.put("姓名","小王");
s.put("成绩",100);
gradeList.add(s);
}
{
Map<String,Object> s=new HashMap<>();
s.put("姓名","小李");
s.put("成绩",90);
gradeList.add(s);
}
{
Map<String,Object> s=new HashMap<>();
s.put("姓名","小张");
s.put("成绩",80);
gradeList.add(s);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//输出:json格式
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json");//json格式的输出
PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
//调用getJSON方法进行拼接操作
String json=getJSON();
writer.println(json);
}
//2.调用别人写好的东西
//数组或线性表方式存储(要按协议来,map方式存储是不遵守协议的,打印不一样)//期望打印达到的效果是上面静态.json文件中哪个效果
private String getJSON() {
ObjectMapper om=new ObjectMapper();
try{
// return om.writeValueAsString(gradeList);
return om.writerWithDefaultPrettyPrinter().writeValueAsString(gradeList);//与上面一样,只是网页显示上带一些换行和缩进,对数据格式没影响
}catch (JsonProcessingException e){
throw new RuntimeException(e);
}
}
}
小结:index.html通过构建ajax GET(用户直接GET)请求动态资源/grade-list.json,动态资源根据数据修改DOM树(将index.html内容按所想要的方式呈现)
继续改造:
对象和Map其实是一回事,JSON直接支持定义好一个类往里面放东西,所以可以:
class Grade{
姓名;
成绩;
}
List<Grade>
public class Grade {
public String 姓名;
public int 成绩;
}
/**
* .json下的静态资源改为动态资源(动态获取json的一个工程)
**/
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {
// private final HashMap<String,Integer> gradeMap=new HashMap<>();
List<Map<String,Object>> gradeList=new ArrayList<>();//list里面放map
List<Grade> gradeList2=new ArrayList<>();
//初始化
@Override
public void init() throws ServletException {
{
Grade g=new Grade();
g.姓名="胡图图";
g.成绩=60;
gradeList2.add(g);
}
{
Grade g=new Grade();
g.姓名="张小丽";
g.成绩=98;
gradeList2.add(g);
}
// gradeMap.put("小A",100);
// gradeMap.put("小B",90);
// gradeMap.put("小C",80);
{
Map<String,Object> s=new HashMap<>();
s.put("姓名","小王");
s.put("成绩",100);
gradeList.add(s);
}
{
Map<String,Object> s=new HashMap<>();
s.put("姓名","小李");
s.put("成绩",90);
gradeList.add(s);
}
{
Map<String,Object> s=new HashMap<>();
s.put("姓名","小张");
s.put("成绩",80);
gradeList.add(s);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//输出:json格式
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json");//json格式的输出
PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
//调用getJSON方法进行拼接操作
String json=getJSON();
writer.println(json);
}
//2.调用别人写好的东西
//数组或线性表方式存储(要按协议来,map方式存储是不遵守协议的,打印不一样)//期望打印达到的效果是上面静态.json文件中哪个效果
private String getJSON() {
ObjectMapper om=new ObjectMapper();
try{
//将gradeList2序列化
return om.writerWithDefaultPrettyPrinter().writeValueAsString(gradeList2);
return om.writeValueAsString(gradeList);
// return om.writerWithDefaultPrettyPrinter().writeValueAsString(gradeList);//与上面一样,只是网页显示上带一些换行和缩进,对数据格式没影响
}catch (JsonProcessingException e){
throw new RuntimeException(e);
}
}
}
问题:一般java中不直接用中文起名,如Grade类中的姓名成绩,一般用英文起名,但是修改代码会出问题,即你在Grade修改成英文时,访问grade-list.json显示修改成功,但是调用index显示是错误显示的(因为html中写的是姓名成绩两个中文)。
解决:通过注解的方式
此时数据全部处理OK,可以将数据改到数据库中(利用之前数据库grades表,直接在数据库中查询),此时代码:
/**
* GetGradeList:从数据源(目前是MySQL)中,获取对应的数据
* 只要调用这个方法就可以从数据库把数据查出来
*/
public class GetGradeList {
List<Grade> list=new ArrayList<>();
public List<Grade> getList(){
try (Connection c= DBUtil.connection()){
String sql="select name,grade from grades order by id";
try(PreparedStatement ps=c.prepareStatement(sql)){
try (ResultSet rs= ps.executeQuery()){
while (rs.next()){
Grade grade=new Grade();
grade.name=rs.getString("name");
grade.score=rs.getInt("grade");
list.add(grade);
}
}
}
}catch (SQLException e){
throw new RuntimeException(e);
}
return list;
}
}
@WebServlet("/grade-list.json")
public class GradeListJsonServlet extends HttpServlet {
//初始化GetGradeList类:(该类负责从数据库中查出数据并放在了该类的list中返回)
private final GetGradeList getGradeList=new GetGradeList();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//输出:json格式
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json");//json格式的输出
PrintWriter writer = resp.getWriter();//把小A...这组数据类似拼接成开心超人那些属性那样的格式
//调用getJSON方法进行拼接操作
String json=getJSON();
writer.println(json);
}
//调用别人写好的东西
//将数据来源改为从数据库拿(与其他没有差别,只是这里数据源出现区别,变成了数据库中读到的数据)
private String getJSON() {
ObjectMapper om=new ObjectMapper();
try{
List<Grade> list=getGradeList.getList();//获取GetGradeList类的对象getGradeList调用到的数据(name,score)给list
return om.writerWithDefaultPrettyPrinter().writeValueAsString(list);//将数据序列化(调用别人写好的方法将数据转化为了对应的json格式)
}catch (JsonProcessingException e){
throw new RuntimeException(e);
}
}
}
总结—图析:
三、Java中为什么需要这么多类
java中一切皆对象,可分成两种:
1.承载数据的对象
eg:宫保鸡丁、鱼香肉丝、麻婆豆腐:是加工出来的产品
承载数据重要的是属性,如上面Grade类,类中只有属性name score,完全无方法,这就是承载数据的对象,以数据位主。
数据承担的是各种各样的序列化 | 如上面代码json格式的序列化 |
数据为什么序列化 | 序列化的目标是存储和搬运(酒厂中的酒才需要搬运,厂房不要) |
一般承载数据的对象中的方法 | equals、comparable、hashCode(产品间才有比较的价值) |
2.承载流程的对象
eg:前台、服务员、厨师:流程,是职责中的一部分
承载流程重要的是方法,如上面代码GetGradeList和GradeListJsonServlet就是流程,GetGradeList就是从数据库中把对象查出来这个过程,就是个流程/方法。
总而言之,实际开发中,有偏向数据的对象,有偏向属性的对象,一般也没有纯数据或纯方法,讲求一个偏向性来理解。
一般使用流程时主要是单例的
1)MVC:Model(模型-厨师)+View(展示)+Controller(控制器)
2)Controller(前台)+Service(服务员)+DataAccessObject(数据访问对象DAO(厨师))
Controller负责承接HTTP,即对接过来的客户点什么菜,DAO,即dao对象,库管,从数据库(仓库)里面把东西拿出来,Service上面代码无,我们流程简单,只是拿出来就给前台了,若业务复杂,就需要服务员参与。
对上面代码重构,分一下包:
GetGradeList放在dao层/包下,改名GradeDao,可以给看的人快速知道这是从数据库取东西的一个类,数据放在model层下,model包下放承载数据职责的类,util包下放DBUtil工具。
Model类型对象也被称为DataObject(DO),注意不要和DAO混起来。
四、浏览器以JSON格式把数据给服务器
之前代码是服务器以json格式把数据给浏览器,浏览器以表单(form)形式将数据交给服务器,但还可以以json方式发送过去,如下:
1)通过JSON方式发送一个请求体
<!DOCTYPE html>
<html lang="zh-hans">
<head>
<meta charset="UTF-8">
<title>只做json数据的发送,即浏览器以json形式把数据发给服务器</title>
</head>
<body>
<button>发送 JSON 到服务器</button>
<script>
// 只能使用 ajax 做到
var btn = document.querySelector('button');
//绑定一个事件
btn.onclick = function() {
var students = [
{ 姓名: "小赵", 成绩: 83 },
{ 姓名: "小钱", 成绩: 84 },
{ 姓名: "小李", 成绩: 85 }
];
//写js代码,key可以不加引号,所以姓名那里没加引号
// 1. 先 JS 的数据变成 String 类型 —— JSON 序列化【准备数据】
// 反序列化: JSON.parse(...);
// 序列化: JSON.stringify
//对students序列化为json格式的内容
var s = JSON.stringify(students);
console.log(s);//通过打印观察,s就是一串字符串(JSON格式的)
console.log(students);//通过打印观察,students就是上面所写的那一组数据
// 2. 发送,因为要在请求体中携带 JSON 数据,所以只能使用 POST 方法
var xhr = new XMLHttpRequest();
xhr.open("POST", "/get-json-from-request-body");
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");//设置请求头
xhr.send(s); // 发送。send 的参数,就是请求体(传入s就代表作为请求体发送)
}
</script>
</body>
</html>
分析:students和json之间是相互转化的,String(JSON下的/JSON格式)到数据(json格式解析后的内容)是在JS中调的是JSON.parse(...),叫做解序列化过程,数据到String,在JS中是JSON.stringify(...),叫做序列化过程。(数据->String在java中用ObjectMapper.writeValueAsString(...),String->数据在java中用ObjectMapper.readValue(...))
2)从请求体中把数据读出
package com.wy4.servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wy4.model.Grade;
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;
import java.util.List;
/**
* @author 美女
* @date 2022/06/28 21:58
**/
@WebServlet("/get-json-from-request-body")
public class GetJsonFromRequestBodyServlet extends HttpServlet {
//支持的方法是POST方法,因为是POST请求提交的
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.请求体放在req.getInputStream()的输入流中
InputStream inputStream = req.getInputStream();
//2.利用Jackson进行解析(这个需要导依赖)
ObjectMapper om=new ObjectMapper();
//3.3
List<Grade> list = om.readValue(inputStream, om.getTypeFactory().constructParametricType(List.class, Grade.class));
for (Grade grade:list) {
System.out.println(grade);
}
//结果:Grade{name='小赵', score=83}
//Grade{name='小钱', score=84}
//Grade{name='小李', score=85}
// //3.2. 读一行
// List list1 = om.readValue(inputStream, List.class);//数据里主要是List.class。至于外面的list会自己帮我解析,最终就会解析出来
//写的话操作是write,在GradeListJsonServlet类里有
// //结果:[{姓名=小赵, 成绩=83}, {姓名=小钱, 成绩=84}, {姓名=小李, 成绩=85}]
// System.out.println(list1);
// //3.1.多个读
// Object o = om.readValue(inputStream, om.getTypeFactory().constructParametricType(List.class, Grade.class));
// System.out.println(o);
//结果:[Grade{name='小赵', score=83}, Grade{name='小钱', score=84}, Grade{name='小李', score=85}]
}
}
public class Grade { //注解意思是写到json里面是用姓名or成绩作为它的key @JsonProperty("姓名") public String name; @JsonProperty("成绩") public int score; @Override public String toString() { return "Grade{" + "name='" + name + '\'' + ", score=" + score + '}'; } }
五、eg:在浏览器中看到一个页面的简单架构
在前后端分离的场景下:
xxx.html是用户看到的主页面,是入口。页面由多个资源组成,一般就是css做样式资源,js做逻辑资源,除此之外,在前后端分离场景下,数据往往是后端以xxx.json形式提供好的。
eg:其他代码小练习:
<!DOCTYPE html>
<html lang="zh-hans">
<head>
<meta charset="UTF-8">
<title>成绩列表</title>
</head>
<body>
<div>
<input type="text" id="name" name="name" placeholder="姓名">
<input type="text" id="score" name="score" placeholder="成绩">
<!--button的type是button时,代表不是提交form表单的功能,我们通过js添加点击事件处理自行控制流程
(即框架中浏览器->服务器的操作方法2类似,通过js手动把数据序列化,通过POST传出去)-->
<button type="button">保存</button><!--由于button本身是提交表单,现在不需要,默认type处是submit,
现在改成button,默认就不提交了(失去了提交表单功能),type可写可不写【此时就需要js添加点击事件处理来处理它】-->
</div>
<table>
<tbody>
</tbody>
</table>
<!--前端要去后端去取一定需要一个js-->
<script>
<!-- function render(students) {-->
<!-- var tbody = document.querySelector('tbody');-->
<!-- for (var i in students) {-->
<!-- var s = students[i];-->
<!-- var tr = `<tr><td>${s.姓名}</td><td>${s.成绩}</td></tr>`;-->
<!-- tbody.innerHTML = tbody.innerHTML + tr;-->
<!-- }-->
<!-- }-->
<!-- var xhr = new XMLHttpRequest();//构建一个ajax出来-->
<!-- xhr.open("GET", "/grade-list.json");//把这个目录下的数据给我-->
<!-- //前端只用读取json数据,读取出来后通过DOM树的修改操作把它加到tbody中-->
<!-- xhr.onload = function() {-->
<!-- // this.responseText 是 JSON 格式的字符串,是我们得到的响应体-->
<!-- // 进行 JSON 的反序列(进行JSON解码)-->
<!-- var students = JSON.parse(this.responseText);-->
<!-- // 进行渲染操作 (渲染:render)(进行DOM树的修改,即渲染操作)-->
<!-- render(students);-->
<!-- }-->
<!-- xhr.send();//发送-->
//通过添加事件处理进行的处理:
var btn=document.querySelector('button');//找到button
btn.onclick=function(){
var name=document.querySelector('#name').value;
var score=document.querySelector('#score').value;
var data={
姓名:name,
成绩:score
};
var s=JSON.stringify(data);
var xhr2=new XMLHttpRequest();
xhr2.open('post','/save.json');
xhr2.onload=function(){
//不考虑错的情况了,所以我们手动重定向【一旦返回代表保存成功,保存成功用一个重定向(这是前端重定向,直接把location一改就会自动重定向)】
location='/';
}
xhr2.send(s);
}
</script>
</body>
</html>
@WebServlet("/save.json")
public class SaveServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper om=new ObjectMapper();
Grade grade=om.readValue(req.getInputStream(),Grade.class);
System.out.println(grade);
//TODO:保存到数据库中
try(Connection c= DBUtil.connection()){
String sql="insert into grades(name,grade) values(?,?)";
try(PreparedStatement ps=c.prepareStatement(sql)){
ps.setString(1,grade.name);
ps.setInt(2,grade.score);
ps.executeUpdate();
}
}catch (SQLException e){
throw new RuntimeException(e);
}
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json");
PrintWriter writer = resp.getWriter();
HashMap<String,String> result=new HashMap<>();
result.put("结果","正确");
String s=om.writeValueAsString(result);
writer.println(s);
}
}