概述
目录
0.新增功能需要完成的功能概述:
1.将新增页create.html添加到项目中
2.在ManagementController类中,添加访问create.jsp的入口
3.编写create.jsp:为文件上传做好准备
4.在ManagementController类中,添加接收create.jsp表单提交的方法;
(1)不借助第三方组件,“在服务器端”采用传统的策略处理【设计文件上传的请求】时:是不行的;
(1.1)演示1:如果不设置form表单的编码方式:enctype="multipart/form-data";
(1.2)演示2:设置form表单的编码方式:enctype="multipart/form-data";
结论:通过上面的演示可以知道:不借助第三方组件,“在服务器端”采用传统的策略处理【设计文件上传的请求】时:是不行的;
(2)利用FileUpload组件实现文件上传(非常重要)
(2.1)FileUpload简介,下载:
(2.2)利用FileUpload组件实现文件上传
(2.3)文件上传后,接下来的任务,就是将这个form表单提交过来的数据,新增到存储数据的xml文件中!!!
(3)向xml中添加数据
(3.1)在XmlDataSource工具类中,编写添加的方法
(3.2)在Dao中编写create()方法,向xml中添加数据;
(3.3)在Service中编写:create()方法;
(3.4)编写Controller部分:
(3.5)最后补充一点:属性中有id(自增,唯一标识对象)属性的时候,javaBean一般不添加有参构造;
5.启动程序:(附加一点说明:Tomcat发布目录)
(1)启动应用,观察效果
(2)再一点说明
0.新增功能需要完成的功能概述:
实现思路:
1.将新增页create.html添加到项目中
create.html外部文件引入: create.html是新增油画的表单页:打开create.html:可以看到这个效果:
将create.html添加到工程中:
<%@page contentType="text/html;charset=utf-8"%>
<!-- 新增油画页面 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>新增油画</title>
<link rel="stylesheet" type="text/css" href="csscreate.css">
<script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
</head>
<body>
<div class="container">
<fieldset>
<legend>新增油画</legend>
<form action="" method="post"
autocomplete="off">
<ul class="ulform">
<li>
<span>油画名称</span>
<span id="errPname"></span>
<input id="pname" name="pname" />
</li>
<li>
<span>油画类型</span>
<span id="errCategory"></span>
<select id="category" name="category">
<option value="-1">请选择油画类型</option>
<option value="1">现实主义</option>
<option value="2">抽象主义</option>
</select>
</li>
<li>
<span>油画价格</span>
<span id="errPrice"></span>
<input id="price" name="price"/>
</li>
<li>
<span>作品预览</span>
<span id="errPainting"></span>
<input id="painting" name="painting" type="file"
style="padding-left: 0px;" accept="image/*" />
</li>
<li>
<span>详细描述</span>
<span id="errDescription"></span>
<textarea
id="description" name="description"></textarea>
</li>
<li style="text-align: center;">
<button type="submit" class="btn-button">提交表单</button>
</li>
</ul>
</form>
</fieldset>
</div>
</body>
</html>
可以看到,create.jsp里面就是一个form表单;
2.在ManagementController类中,添加访问create.jsp的入口
因为,在工程中,所有JSP对外是不可见的,所以需要在Controller这只一个跳转,作为访问create.jsp的入口;
而在https://blog.csdn.net/csucsgoat/article/details/114897829这篇博客中创建的ManagementController类,ManagementController类需要完成,【增】,【删】,【改】,【查】的操作的;自然新增文件这条业务的Controller部分也是在ManagementController这个类中写的:
package com.imooc.mgallery.controller;
import java.io.IOException;
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 com.imooc.mgallery.service.PaintingService;
import com.imooc.mgallery.utils.PageModel;
/**
* 后台管理功能Controller;
* 后台系统,所需要的增,删,改,查的操作,都在这一个Controller类中完成;
* Servlet implementation class ManagementController
*/
@WebServlet("/management")
public class ManagementController extends HttpServlet {
private static final long serialVersionUID = 1L;
// 创建PaintingService对象;;
// 即无论是前台系统的PaintingController,还是后台系统的ManagementController都调用PaintingService中提供的方法;
private PaintingService paintingService = new PaintingService();
/**
* @see HttpServlet#HttpServlet()
*/
public ManagementController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置请求体中的字符集编码方式;;;
// get请求没有请求体,所以,这个语句是为doPost()方法中执行doGet(request,response)后,跳转过来的post请求来设置的;
// 即这条代码是为doPost()来进行服务的;
request.setCharacterEncoding("UTF-8");
// 设置响应的字符集编码方式
response.setContentType("text/html;charset=utf-8");
String method = request.getParameter("method");
if(method.equals("list")) { // 当前台传入的method参数值为“list”的时候,代表是分页请求,调用定义的list方法;
this.list(request,response); // 然后,将分页处理的代码写在list方法中就可以了;
}else if(method.equals("delete")) { // 当method参数值为“delete”时,表示是删除请求,调用定义的delete方法;
this.delete(request, response);
}else if(method.equals("show_create")) {
// method参数为“show_create”,表示是新增;调用新增的方法,跳转到create.jsp
this.showCreatePage(request, response);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response); // doPost调用了doGet()方法,所以,把逻辑代码都写在doGet方法中就可以了;
}
/**
*
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String p = request.getParameter("p");
String r = request.getParameter("r");
if(p==null) {
p = "1";
}
if(r==null) {
r = "6";
}
PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
request.setAttribute("pageModel", pageModel);
request.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(request, response);
}
private void delete(HttpServletRequest request, HttpServletResponse response) {
}
/**
* 这个方法,是一个纯粹的入口;
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void showCreatePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(request, response);
}
}
ManagementController类的几点说明:
(1)首先,如果请求中method参数值为“show_create”时,就调用showCreatePage()方法;
else if(method.equals("show_create")) {
// method参数为“show_create”,表示是新增;调用新增的方法,跳转到create.jsp
this.showCreatePage(request, response);
}
(2)showCreatePage()方法就是将请求转发到create.jsp上去;再次说明,这个方法仅仅为create.jsp提供一个访问入口!!!
private void showCreatePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(request, response);
}
自此:在浏览器中访问: localhost:8080/management?method=show_create时候,就会访问ManagementController类,然后ManagementController再请求重定向到create.jsp;即还是那句话,jsp需要在Controller中提供访问入口。
3.编写create.jsp:为文件上传做好准备
文件上传:前提条件
下面,根据文件上传的前提条件,对create.jsp进行编写:
<%@page contentType="text/html;charset=utf-8"%>
<!-- 新增油画页面 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>新增油画</title>
<link rel="stylesheet" type="text/css" href="csscreate.css">
<script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
</head>
<body>
<div class="container">
<fieldset>
<legend>新增油画</legend>
<!-- enctype="multipart/form-data"只有这样设置,我们选择的文件才能以二进制的形式上传至服务器 -->
<form action="/management?method=create" method="post"
autocomplete="off" enctype="multipart/form-data">
<ul class="ulform">
<li>
<span>油画名称</span>
<span id="errPname"></span>
<input id="pname" name="pname" />
</li>
<li>
<span>油画类型</span>
<span id="errCategory"></span>
<select id="category" name="category">
<option value="-1">请选择油画类型</option>
<option value="1">现实主义</option>
<option value="2">抽象主义</option>
</select>
</li>
<li>
<span>油画价格</span>
<span id="errPrice"></span>
<input id="price" name="price"/>
</li>
<li>
<span>作品预览</span>
<span id="errPainting"></span>
<!-- accept="image/*":表示在文件选择框中默认保留所有图片格式的文件,这相当于是一个文件的筛选条件,
但是这个条件并不靠谱;在后面涉及表单校验的时候,就会明白。 -->
<input id="painting" name="painting" type="file"
style="padding-left: 0px;" accept="image/*" />
</li>
<li>
<span>详细描述</span>
<span id="errDescription"></span>
<textarea
id="description" name="description"></textarea>
</li>
<li style="text-align: center;">
<button type="submit" class="btn-button">提交表单</button>
</li>
</ul>
</form>
</fieldset>
</div>
</body>
</html>
create.jsp的几点说明:
(1)form表单提交方式设为post;enctype="multipart/form-data":设置表单编码类型;
<form action="/management?method=create" method="post"
autocomplete="off" enctype="multipart/form-data">
(2)添加type="file"的input标签;
<input id="painting" name="painting" type="file"
style="padding-left: 0px;" accept="image/*" />
(3)action="/management?method=create" :form表单提交后,设置提交的地址;
<form action="/management?method=create" method="post"
autocomplete="off" enctype="multipart/form-data">
4.在ManagementController类中,添加接收create.jsp表单提交的方法;
可以发现,create.jsp表单提交的地址是ManagementController类:
<form action="/management?method=create" method="post"
autocomplete="off" enctype="multipart/form-data">
(1)不借助第三方组件,“在服务器端”采用传统的策略处理【设计文件上传的请求】时:是不行的;
这个表单提交涉及到了文件上传,如果不借助第三方组件,采用以前的方式,在Controller中通过诸如【request.getParameter("pname");】的方式,获取请求中的数据的时候:
package com.imooc.mgallery.controller;
import java.io.IOException;
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 com.imooc.mgallery.service.PaintingService;
import com.imooc.mgallery.utils.PageModel;
/**
* 后台管理功能Controller;
* 后台系统,所需要的增,删,改,查的操作,都在这一个Controller类中完成;
* Servlet implementation class ManagementController
*/
@WebServlet("/management")
public class ManagementController extends HttpServlet {
private static final long serialVersionUID = 1L;
// 创建PaintingService对象;;
// 即无论是前台系统的PaintingController,还是后台系统的ManagementController都调用PaintingService中提供的方法;
private PaintingService paintingService = new PaintingService();
/**
* @see HttpServlet#HttpServlet()
*/
public ManagementController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置请求体中的字符集编码方式;;;
// get请求没有请求体,所以,这个语句是为doPost()方法中执行doGet(request,response)后,跳转过来的post请求来设置的;
// 即这条代码是为doPost()来进行服务的;
request.setCharacterEncoding("UTF-8");
// 设置响应的字符集编码方式
response.setContentType("text/html;charset=utf-8");
String method = request.getParameter("method");
if(method.equals("list")) { // 当前台传入的method参数值为“list”的时候,代表是分页请求,调用定义的list方法;
this.list(request,response); // 然后,将分页处理的代码写在list方法中就可以了;
}else if(method.equals("delete")) { // 当method参数值为“delete”时,表示是删除请求,调用定义的delete方法;
this.delete(request, response);
}else if(method.equals("show_create")) {
// method参数为“show_create”,表示是新增;调用新增的方法,跳转到create.jsp
this.showCreatePage(request, response);
}else if(method.equals("create")) {
this.create(request, response);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response); // doPost调用了doGet()方法,所以,把逻辑代码都写在doGet方法中就可以了;
}
/**
*
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String p = request.getParameter("p");
String r = request.getParameter("r");
if(p==null) {
p = "1";
}
if(r==null) {
r = "6";
}
PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
request.setAttribute("pageModel", pageModel);
request.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(request, response);
}
private void delete(HttpServletRequest request, HttpServletResponse response) {
}
/**
* 显示【新增】页面;这个方法,是一个纯粹的入口;
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void showCreatePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(request, response);
}
/**
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pname = request.getParameter("pname");
System.out.println(pname);
//request.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(request, response);
}
}
为什么采取传统方式(不使用第三方组件)去处理【涉及文件上传的请求】是不行的???演示如下
(1.1)演示1:如果不设置form表单的编码方式:enctype="multipart/form-data";
说明,此时这个表单的编码方式还是采取传统的提交方式,这也是我们以前经常惯见的编码方式:
在ManagementController类中,添加的create()方法中,在System.out.println(pname);处打一个断点:为了方便观察效果
发现,在请求头中有表单提交的参数;
发现,如果不设置form表单的编码方式:enctype="multipart/form-data";一切正常,和以前经常遇到的情况一样!!!
(1.2)演示2:设置form表单的编码方式:enctype="multipart/form-data";
但是,这个表单是需要上传文件,而如果表单需要上传文件,这个表单的编码方式就需要设置为enctype="multipart/form-data";
发现,请求头中没有form表单提交的参数了,因为这些数据存放在请求体中了;
即:发现,此时,“在服务器端”再采取传统的 【request.getParameter("pname");】的策略,无法获得请求中的数据了;;;;这表示,出问题了。。。。。
结论:通过上面的演示可以知道:不借助第三方组件,“在服务器端”采用传统的策略处理【设计文件上传的请求】时:是不行的;
当表单提交包含文件上传时,需要设置form编码方式为【enctype="multipart/form-data"】,因此从客户端向服务器传递的是二进制数据,如果都是自己编写代码实现,而不借助第三方提供的组件,是一件非常麻烦的事情。所以上面的演示给的启发就是:当涉及文件上传的时候需要用到第三方组件:也就是Apache提供的FileUpload组件。
换句话说,客户端(也就是前端)的Fome表单为了实现【请求中包含上传文件的需求】,需要将form表单的编码方式设置为【enctype="multipart/form-data"】,至此前端准备好了;;;但是服务器端(也就是后端)如果仍旧采取传统的策略是不行的,服务器端也必须变换一种处理策略,以应对【请求中包含上传文件的需求】;;;这也就是下面要介绍的【FileUpload组件】;
……………………………………………………
(2)利用FileUpload组件实现文件上传(非常重要)
(2.1)FileUpload简介,下载:
但是,fileupload在运行时候,要基于另外一个组件【commons-io】才可以,所以需要在Apache官网上下载【commons-io】的jar包;
(2.2)利用FileUpload组件实现文件上传
前端点击提交后,会跳转到【"/management?method=create"】,在ManagementController中完成接受这个“提交请求”的逻辑代码;
即在后端,接收前端(附带上传文件的)form提交请求,并将提交的文件上传的代码如下;下面的部分,仅仅是完成了将文件上传到Tomcat服务器某个目录下,并不涉及其他!!!
package com.imooc.mgallery.controller;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
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 org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.imooc.mgallery.service.PaintingService;
import com.imooc.mgallery.utils.PageModel;
/**
* 后台管理功能Controller;
* 后台系统,所需要的增,删,改,查的操作,都在这一个Controller类中完成;
* Servlet implementation class ManagementController
*/
@WebServlet("/management")
public class ManagementController extends HttpServlet {
private static final long serialVersionUID = 1L;
// 创建PaintingService对象;;
// 即无论是前台系统的PaintingController,还是后台系统的ManagementController都调用PaintingService中提供的方法;
private PaintingService paintingService = new PaintingService();
/**
* @see HttpServlet#HttpServlet()
*/
public ManagementController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置请求体中的字符集编码方式;;;
// get请求没有请求体,所以,这个语句是为doPost()方法中执行doGet(request,response)后,跳转过来的post请求来设置的;
// 即这条代码是为doPost()来进行服务的;
request.setCharacterEncoding("UTF-8");
// 设置响应的字符集编码方式
response.setContentType("text/html;charset=utf-8");
String method = request.getParameter("method");
if(method.equals("list")) { // 当前台传入的method参数值为“list”的时候,代表是分页请求,调用定义的list方法;
this.list(request,response); // 然后,将分页处理的代码写在list方法中就可以了;
}else if(method.equals("delete")) { // 当method参数值为“delete”时,表示是删除请求,调用定义的delete方法;
this.delete(request, response);
}else if(method.equals("show_create")) {
// method参数为“show_create”,表示是新增;调用新增的方法,跳转到create.jsp
this.showCreatePage(request, response);
}else if(method.equals("create")) {
this.create(request, response);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response); // doPost调用了doGet()方法,所以,把逻辑代码都写在doGet方法中就可以了;
}
/**
*
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String p = request.getParameter("p");
String r = request.getParameter("r");
if(p==null) {
p = "1";
}
if(r==null) {
r = "6";
}
PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
request.setAttribute("pageModel", pageModel);
request.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(request, response);
}
private void delete(HttpServletRequest request, HttpServletResponse response) {
}
/**
* 显示【新增】页面;这个方法,是一个纯粹的入口;
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void showCreatePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(request, response);
}
/**
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
* @throws FileUploadException
*/
private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
// 使用FileUpload需要按照以下步骤
// 1.初始化FileUpload组件
FileItemFactory factory = new DiskFileItemFactory();
/**
* FileItemFactory :将前端表单的数据转化为一个一个的FileItem对象;即用于数据的转换;
* ServletFileUpload :为FileUpload组件提供凝Java web层面上的HTTP请求解析的功能;即提供对web的支持;
*/
ServletFileUpload sf = new ServletFileUpload(factory);
// 2.遍历所有的FileItem
try {
// 对原有的request请求进行解析;这个方法执行后,就会将当前请求中每一个提交来的表单数据转化为一个一个的FileItem对象;
// 并且方法的返回值是一个List
// 这个会抛出“FileUploadException”,这儿建议对异常进行捕捉而不是抛出;
// 这个“FileUploadException”异常抛出的时机是:当对form表单进行解析时候,发现当前表单的编码格式并不是【enctype="multipart/form-data"】时候,就会抛出“FileUploadException”异常
List<FileItem> formData = sf.parseRequest(request);
for(FileItem fi:formData) {
// FileItem对象的isFormField()方法可以判断:这个FileItem对象是一个普通输入项,还是一个文件上传框;
// 如果FileItem对象是一个普通输入项,该FileItem对象调用isFormField()方法,返回true;
// 如果FileItem对象是一个文件上传框,该FileItem对象调用isFormField()方法,返回false;
if(fi.isFormField()) {
// 这个输出,只是开发阶段的测试用,后续会有其他处理方式;;;
System.out.println("普通输入项"+fi.getFieldName()+":"+fi.getString("UTF-8"));
}else {
// 这个输出,只是开发阶段的测试用,后续会有其他处理方式;;;
System.out.println("文件上传框"+fi.getFieldName());
// 3.将前端上传的文件,保存到服务器某个目录中
// getRealPath()方法:获取Tomcat在实际运行环境中,某个对应的目录在(部署了该Tomcat)服务器上的物理地址;
String path = request.getServletContext().getRealPath("/upload");
System.out.println(path);
//String fileName = "test.jpg";
// UUID:根据计算机(实际中就是Tomcat部署的服务器)的本地特性,根据当前时间,计算机网卡的mac地址,或
// 者其他的,诸如此类独特的特性,生成一个全世界唯一的字符串;
// UUID类是Java内置的,可以直接使用;调用randomUUID()就可以得到随机字符串;
// 以这个随机字符串作为文件名,根本不用担心重名的问题;
String fileName = UUID.randomUUID().toString();
// 得到文件扩展名:getName():获取文件的名称;然后substring()获取文件的扩展名
String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
fi.write(new File(path,fileName+suffix));
}
}
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
}
ManagementController类需要注意的:
(1)需要导入的包:看下,注意一下就行,别import错包就行;
(2)【FileItemFactory】是一个工厂类,用于创建FileItem对象;
●【FileItem】是什么:在前端表单进行数据提交的时候,表单的每一个输入项在提交到后端(也就是服务端)的时候,都被叫做是一个【FileItem文件对象】;即可以把每一个FileItem与前端所传递的对应数据进行对应;即可以理解为:FileItem对象就是包含了表单(某一)项的数据对象;
●【FileItemFactory factory = new DiskFileItemFactory();】中的,factory对象的职责就是负责将前台传入的数据,一个一个转化为对应的FileItem;后续的代码体现了出来;
(3)一点对应关系的说明:
其中注意一下;
● getFieldName():获得【普通输入项】和【文件上传框】的,在前端文件标签中的name属性;
● getString():获得【普通输入项】的实际输入值;【文件上传框】到后端之后是二进制数据文件,无法通过getString()获取实际输入值;
(4)文件上传的逻辑分析
需要注意:
● request.getServletContext().getRealPath("/upload");:获取当前Tomcat在服务器中某个目录的实际物理地址;
● FileItem类的write方法:将文件写入到服务器的对应的地址中去;
(5)上传的文件,如何防止重名异常
● getName()方法:获得【文件上传框】的文件文件名;
如果名字写死,或者后面上传的文件名字,和在upload目录中已有文件重名的话,会报异常,提示文件已经存在,而且这个是不允许进行文件覆盖的;
即FileUpload组件在上传文件的时候:不支持后上传的文件去覆盖掉先前上传的(重名的)文件;
解决方案:需要在文件名上,让每一次上传的文件,都产生一个不一样的文件名;
可以采用随机数,或者当前的时间作为文件名;
利用UUID类获取随机文件名:注意FileItem对象的getName()方法,可以获取上传文件的文件名;
这样以后,上传的文件如下:发现在Tomcat在服务器(这儿的服务器是本地的计算机)中的某个指定目录下,有这个文件了;
一个疑问:?????????????????????????????、、这个问题的答案,本篇文章的第5大节:【5.启动程序:(附加一点说明:Tomcat发布目录)】有说明!!!!!
…………………………
(2.3)文件上传后,接下来的任务,就是将这个form表单提交过来的数据,新增到存储数据的xml文件中!!!
(3)向xml中添加数据
(3.1)在XmlDataSource工具类中,编写添加的方法
具体Dom4j内容,可以参考这篇博客操作xml,使用Dom4j追加这篇博客;
XmlDataSource这个工具类:
package com.imooc.mgallery.utils;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import com.imooc.mgallery.entity.Painting;
/**
* 数据源类,将xml文件解析为Java对象
*
* @author dell
*
*/
public class XmlDataSource {
// 为了保证在同一时间,内存中只有一份xml的Document对象,即为了保证加载的xml对象全局唯一
// 通过static静态关键字保证数据的全局唯一性;(自然也可以通过单例模式)
private static List<Painting> data = new ArrayList<Painting>(); // 在XmlDataSource类加载的时候,就会创建data对象,这个对象隶属于XmlDataSource类,可以保证是全局唯一;
private static String dataFile; // xml文件的地址;
static {
// 程序运行编译后,src目录下得java文件会被编译为class文件,这些class文件会被放在classes目录下,而同样处于src目录下的painting.xml文件也会放在classes目录下;
// XmlDataSource.class.getResoure()得到classes目录的根路径,然后在classes目录的根路径下找到painting.xml,然后getPath()获得painting.xml文件的完整的物理磁盘的地址;
dataFile = XmlDataSource.class.getResource("/painting.xml").getPath();
//System.out.println(dataFile);
// 如painting.xml文件的地址是:c:new stylepainting.xml;可以发现,new和style之间有一个空格,这个空格是个特殊字符;
// datFile得到painting.xml文件地址的时候,会进行base64转换,实际dataFile的值会是:c:new%20stylepainting.xml,即空格被转化成了%20;
// 但是如果在后续中,利用JavaIO对painting.xml文件按照“c:new%20stylepainting.xml”读取时,会提示路径找不到,因为%20不会被JavaIO解析;需要手动的将%20转换为空格;
// URLDecoder的作用就是:将base64转回普通的字符串;
reload();
}
/**
* 读取xml文件内容到内存中,把这个功能,提炼成了一个方法
*/
private static void reload() {
data.clear();// 先清空
URLDecoder decoder = new URLDecoder();
try {
dataFile = decoder.decode(dataFile, "UTF-8"); // 这个需要捕获“不支持的编码格式”异常
//System.out.println(dataFile);
SAXReader reader = new SAXReader();
Document document = reader.read(dataFile); // 需要捕获“DocumentException”异常
List<Node> nodes = document.selectNodes("/root/painting");
for(Node node:nodes) {
Element element = (Element)node;
// 提取数据,如何将数据转换成Java对象?通过什么载体来保存油画的数据?所以,需要开发对应的JavaBean承载油画数据;
String id = element.attributeValue("id");
String pname = element.elementText("pname");
//
Painting painting = new Painting();
painting.setId(Integer.parseInt(id));
painting.setPname(pname);
// 剩余几个采用紧凑的写法
painting.setCategory(Integer.parseInt(element.elementText("category")));
painting.setPrice(Integer.parseInt(element.elementText("price")));
painting.setPreview(element.elementText("preview"));
painting.setDescription(element.elementText("description"));
// 将对象存储到data集合中;
data.add(painting); // 这样以后,当XmlDataSource这个类被加载以后,data集合中就保存了完整的油画信息;
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 为了保证外部可以访问data集合,需要给data增加一个出口;;;
* 这儿添加一个方法,getRawData():即获取最原始的信息;
* @return
*/
public static List<Painting> getRawData(){
return data;
}
/**
*
* @param painting :从前端封装好的Painting对象,一个Painting对象就是一个油画数据
* @throws DocumentException
*/
public static void append(Painting painting) {
//1.读取XML文档,得到Document对象
SAXReader reader = new SAXReader();
Writer writer = null;
try {
Document document = reader.read(dataFile); // xml文件地址,上面已经定义好了,直接拿来用就可以了
//2.创建新的painting
Element root = document.getRootElement(); // 获取根节点
Element p = root.addElement("painting"); // 创建一个新的节点
// 下面,就根据原始xml的结构,来以此设置它的属性和子节点了
//3.创建painting节点的各个子节点
p.addAttribute("id", String.valueOf(data.size()+1)); // 直接在原有节点数的基础上加一就可以了;
Element pname = p.addElement("pname");
pname.setText(painting.getPname());
p.addElement("category").setText(painting.getCategory().toString());
p.addElement("price").setText(painting.getPrice().toString());
p.addElement("preview").setText(painting.getPreview());
p.addElement("description").setText(painting.getDescription());
// 自此,就创建了一个新的painting节点,属性节点已经设置好了;;即内存中的Document对象就形成了一个全新的油画数据;
// 接下来,需要把内存中的油画数据,写入到xml文件中
//4.写入XML,完成追加操作
writer = new OutputStreamWriter(new FileOutputStream(dataFile),"UTF-8");
document.write(writer);
//System.out.println(dataFile); // 测试用,测试后删除
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}finally {
if(writer != null) { // 这儿将writer的关闭操作,写在了finally中;
try {
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
reload();// 将重新加载的方法,写在了finally中,这或许也是为了保证reaload()方法一定会被执行的 一个保证措施吧
}
}
// public static void main(String[] args) { // 这个main方法主要目的,是测试一下
//new XmlDataSource(); // 创建这个对象之前,需要加载XmlDataSource这个类,一旦加载这个类,那么和这个类的static块就会初始dataFile对象;
List<Painting> list = XmlDataSource.getRawData();
System.out.println(list);
// Painting p = new Painting();
// p.setPname("测试油画");
// p.setCategory(1);
// p.setPrice(4500);
// p.setPreview("/upload/10.jpg");
// p.setDescription("这是一张测试的图片");
//
// XmlDataSource.append(p);
// }
}
XmlDataSource这个工具类几点说明:
(1)主要是新增了append()方法;注意一下,【writer.close();】写在了finally中了,注意下这种写法吧。目前觉得这样做也可以,但不这样做为什么不行还不是特别明白;;;
(2)为了能在xml文件内容改变后,能实时更新xml在内存中的内容,将data的产生的内容,实例化了一个方法;注意reload()方法首行的data.clear();
XmlDataSource类中编写main方法,运行测试后:
发现,此时油画信息已经写进xml文件中了,表示这部分的编码没有问题;;;;;;在实际工作中,为了测试某些功能代码是否编写成功,似乎可以采用这种,添加个main方法,来局部测试的套路!!!!!
(3.2)在Dao中编写create()方法,向xml中添加数据;
package com.imooc.mgallery.dao;
import java.util.ArrayList;
import java.util.List;
import com.imooc.mgallery.entity.Painting;
import com.imooc.mgallery.utils.PageModel;
import com.imooc.mgallery.utils.XmlDataSource;
/**
* Dao类;调用PageModel类和XmlDataSource这个两个工具类;
* 数据访问对象类,作用是获取最原始的xml数据,并且对其进行分页;
* @author dell
*
*/
public class PaintingDao {
/**
* 实现分页的方法
* @param page 查询第几页数据;
* @param rows 每一页显示几条
* @return
*/
public PageModel pagination(int page,int rows) {
List<Painting> xmlDataSourceList = XmlDataSource.getRawData(); // 先获取xml完整的数据;
PageModel pageModel = new PageModel(xmlDataSourceList,page,rows);
return pageModel;
}
/**
* 对pagination方法进行重载
* @param category 类别
* @param page 查询第几页数据
* @param rows 每一页显示几条数据
* @return
*/
public PageModel pagination(int category,int page,int rows) {
List<Painting> xmlDataSourceList = XmlDataSource.getRawData(); // 先获取xml完整的数据;
List<Painting> categoryList = new ArrayList<Painting>(); // 保存符合类别要求的数据
for(Painting p:xmlDataSourceList) {
if(p.getCategory() == category) {
categoryList.add(p);
}
}
PageModel pageModel = new PageModel(categoryList,page,rows);
return pageModel;
}
/**
* 新增油画数据
* @param painting
*/
public void create(Painting painting) {
XmlDataSource.append(painting);
}
}
PaintingDao类的几点说明:
(1)Dao中内容很简单,只是调用了 XmlDataSource工具类中的append()方法;
(3.3)在Service中编写:create()方法;
package com.imooc.mgallery.service;
import java.util.List;
import com.imooc.mgallery.dao.PaintingDao;
import com.imooc.mgallery.entity.Painting;
import com.imooc.mgallery.utils.PageModel;
/**
* 这个类主要职责:完成的程序业务逻辑;
* 涉及到与底层数据交互的工作,交给Dao类去实现
* @author dell
*
*/
public class PaintingService {
private PaintingDao paintingDao = new PaintingDao();
/**
* 调用PaintingDao类的pagination()方法,获得分页数据;
* 这个类的内容看似和PageModel类的内容雷同,但是这个类还是必须的,在实际的开发中,需要遵从MVC原
* 则的按层逐级调用的规范;;;所以,即便没有其他的业务逻辑,我们也要写一个Service,然后让这个Service去调用Dao;
* @param page 当前第几页
* @param rows 每页有几条数据
* @return 分页对象
*/
public PageModel pagination(int page,int rows,String...category) {
if(rows == 0) { // 可以看到,Service类中不但需要调用Dao来进行数据访问;
// Service类还包括:一些前置条件的检查,以及得到调用结果后的后置数据的处理,这些工作都是与底层数据无关的
// Service类中的方法用于处理完整的业务逻辑,Service类中方法需要尽量写的完整;
// 而Dao中的方法只与底层数据进行交互的;;;
// 所以,在这个例子中,即使Service类中的方法和Dao中的内容基本相同,也必须要要写这个Service类;
throw new RuntimeException("无效的rows参数");
}
if((category.length==0)||(category[0]==null)) { // 如果没有传递可选参数category的时候;
return paintingDao.pagination(page, rows);
}else {
return paintingDao.pagination(Integer.parseInt(category[0]),page, rows);
}
}
/**
* 新增油画数据
* 因为这儿逻辑比较简单,所以没有进行前置或者后置处理
* @param painting
*/
public void create(Painting painting) {
paintingDao.create(painting);
}
}
PaintingService类的几点说明:
(1)因为这儿的逻辑比较简单,Service中并没有添加一些前置或者后置代码,而是直接调用了Dao中的create()方法;
(3.4)编写Controller部分:
package com.imooc.mgallery.controller;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
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 org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.imooc.mgallery.entity.Painting;
import com.imooc.mgallery.service.PaintingService;
import com.imooc.mgallery.utils.PageModel;
/**
* 后台管理功能Controller;
* 后台系统,所需要的增,删,改,查的操作,都在这一个Controller类中完成;
* Servlet implementation class ManagementController
*/
@WebServlet("/management")
public class ManagementController extends HttpServlet {
private static final long serialVersionUID = 1L;
// 创建PaintingService对象;;
// 即无论是前台系统的PaintingController,还是后台系统的ManagementController都调用PaintingService中提供的方法;
private PaintingService paintingService = new PaintingService();
/**
* @see HttpServlet#HttpServlet()
*/
public ManagementController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置请求体中的字符集编码方式;;;
// get请求没有请求体,所以,这个语句是为doPost()方法中执行doGet(request,response)后,跳转过来的post请求来设置的;
// 即这条代码是为doPost()来进行服务的;
request.setCharacterEncoding("UTF-8");
// 设置响应的字符集编码方式
response.setContentType("text/html;charset=utf-8");
String method = request.getParameter("method");
if(method.equals("list")) { // 当前台传入的method参数值为“list”的时候,代表是分页请求,调用定义的list方法;
this.list(request,response); // 然后,将分页处理的代码写在list方法中就可以了;
}else if(method.equals("delete")) { // 当method参数值为“delete”时,表示是删除请求,调用定义的delete方法;
this.delete(request, response);
}else if(method.equals("show_create")) {
// method参数为“show_create”,表示是新增;调用新增的方法,跳转到create.jsp
this.showCreatePage(request, response);
}else if(method.equals("create")) {
this.create(request, response);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response); // doPost调用了doGet()方法,所以,把逻辑代码都写在doGet方法中就可以了;
}
/**
*
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String p = request.getParameter("p");
String r = request.getParameter("r");
if(p==null) {
p = "1";
}
if(r==null) {
r = "6";
}
PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
request.setAttribute("pageModel", pageModel);
request.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(request, response);
}
private void delete(HttpServletRequest request, HttpServletResponse response) {
}
/**
* 显示【新增】页面;这个方法,是一个纯粹的入口;
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void showCreatePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(request, response);
}
/**
* 新增油画数据
* @param request
* @param response
* @throws ServletException
* @throws IOException
* @throws FileUploadException
*/
private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
// 使用FileUpload需要按照以下步骤
// 1.初始化FileUpload组件
FileItemFactory factory = new DiskFileItemFactory();
/**
* FileItemFactory :将前端表单的数据转化为一个一个的FileItem对象;即用于数据的转换;
* ServletFileUpload :为FileUpload组件提供凝Java web层面上的HTTP请求解析的功能;即提供对web的支持;
*/
ServletFileUpload sf = new ServletFileUpload(factory);
// 2.遍历所有的FileItem
try {
// 对原有的request请求进行解析;这个方法执行后,就会将当前请求中每一个提交来的表单数据转化为一个一个的FileItem对象;
// 并且方法的返回值是一个List
// 这个会抛出“FileUploadException”,这儿建议对异常进行捕捉而不是抛出;
// 这个“FileUploadException”异常抛出的时机是:当对form表单进行解析时候,发现当前表单的编码格式并不是【enctype="multipart/form-data"】时候,就会抛出“FileUploadException”异常
List<FileItem> formData = sf.parseRequest(request);
Painting painting = new Painting();
for(FileItem fi:formData) {
// FileItem对象的isFormField()方法可以判断:这个FileItem对象是一个普通输入项,还是一个文件上传框;
// 如果FileItem对象是一个普通输入项,该FileItem对象调用isFormField()方法,返回true;
// 如果FileItem对象是一个文件上传框,该FileItem对象调用isFormField()方法,返回false;
if(fi.isFormField()) {
// 这个输出,只是开发阶段的测试用,后续会有其他处理方式;;;
switch (fi.getFieldName()) {
case "pname":
// 当请求中是一个form,而且这个form的编码方式是“multipart/form-data”时候,在doGet()方法中的【request.setCharacterEncoding("UTF-8");】会失效;;;所以这儿在获取请求中(表单的)参数值的时候,需要设置下编码方式
painting.setPname(fi.getString("UTF-8"));
break;
case "category":
painting.setCategory(Integer.parseInt(fi.getString("UTF-8")));
break;
case "price":
painting.setPrice(Integer.parseInt(fi.getString("UTF-8")));
break;
case "description":
painting.setDescription(fi.getString("UTF-8"));
break;
default:
break;
}
//System.out.println("普通输入项"+fi.getFieldName()+":"+fi.getString("UTF-8"));
}else {
// 这个输出,只是开发阶段的测试用,后续会有其他处理方式;;;
System.out.println("文件上传框"+fi.getFieldName());
// 3.将前端上传的文件,保存到服务器某个目录中
// getRealPath()方法:获取Tomcat在实际运行环境中,某个对应的目录在(部署了该Tomcat)服务器上的物理地址;
String path = request.getServletContext().getRealPath("/upload");
System.out.println(path);
//String fileName = "test.jpg";
// UUID:根据计算机(实际中就是Tomcat部署的服务器)的本地特性,根据当前时间,计算机网卡的mac地址,或
// 者其他的,诸如此类独特的特性,生成一个全世界唯一的字符串;
// UUID类是Java内置的,可以直接使用;调用randomUUID()就可以得到随机字符串;
// 以这个随机字符串作为文件名,根本不用担心重名的问题;
String fileName = UUID.randomUUID().toString();
// 得到文件扩展名:getName():获取文件的名称;然后substring()获取文件的扩展名
String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
fi.write(new File(path,fileName+suffix));
painting.setPreview("/upload/"+fileName + suffix);// 设置油画地址
}
}
paintingService.create(painting);
// 由此,后台部分,油画的新增操作已经完成了;;;;然后可以再跳转到油画列表页面,对数据进行展示
// 使用响应重定向,调回到油画列表页面;
// 上面【完成新增数据】和【显示列表页】,这两者并没有明显的直接关系;;;【显示列表页】仅仅是让浏览器跳转到一个全新的功能上,对于此类场景就可以使用响应重定向;
// 如果此时,【新增完数据 】之后,不是显示列表页,而是弹出另外一个页面,进行新增数据以后的后续操作,这个操作和前面的新增数据是紧密联系的,此时就需要使用请求转发,将当前的请求转给下面的功能,继续进行操作;
response.sendRedirect("/management?method=list");
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
}
ManagementController类的几点说明:
(1)这个类主要作用,是获取前端传过来的form表单中的各项目数据,组成一个Painting类对象,作为调用Service中的方法的参数;
(2) 注意Switch的这种用法;;;表单+附带文件+FileUpload组件+获取对应值::就用Switch!!!!!!不过好像不一定,利用if判断,好像也能完成,不过这儿使用了Switch,注意一下吧,这大概率是更好的的一种策略;
(3)还有需要注意【paintingService.create(painting);】和【response.sendRedirect("/management?method=list");】也都写在了try块中,需要注意下哦;这样写是更合理的,可以设想一下,万一上面的代码有异常了后,需要先处理异常,如果异常没有处理完,就不要执行“【paintingService.create(painting);】和【response.sendRedirect("/management?method=list");】”;;;;如果“【paintingService.create(painting);】和【response.sendRedirect("/management?method=list");】”不写在try块中,那么会发生这种现象:try块中代码报错了,异常处理完成后,本次的信息按理说已经是异常的了,应该重新发起一次正确的调用,但是程序会继续执行“【paintingService.create(painting);】和【response.sendRedirect("/management?method=list");】”,这显然是不合理的;;;说的好乱,但是自己应该能懂;;;
(4)最后,注意一下,请求转发和重定向,应用场景的不同;;;这一点有点感觉,但似乎并没有彻底理解;;;可以看下请求转发与响应重定向这篇文章。
……………………………………………………
(3.5)最后补充一点:属性中有id(自增,唯一标识对象)属性的时候,javaBean一般不添加有参构造;
5.启动程序:(附加一点说明:Tomcat发布目录)
(1)启动应用,观察效果
点击【提交表单】后,会根据Controller中编写的逻辑,跳转到油画列表页面;
注:当关闭Tomcat服务器,(需要clean一下,具体clean是什么意思,可以看下eclipse中使用clean命令这篇文章;;;可以这样认为,当使用clean命令后,下次启动应用的时候,Tomcat会先彻底清空发布目录的东西,然后重新发布项目所有相关的所需文件),再次发布项目并查看Tomcat项目的发布目录的时候:
OK,能懂。。。
(2)再一点说明
Eclipse的WorkSpace:
……………………………………………………
Eclipse这个IDE中的build目录:
……………………………………………………
Tomcat项目发布目录:(这儿就和eclipse中的clean命令的清空和刷新发布目录的问题了)
OK,自己慢慢熟悉吧~~~目测,在IDEA一定会有和【Eclipse中的clean命令】效果一样的操作;
最后
以上就是冷酷飞鸟为你收集整理的后台系统四:【新增】功能;(FileUpload组件) 0.新增功能需要完成的功能概述:1.将新增页create.html添加到项目中2.在ManagementController类中,添加访问create.jsp的入口3.编写create.jsp:为文件上传做好准备4.在ManagementController类中,添加接收create.jsp表单提交的方法; 5.启动程序:(附加一点说明:Tomcat发布目录)的全部内容,希望文章能够帮你解决后台系统四:【新增】功能;(FileUpload组件) 0.新增功能需要完成的功能概述:1.将新增页create.html添加到项目中2.在ManagementController类中,添加访问create.jsp的入口3.编写create.jsp:为文件上传做好准备4.在ManagementController类中,添加接收create.jsp表单提交的方法; 5.启动程序:(附加一点说明:Tomcat发布目录)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复