这里是javaweb小白第一次尝试写博客,主要是想记录一下自己在学习JavaWeb的成长历程、记录在编写程序时解决的一些问题。也想借此帮助自己对知识点的消化和对代码结构体系上的深入理解,如果能帮助到同样正在学习这部分内容的小伙伴,那是莫大的荣幸!
回到正题,该篇讲解的是一个利用Servlet处理页面上传的JSON文件,对其解析后以表格形式呈现在响应页面,并可勾选相应的行下载导出excel文件。
首先我们要了解文件在http请求消息中的格式
我们通过表单向服务器发送包含了用户在浏览器页面上传的文件的http请求
<form enctype="multipart/form-data" method="post" action="fileUpload.do">
<table border="1px">
<tr>
<td colspan="2" style="text-align: center">文件上传td>
tr>
<tr>
<td>会员号:td>
<td><input type="text" name="mnumber" size="30">td>
tr>
<tr>
<td>文件名:td>
<td><input type="file" name="fname" size="30">td>
tr>
<tr>
<td style="text-align: right"><input type="submit" value="提交">td>
<td style="text-align: left"><input type="reset" value="重置">td>
tr>
table>
form>
在form标签内 定义了编码类型enctype="multipart/form-data"
,并以POST方法发送请求。
关于enctype
属性,在POST请求方法下默认为application/x-www-form-urlencoded
,在这种编码格式下,数据会被编码成以被&
分隔的健-值对的形式,健-对值之间以=
分隔,也就是key=value
另一种text/plain
纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码。这里不做过多介绍。
还有一种编码方式就是例中所用的multipart/form-data
指定传输数据为二进制类型,比如图片、mp3、文件。表明表单数据为复合类型数据,包含多个子部分,这种方式提交的表单在我们的数据分离后,会以boundary
开头、last boundary
结尾。其中每个部分的描述都有HTTP头部描述子包体,如Content-type
对表单编码方式的介绍参考这篇文章,想要了解更详细内容可以点此查看该文章
我们使用wireshark抓取这个请求消息进一步解析
看到请求头Content-Type正文类型为multipart/form-data,对应了form标签内定义的编码类型。
表单的包体在http报文的正文内容,使用MIME多功能网际邮件拓展协议
图中第三行First boundary
开始为第一个子包体,name="mnumber"
,是对应的请求参数名,该子包体以
Boundary
为标志结束
第二个part是请求中JSON文件,Content-Type:application/json
表示了这个资源的类型,name="fname"
是该资源的请求参数名,之后的代码中我们就会借助这个参数名来获取这个part,filename="javascore.json"
是资源文件名
报文最后以Last boundary
来结尾表明所有的资源文件都传输完毕。
由此条抓取的报文印证了multipart/form-data
方式,复合型、多个部分、以boundary
边界分隔的结构特征,能帮助我们从代码层面对请求包资源的操作理解。
String mNumber=request.getParameter("mnumber");
//获取上传的文件、request.getPart(“属性名”)用于获取使用multipart/form-data格式传递的http请求的请求体,通常用于获取上传文件。
//part.getSubmittedFileName();拿到文件名
Part part= request.getPart("fname");
//将上传的文件内容写入服务器文件中
part.write(fileSavingPath);
附上相应的代码语句,是不是就能更好地理解这些方法的含义
对于保存上传的文件到服务器,本例中采取了指定绝对路径的做法,这样做的缺点是只适用于一台服务器,不好做迁移。更好的解决办法是获取web项目在硬盘的绝对路径,再保存文件到web应用根目录的相对路径上。
为此我们可以使用servlet中用ServlertContext
域的getRealPath()
这个方法来动态的获取web应用的绝对路径(关于ServlertContext
域将在后文介绍)
//参数"/"获取web应用输出根目录
String fileSavingFolder = this.getServletContext().getRealPath("/");
此方法有时候会遇到获取的路径在target文件夹下,这时需要更改tomcat服务器项目部署的输出目录至webapp文件夹下
简单介绍一下JSON的格式
JSON三种格式:
(一)简单值:数字、字符串、布尔值
(二)对象形式:
(三)数组形式
本例中的测试案例:
使用FastJson对JSON解析,当然也可以以字符串类型手动解析,只是比较繁琐。
FastJson环境配置非常简单,只需在pom.xml文件中加入以下依赖项,重新加载maven即可
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
解析的思路:先读取文件内容到字符串数组再使用JSON.parseArray()
转化为JSON数组,逐个取出对象进行解析
代码如下(示例):
//获取存储在请求对象中的文件路径
String filePath=String.valueOf(request.getAttribute("fileSavingPath"));
//存储文件路径至全局作用域对象ServletContext中
ServletContext application=request.getServletContext();
application.setAttribute("filePath",filePath);
//读取文件内容到字符串
StringBuilder strJson= new StringBuilder();
try {
//设置字符编码UTF-8
BufferedReader br=new BufferedReader(new FileReader(filePath, StandardCharsets.UTF_8));
String str="";
while((str=br.readLine())!=null){
strJson.append(str);
}
br.close();
}catch (IOException e){
e.printStackTrace();
}
//转为JSON数组
JSONArray jsonArray= JSON.parseArray(String.valueOf(strJson));
System.out.println(jsonArray);
因为对JSON文件的解析和导出excel的实现在两个不同servlet实现,所以需要servlet共享JSON文件路径,可以通过ServletContext
对象来存储数据。
Web容器在加载每个Web程序时会创建一个唯一的ServletContext
实例对象,该对象称为Servlet上下文对象。
//两种获取servletcontext的方法
ServletContext application=request.getServletContext();
ServletContext application=getServletConfig().getServletContext();
使用setAttribute(String name,Object object)
和getAttribute(String name,Object object)
存储和提取数据
实现样例:
思路: 通过getParameterValues()
方法获取被选中的checkbox
的value
数组
依据数组选择对应顺序的JSON对象写入excel
设置报头Content-Encoding
编码类型为gb2312
。
Content-Disposition
是 MIME 协议的扩展,指示回复的内容、浏览器要以何种形式展示(以内联的形式即网页或者页面的一部分 or 以附件的形式下载并保存到本地)。
这里的字段值为"attachment; filename=\"学生成绩\""
分别指定了文件以附件的形式下载和默认文件名。
Content-Type
类型为"application/vnd.ms-excel;charset=gb2312"
fileUpload.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<style>
form{
margin-right: auto;
margin-left: auto;
width: max-content;
}
style>
head>
<body>
<form enctype="multipart/form-data" method="post" action="fileUpload.do">
<table border="1px">
<tr>
<td colspan="2" style="text-align: center">文件上传td>
tr>
<tr>
<td>会员号:td>
<td><input type="text" name="mnumber" size="30">td>
tr>
<tr>
<td>文件名:td>
<td><input type="file" name="fname" size="30">td>
tr>
<tr>
<td style="text-align: right"><input type="submit" value="提交">td>
<td style="text-align: left"><input type="reset" value="重置">td>
tr>
table>
form>
body>
html>
FileUploadServlet.java
package com.example.excel;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "FileUploadServlet", value = "/fileUpload.do")
@MultipartConfig(fileSizeThreshold = 1024)
public class FileUploadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out=response.getWriter();
String mNumber=request.getParameter("mnumber");
//获取上传的文件、request.getPart(“属性名”)用于获取使用multipart/form-data格式传递的http请求的请求体,通常用于获取上传文件。
//part.getSubmittedFileName();拿到文件名
Part part= request.getPart("fname");
//反馈信息字符串
String message="";
//上传文件大小不能超过1MB
if(part.getSize()>1024*1024){
part.delete();
message="文件太大,不能上传!";
}else {
//获得webapp根目录
String fileSavingFolder = this.getServletContext().getRealPath("/");
System.out.println(fileSavingFolder);
//获得存储上传文件的完整路径(文件夹路径+文件名)
//文件夹位置固定,文件夹采用与上传文件的原始名字相同
String fileSavingPath = "C:\\Users\\Hana-bi\\Documents\\webfile" + File.separator + "students" + File.separator + mNumber;
//如果存储上传文件的文件夹不存在,则创建文件夹
System.out.println("文件路径:"+fileSavingPath);
File f = new File(fileSavingPath);
if (!f.exists()) {
System.out.println("文件不存在!");
f.mkdirs();
System.out.println("目录已创建");
}
//获取HTTP头信息headerInfo=(form-data; name="file" filename="文件名")
String headerInfo = part.getHeader("content-disposition");
//从HTTP头信息中获取文件名fileName=(文件名)
String fileName = headerInfo.substring(headerInfo.lastIndexOf("=") + 2, headerInfo.length() - 1);
fileSavingPath+=File.separator+fileName;
//将上传的文件内容写入服务器文件中
part.write(fileSavingPath);
message="文件上传成功~!";
//使用request对象存储数据
request.setAttribute("fileSavingPath",fileSavingPath);
//获取转发对象
RequestDispatcher rd=request.getRequestDispatcher("/Parsejson.do");
//转发请求响应
rd.forward(request,response);
}
}
}
ParseJsonServlet.java
package com.example.excel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@WebServlet(name = "ParseJsonServlet", value = "/Parsejson.do")
public class ParseJsonServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
第一步读取文件
*/
//获取存储在请求对象中的文件路径
String filePath=String.valueOf(request.getAttribute("fileSavingPath"));
//存储文件路径至全局作用域对象ServletContext中
ServletContext application=request.getServletContext();
application.setAttribute("filePath",filePath);
//读取文件内容到字符串
StringBuilder strJson= new StringBuilder();
try {
//设置字符编码UTF-8
BufferedReader br=new BufferedReader(new FileReader(filePath, StandardCharsets.UTF_8));
String str="";
while((str=br.readLine())!=null){
strJson.append(str);
}
br.close();
}catch (IOException e){
e.printStackTrace();
}
//转为JSON数组
JSONArray jsonArray= JSON.parseArray(String.valueOf(strJson));
System.out.println(jsonArray);
/*
响应消息写入浏览器
*/
PrintWriter out=response.getWriter();
out.println("\n" +
"\n" +
"\n" +
" \n" +
" 成绩 \n" +
"\n" +
"\n" +
");
out.println("
学生成绩列表");
out.println("
序号 学号 姓名 课程名 成绩 ");
int no=1;
//foreach或使用iterator
for (Object ob:jsonArray) {
JSONObject jsonOb=(JSONObject) ob;
String stuid=jsonOb.getString("stuid");
String name=jsonOb.getString("name");
String courseName=jsonOb.getString("courseName");
String score=jsonOb.getString("score");
out.println("+(no-1)+"\"> "+no+" "+""+stuid+" "+""+name+" "+""+courseName+" "+""+score+" ");
no++;
}
out.println("
");
out.println("
");
out.println("");
}
}
ExportScoreServlet.java
package com.example.excel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@WebServlet(name = "ExportScoreServlet", value = "/exportScore.do")
public class ExportScoreServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//注意编码为gb2312
response.setHeader("Content-Encoding","gb2312");
response.setHeader("Content-Disposition","attachment; filename="+ URLEncoder.encode("学生成绩.xls", StandardCharsets.UTF_8));
response.setContentType("application/vnd.ms-excel;charset=gb2312");
PrintWriter out=response.getWriter();
out.println("序号\t学号\t姓名\t课程名\t成绩");
//获取勾选的checkbox参数
String []checks=request.getParameterValues("C");
System.out.println(Arrays.toString(checks));
//通过全局作用域对象获取先前存储的文件路径
ServletContext application=request.getServletContext();
String filePath=String.valueOf(application.getAttribute("filePath"));
System.out.println(filePath);
//读取文件内容到字符串
StringBuilder strJson= new StringBuilder();
try {
//设置字符编码UTF-8
BufferedReader br=new BufferedReader(new FileReader(filePath, StandardCharsets.UTF_8));
String str="";
while((str=br.readLine())!=null){
strJson.append(str);
}
br.close();
}catch (IOException e){
e.printStackTrace();
}
//转为JSON数组
JSONArray jsonArray= JSON.parseArray(String.valueOf(strJson));
System.out.println(jsonArray);
//将JSON对象写入excel
try {
for (String i:checks) {
JSONObject jsonOb=jsonArray.getJSONObject(Integer.parseInt(i));
String line=(Integer.parseInt(i)+1)+"\t"+jsonOb.getString("stuid")+"\t"+jsonOb.getString("name")+"\t"+jsonOb.getString("courseName")+"\t"+jsonOb.getString("score");
out.println(line);
}
}catch (NumberFormatException e){
e.printStackTrace();
}
}
}
这是一个简单的Servlet上传、转发、响应的实例,同时也需要对html请求和响应消息格式、JSON格式的解析处理、ServletContext等作用域对象有清楚的理解,才能对程序有更好的实现。
第一次写这种技术文章,在整体逻辑的表达上还有欠缺,我也只是个初识JavaWeb的初学者,对相关知识的掌握比较浅显,如有纰漏和错误,欢迎指出!如果您有更好的实现方法也欢迎评论交流~