JSP基础知识第四篇
本次博文介绍了文件的上传与下载、Java上对Excel的处理、Servlet监听器、RBAC权限模型的简单介绍。大部分为示例操作,仅供参考,如有错误和不周请指出。
文件的上传与下载
文件处理的包
commons-io.jar
封装了常用的 IO 的相关操作,提供了 IOUtils 工具类供开发人员使用 commons-fileupload.jar 文件上传的处理包,因为文件上传也会涉及到 IO 操作,因此,该包需要配合commons-io.jar 使用
一些常用的API...
FileItemFactory 文件项工厂,主要提供创建文件项的功能
DiskFileItemFactory 磁盘文件项工厂,主要用于解析上传文件时,创建对应的文件项 ServletFileUpload Servlet 文件上传对象,主要用于判断请求是否是文件上传请求,以及请 求中的内容解析。解析时需要使用文件项工厂来创建文件项
文件上传
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件上传和下载</title> </head> <body> <%-- 使用form表单进行文件上次的时候,必须设置enctype属性, 而且这个属性值必须是multipart/form-data--%> <form action="upload" method="post" enctype="multipart/form-data"> <input type="text" name="name"> <input type="file" name="uploadFile"> <input type="submit" value="上传"> </form> <input type="file" id="uploadFile"> <input type="button" value="上传" id="uploadBtn"> <a href="download?name=图片.png">图片.png</a> </body> <script type="text/javascript" src="jQuery/jquery-3.6.0.js" ></script> <script type="text/javascript"> $(function (){ $("#uploadBtn").click(function (){ let formData = new FormData();//创建一个表单数据对象 主要用于模拟表单数据 formData.append("file",$("#uploadFile")[0].files[0]) $.ajax({ // 文件上传 url:'upload', type: 'post', data:formData, processData: false,//告诉jQuery不要处理数据 contentType: false,//告诉jQuery不要设置内容的类型 success: function (resp){ alert(resp); }, error: function (xhr){ } }); }) }) </script> </html>
UploadServlet
package com.noy.jsp.servlet; import com.noy.jsp.excel.ExcelUtil; import com.noy.jsp.excel.read_writer; import com.noy.jsp.pojo.Student; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.IOUtils; 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.*; import java.util.List; /** * 文件上传 */ @WebServlet("/upload") public class UploadServlet extends HttpServlet { private static final String SAVE_DIR = "D:\\Java Practise\\jsp06\\upload"; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //首先判断它到底是不是文件上传请求 if (ServletFileUpload.isMultipartContent(req)){//如果是文件上传的请求 DiskFileItemFactory factory = new DiskFileItemFactory();//创建一个磁盘文件项工厂 factory.setDefaultCharset("UTF-8");//设置字符集编码 factory.setRepository(new File("java.io.tmpdir"));//设置临时文件的存储位置 factory.setSizeThreshold(4096*1024);//设置每一个文件项的最大的大小为4096KB,超过了这个大小就会使用临时文件 ServletFileUpload upload = new ServletFileUpload(factory);//创建文件按上传的对象 upload.setFileSizeMax(5*1024*1024);//设置每一个上传文件的最大大小为5M upload.setSizeMax(50*1024*1024);//设置每次上传的所有文件的总大小最大为50M upload.setHeaderEncoding("UTF-8"); try { List<FileItem> fileItems = upload.parseRequest(req);//开始 解析请求 得到文件项 for(FileItem fileItem : fileItems){ if(fileItem.isFormField()){//判断是否是普通的表单字段,比如name:123 //打印参数名和参数值 System.out.println(fileItem.getFieldName() + "=>" + fileItem.getString()); }else {//说明是上传的文件,要保存到一个地方 File dir = new File(SAVE_DIR); if(!dir.exists()) dir.mkdirs(); //创建保存文件 File saveFile = new File(dir,fileItem.getName()); InputStream is = fileItem.getInputStream();//获取上传文件的输入流 List<Student> students = read_writer.readExcel(is, Student.class); OutputStream os = new FileOutputStream(saveFile);//获取上传文件的输出流 IOUtils.copy(is,os);// 将输入流中的信息拷贝至输出流 , 这就是文件保存 IOUtils.closeQuietly(is);//关闭流 IOUtils.closeQuietly(os);//关闭流 } } resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().print("上传成功"); } catch (FileUploadException e) { e.printStackTrace();//打印异常原因 resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().print("上传失败"); } }else {//抛出运行时异常 throw new RuntimeException("请求头中未发现multipart/form-data"); } } }
文件下载
DownloadServlet
package com.noy.jsp.servlet; import com.noy.jsp.excel.read_writer; import com.noy.jsp.pojo.Student; import org.apache.commons.io.IOUtils; 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.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @WebServlet("/download") public class DownloadServlet extends HttpServlet { private static final String DOWNLOAD_DIR = "D:\\"; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = req.getParameter("name");//获取下载的文件的名字 File file = new File(DOWNLOAD_DIR,name); if (file.exists()) { byte[] data = name.getBytes(StandardCharsets.UTF_8);//获取下载的文件名的字节数据 //转换编码的格式:重新构建字符串,因为浏览器默认支持的编码是IS0_8859_1,因此,要转换为这种编码下中文才能正常显示 name = new String(data, StandardCharsets.ISO_8859_1); //设置内容处理方案:以附件的形式处理 resp.setHeader("Content-Disposition","attachment;filename="+name); InputStream is = new FileInputStream(file); OutputStream os = resp.getOutputStream();//获取响应的输出流,这个流就会将信息输出到页面,从而形成下载的效果 List<Student> students = new ArrayList<>();//这个是假数据,实际数据应该从数据库中查询出来 read_writer.writerExcel(os, Student.class, "学生信息表", students); IOUtils.copy(is, os);//传输信息 IOUtils.closeQuietly(is);//关闭这两个流 IOUtils.closeQuietly(os); }else { resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); PrintWriter writer = resp.getWriter(); writer.print("下载的文件不存在"); writer.flush(); writer.close(); } } }
Excel的处理
EasyExcel
EasyExcel 是阿里巴巴开源的一个 Excel 处理框架,以使用简单、节省内存著称。
EasyExcel 能大大减少占用内存的主要原因是在解析 Excel 时没有将文件数据一次性全部加载到内存中,而是从磁盘上一 行行读取数据,逐个解析。
EasyExcel 生成 Excel
package com.noy.jsp.pojo; import com.alibaba.excel.annotation.ExcelProperty; public class Student { //思考一个问题 这个name属性与excel中的哪一个表头对应 @ExcelProperty("姓名") private String name; @ExcelProperty("性别") //ExcelProperty表示在生成的Excel表格中的属性,value表示该属性对应的表头名称, index表示该属性所处的列的位置 private String gender; @ExcelProperty("年龄") private int age; @ExcelProperty("班级") private String className; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", gender='" + gender + '\'' + ", age=" + age + ", className='" + className + '\'' + '}'; } }
封装一个工具类
这个工具类是笔者以前写的,所以大部分的注释是复杂但可参考的,实际编写可以忽略
package com.noy.jsp.excel; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.excel.write.metadata.WriteSheet; import com.noy.jsp.pojo.Student; import java.io.*; import java.util.ArrayList; import java.util.List; public class ExcelUtil { private static final int MAX_COUNT_PER_SHEET = 5000;//每个sheet中写的最大数量 public static void main(String[] args) throws IOException { List<Student> students = new ArrayList<>(); for (int i = 0; i < 30000; i++) { Student stu = new Student(); stu.setName("作者的名字" + i); stu.setGender(i % 2 == 0 ? "男" : "女"); if (stu.getGender() == "女") stu.setName("作者女朋友的名字" + i); stu.setAge(10 + i); stu.setClassName("计算机科学"); if (stu.getName().startsWith("作者女朋友的名字")) stu.setClassName("汉语言文学"); students.add(stu); } OutputStream os = new FileOutputStream("C:\\Users\\dril\\OneDrive\\Desktop\\学生信息表2.xlsx"); exportExcel(os, Student.class, "学生信息表", students); // InputStream is = new FileInputStream("C:\\Users\\dril\\OneDrive\\Desktop\\学生信息表(创建).xlsx"); // importExcel(is,Student.class); // List<Student> students = new ArrayList<>(); // for (int i = 0; i < 100; i++) { // Student stu = new Student(); // stu.setName("作者的名字" + i); // stu.setAge(10+i); // stu.setGender(i % 2 == 0 ? "男":"女"); // stu.setClassName("计算机科学"); // students.add(stu); // } //如何将这个集合中的数据写入一个excel中? //excel必须要有名称,有存放的位置 // String excelPath = "C:\\Users\\dril\\OneDrive\\Desktop\\学生信息表(创建).xlsx"; // writeExcel(excelPath, Student.class,"学生信息表", students); // String excelPath = "C:\\Users\\dril\\OneDrive\\Desktop\\学生信息表(创建).xlsx"; // List<Student> students = readExcel(excelPath,"学生信息表", Student.class); // // System.out.println(students.size()); } public static<T> List<T> importExcel(InputStream is, Class<T> clazz) { List<T> dataList = new ArrayList<>(); ReadListener<T> listener = new ReadListener<T>() {//实现一个监听器 @Override public void invoke(T t, AnalysisContext analysisContext) { System.out.println("读取了一行数据" + t); dataList.add(t); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("excel读取一个sheet完毕"); } }; EasyExcel.read(is, clazz, listener).doReadAll();//全读了,包括所有sheet return dataList; } public static<T> void exportExcel(OutputStream os, Class<T> clazz,String sheetName,List<T> dataList) throws IOException { //EasyExcel写excel的时候需要指定excel存放的位置 // 还需要指定写的时候excel的表头的对应关系 ExcelWriter writer = EasyExcel.write(os, clazz).build(); int size = dataList.size();//数据总条数 //deepseek的代码写的真是美如画 int sheetCount = (size + MAX_COUNT_PER_SHEET - 1) / MAX_COUNT_PER_SHEET;//计算sheet的数量 for (int i = 0; i < sheetCount; i++) { WriteSheet sheet = new WriteSheet(); sheet.setSheetNo(i); sheet.setSheetName(sheetName + (i+1)); // 计算当前sheet的数据范围 int start = i * MAX_COUNT_PER_SHEET; int end = Math.min(start + MAX_COUNT_PER_SHEET, size); // 防止越界 List<T> sheetData = dataList.subList(start, end); writer.write(sheetData,sheet); } writer.finish(); os.close(); } public static<T> List<T> readExcel(String excelPath,String sheetName, Class<T> clazz) { List<T> dataList = new ArrayList<>(); ReadListener<T> listener = new ReadListener<T>() {//实现一个监听器 @Override public void invoke(T t, AnalysisContext analysisContext) { //System.out.println("读取了一行数据"+t); dataList.add(t); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { //System.out.println("excel读取一个sheet完毕"); } }; //EasyExcel读取excel时需要指定读取的excel的位置,还需要指定读取的类型, //因为这个类型中就指定了excel表头与类定义的属性的映射关系, //还需要指定新的监听器,因为EasyExcel是按行读取的, //这个监听器就是感知一行的读取过程 EasyExcel.read(excelPath, clazz,listener) .sheet(sheetName)//指定读取的sheet的名称 .doRead();//执行读 return dataList; } public static<T> void writeExcel(String excelPath, Class<T> clazz,String sheetName,List<T> dataList) { //EasyExcel写excel时必须指定excel存放的位置, // 还需要指定写的时候excel的表头的对应关系 EasyExcel.write(excelPath,clazz) .sheet(sheetName) //指定写的时候sheet的名称 .doWrite(dataList);//执行写数据操作 } }
不管是文件上传还是EasyExcel都很适合初学者照着代码练习,有机会多多试试吧
还有几点要注意:
文件上传和下载:
文件上传: 上传的时候必须要带有multipart/form-data
文件上传有两种方式:form表单上传,ajax上传=>form表单模拟
文件下载: 下载文件的时候,一定要注意下载文件的文件名字符集处理
一定要记得带jar包:commons-io.jar commons-fileupload.jar
Excel的导入导出:
导入的时候也需要注意导入的数据量,如果数据量过大,需要分批处理,每次处理的数量根据实际开发场景去定义.
导出的时候也需要注意导出的数据量,如果数据量过大,需要分批处理,每一批数据放在一个sheet中
Servlet监听器
什么是监听器?
监听器顾名思义就是监听某种事件的发生,一旦监听的事件触发,那么监听器就将开始执行。例如:在 上课的时候,老师会观察每一位学生的听课情况,如果有学生上课打瞌睡,那么老师就会提醒他。这个 场景中,老师就是一个监听器,监听的是学生是否打瞌睡,一旦学生出现打瞌睡的情况,监听器就开始 执行(老师提醒学生)
ServletContextListener(Servlet上下文监听器)
该监听器主要监听的是Servlet上下文的初始化和销毁。一旦Servlet上下文初始化或者销毁,ServletContextListener 就执行响应的操作。
具体实现:
public interface ServletContextListener extends EventListener {
// Servlet上下文初始化时调用
default void contextInitialized(ServletContextEvent sce) {
}
// Servlet上下文销毁时调用
default void contextDestroyed(ServletContextEvent sce) {
}
}
示例: 使用Servlet上下文监听器
在resources目录下创建jdbc.properties ------- 连接你的数据库
jdbc.url=jdbc:mysql://localhost:3306/jsp?serverTimezone=UTF-8
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=root
在web.xml中配置上下文参数
<context-param>
<param-name>jdbcConfig</param-name>
<param-value>/jdbc.properties</param-value>
</context-param>
创建一个上下文监听器
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;@WebListener
public class ApplicationContextListener implements ServletContextListener {@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("Servlet Context初始化");
ServletContext context = sce.getServletContext();
String config = context.getInitParameter("jdbcConfig");
if (config != null && !"".equals(config)) {
InputStream is = this.getClass().getResourceAsStream(config);
Properties props = new Properties();
try {
props.load(is);
System.out.println(props);
} catch (IOException e) {
e.printStackTrace();
}
}
}@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Servlet Context销毁");
}
}
接下来可以测试了
启动服务器,查看控制台信息
关闭服务器,查看控制台信息
于是发现:Servelt上下文监听器可以读取到上下文参数,这些参数可以用来配置开发中所需要的环 境
DruidDataSource(德鲁伊数据源)
DruidDataSource是阿里巴巴开发的一款高性能的数据源。利用Servlet上下文监听器建立工程中需要的数据源
在开始之前,我们需要引入DruidDataSource的依赖包,然后创建一个JbdcUtil 工具类。
package com.noy.jsp.handler; import java.sql.ResultSet; import java.sql.SQLException; /** * 对查询的结果集进行处理的接口,具体如何处理需要用户来实现 * @param <T> */ public interface ResultHandler<T>{ T handle(ResultSet rs) throws SQLException; }
对单个结果的处理
package com.noy.jsp.handler; import org.apache.commons.beanutils.BeanUtils; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; public class SingleResultHandler<T> implements ResultHandler<T> { private Class<T> clazz; public SingleResultHandler(Class<T> clazz) { this.clazz = clazz; } @Override public T handle(ResultSet rs) throws SQLException { int totalCount = 0; while (rs.next()) { totalCount++; if (totalCount > 1) throw new RuntimeException("查询结果存在多条数据:"+totalCount); try { if (clazz.isPrimitive() || Integer.class == clazz || Long.class == clazz) { return rs.getObject(1,clazz); } T t = clazz.newInstance(); Map<String,Object> values = new HashMap<>(); ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); for (int i = 1; i < columnCount; i++) { String label = rsmd.getColumnLabel(i); Object value = rs.getObject(label); values.put(label, value); } //使用工具类对我们的对象的属性值进行注入 BeanUtils.populate(t, values); return t; } catch (Exception e) { e.printStackTrace(); } } return null; } }
对多个结果的处理
package com.noy.jsp.handler; import org.apache.commons.beanutils.BeanUtils; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.*; public class MultiResultHandler<T> implements ResultHandler<List<T>> { private Class<T> clazz; public MultiResultHandler(Class<T> clazz) { this.clazz = clazz; } @Override public List<T> handle(ResultSet rs) throws SQLException { List<T> dataList = new ArrayList<>(); while (rs.next()) { try { T t = clazz.newInstance();//newInstance()方法创建目标对象的实例 Map<String,Object> values = new HashMap<>(); ResultSetMetaData rsmd = rs.getMetaData();//获取 ResultSet 的元数据,用于获取列信息。 int columnCount = rsmd.getColumnCount();//获取结果集中列的数量 for (int i = 1; i <= columnCount; i++) { String label = rsmd.getColumnLabel(i);//获取列的别名(标签) Object value = rs.getObject(label);//获取列的值 values.put(label, value); } //使用工具类对我们的对象的属性值进行注入 BeanUtils.populate(t, values); dataList.add(t); } catch (Exception e) { e.printStackTrace(); } } return dataList; } }
package com.noy.jsp.servlet; import com.noy.jsp.service.StudentService; import com.noy.jsp.service.impl.StudentServiceImpl; 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.PrintWriter; @WebServlet("/update") public class UpdateServlet extends HttpServlet { private StudentService studentService = new StudentServiceImpl(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String id = req.getParameter("id"); String name = req.getParameter("name"); String gender = req.getParameter("gender"); String age = req.getParameter("age"); int result = studentService.updateStudent(id,name,gender,age); PrintWriter writer = resp.getWriter(); writer.print(result); writer.flush(); writer.close(); } }
package com.noy.jsp.listener; import com.noy.jsp.jdbc.jdbcUtil; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.io.IOException; import java.io.InputStream; import java.util.Properties; @WebListener//标明这是一个监听器 public class ApplicationContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("Servlet上下文初始化"); //获取servlet上下文 ServletContext context = sce.getServletContext(); String jdbcConfig = context.getInitParameter("jdbcConfig"); InputStream is = this.getClass().getResourceAsStream(jdbcConfig); Properties props = new Properties(); try { props.load(is); System.out.println(props); //对数据源进行初始化操作 jdbcUtil.initDataSource(props); } catch (IOException e) { throw new RuntimeException(e); } } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("Servlet上下文销毁"); jdbcUtil.destroyDataSource(); } }
接下来可以修改jdbc.properties配置 编写访问接口,启动测试了。
RBAC权限模型
Role-Based Access Control,表示基于角色的访问控制。
在RBAC中,有三个最常用的术语:
用户:系统资源的操作者
角色:具有一类相同操作权限的用户的总称
权限:能够访问资源的资格
资源:服务器上的一切数据都是资源,比如静态文件,查询的动态数据等。
RBAC怎么与用户建立联系?
服务器感知用户是通过session来感知的,因此,RBAC的实现需要与 session配合。前提是用户需要登录,登录后将用户信息存储在session中,这样才能在session中获取用户的信息。
RBAC结构图
一个用户可能充当多个角色,而一个角色可能有不同的权限
RBAC的练习和Servlet监听器的练习都需要联系Navicat的数据库一起操作。