Docker下Java文件上传服务三部曲之二:服务端开发
本章是《Docker下Java文件上传服务三部曲》的第二篇,上一章《Docker下Java文件上传服务三部曲之一:准备环境》我们把客户端准备好了,Tomcat容器也部署好了,今天就来开发和部署文件服务的后台应用吧;
原文地址:http://blog.csdn.net/boling_cavalry/article/details/79367520
三部曲所有文章链接
《Docker下Java文件上传服务三部曲之一:准备环境》
《Docker下Java文件上传服务三部曲之二:服务端开发》
《Docker下Java文件上传服务三部曲之三:wireshark抓包分析》
本章实战内容概要
本章要创建三个web应用,都是文件上传的服务端,分别部署在ubuntu电脑上的三个Docker容器中,接收来自客户端的上传文件的请求,结构如下:
这里写图片描述
三个web应用功能相同,都是文件上传的服务端,它们的差别用下表来说明:
应用名 框架 文件服务技术方案 Docker上的部署方式
springmvcfileserver spring mvc springframework的CommonsMultipartResolver 构建war,部署到Tomcat容器上
fileserverdemo spring mvc apache的commons-fileupload库 构建war,部署到Tomcat容器上
springbootfileserver springboot springframework的CommonsMultipartResolver 构建jar,做成Docker镜像
如何将war包在线部署到Tomcat容器上
springmvcfileserver和fileserverdemo在pom.xml中都用到了tomcat7-maven-plugin插件,可以将war包在线部署到Tomcat容器上,记得修改本地的maven配置文件,将登录tomcat的用户名和密码加进去,配置的详情请参照《实战docker,编写Dockerfile定制tomcat镜像,实现web应用在线部署》;
如何将springboot工程构建成docker镜像
springboot工程是可以直接构建成docker镜像的,在docker下以此镜像创建容器,该springboot工程就直接运行起来了,如果您想了解具体构建的细节,请访问以下几篇文章:
《maven构建docker镜像三部曲之一:准备环境》;
《maven构建docker镜像三部曲之二:编码和构建镜像》;
《maven构建docker镜像三部曲之三:推送到远程仓库(内网和阿里云)》;
三个web应用的源码下载
您可以在GitHub下载本章三个web应用的源码,地址和链接信息如下表所示:
名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
这个git项目中有多个目录,本次所需的资源放在springmvcfileserver、fileserverdemo、springbootfileserver这三个目录下,如下图红框所示:
这里写图片描述
SpirngMVC框架如何处理上传的文件?
SpirngMVC对POST请求中的二进制文件的处理,是依赖apache的commons-fileupload库来完成的,如果您想了解更多细节,请参考文章《SpringMVC源码分析:POST请求中的文件处理》;
创建应用springmvcfileserver
创建一个maven工程springmvcfileserver,pom.xml内容如下:
1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 3 <modelVersion>4.0.0</modelVersion> 4 <groupId>com.bolingcavalry</groupId> 5 <artifactId>springmvcfileserver</artifactId> 6 <packaging>war</packaging> 7 <version>1.0-SNAPSHOT</version> 8 <name>springmvcfileserver Maven Webapp</name> 9 <url>http://maven.apache.org</url> 10 <properties> 11 <!-- spring版本号 --> 12 <spring.version>4.0.2.RELEASE</spring.version> 13 </properties> 14 <dependencies> 15 <!-- spring核心包 --> 16 <dependency> 17 <groupId>org.springframework</groupId> 18 <artifactId>spring-core</artifactId> 19 <version>${spring.version}</version> 20 </dependency> 21 <dependency> 22 <groupId>org.springframework</groupId> 23 <artifactId>spring-web</artifactId> 24 <version>${spring.version}</version> 25 </dependency> 26 <dependency> 27 <groupId>org.springframework</groupId> 28 <artifactId>spring-webmvc</artifactId> 29 <version>${spring.version}</version> 30 </dependency> 31 <dependency> 32 <groupId>org.springframework</groupId> 33 <artifactId>spring-aop</artifactId> 34 <version>${spring.version}</version> 35 </dependency> 36 <dependency> 37 <groupId>org.springframework</groupId> 38 <artifactId>spring-context-support</artifactId> 39 <version>${spring.version}</version> 40 </dependency> 41 <!-- 导入java ee jar 包 --> 42 <dependency> 43 <groupId>javax</groupId> 44 <artifactId>javaee-api</artifactId> 45 <version>7.0</version> 46 </dependency> 47 48 <!-- 映入JSON --> 49 <dependency> 50 <groupId>org.codehaus.jackson</groupId> 51 <artifactId>jackson-mapper-asl</artifactId> 52 <version>1.9.13</version> 53 </dependency> 54 <!-- 上传组件包 --> 55 <dependency> 56 <groupId>commons-fileupload</groupId> 57 <artifactId>commons-fileupload</artifactId> 58 <version>1.3.1</version> 59 </dependency> 60 61 <dependency> 62 <groupId>org.slf4j</groupId> 63 <artifactId>slf4j-api</artifactId> 64 <version>1.7.6</version> 65 </dependency> 66 67 <dependency> 68 <groupId>ch.qos.logback</groupId> 69 <artifactId>logback-core</artifactId> 70 <version>1.0.9</version> 71 </dependency> 72 73 <dependency> 74 <groupId>ch.qos.logback</groupId> 75 <artifactId>logback-classic</artifactId> 76 <version>1.0.9</version> 77 </dependency> 78 79 </dependencies> 80 81 <build> 82 <finalName>${project.artifactId}</finalName> 83 84 <resources> 85 <resource> 86 <directory>src/main/java</directory> 87 <includes> 88 <include>**/*.properties</include> 89 <include>**/*.xml</include> 90 </includes> 91 <!-- 是否替换资源中的属性--> 92 <filtering>false</filtering> 93 </resource> 94 <resource> 95 <directory>src/main/resources</directory> 96 </resource> 97 </resources> 98 <plugins> 99 <plugin> 100 <groupId>org.apache.tomcat.maven</groupId> 101 <artifactId>tomcat7-maven-plugin</artifactId> 102 <version>2.2</version> 103 <configuration> 104 <url>http://192.168.119.155:8088/manager/text</url> 105 <server>tomcat7</server> 106 <path>/${project.artifactId}</path> 107 <update>true</update> 108 </configuration> 109 </plugin> 110 <plugin> 111 <groupId>org.apache.maven.plugins</groupId> 112 <artifactId>maven-compiler-plugin</artifactId> 113 <configuration> 114 <source>1.6</source> 115 <target>1.6</target> 116 </configuration> 117 </plugin> 118 </plugins> 119 </build> 120 </project>
注意上述配置中的http://192.168.119.155:8088/manager/text这个地址,“192.168.119.155”是我的ubuntu电脑的IP地址,8088是Tomcat容器启动用映射到ubuntu电脑的端口号,请您按照自己电脑的实际情况修改;
2. 在web.xml中添加springmvc相关的配置:
1 <servlet> 2 <servlet-name>SpringMVC</servlet-name> 3 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 4 <init-param> 5 <param-name>contextConfigLocation</param-name> 6 <param-value>classpath:spring-mvc.xml</param-value> 7 </init-param> 8 <load-on-startup>1</load-on-startup> 9 <async-supported>true</async-supported> 10 </servlet> 11 <servlet-mapping> 12 <servlet-name>SpringMVC</servlet-name> 13 <!-- 此处可以可以配置成*.do,对应struts的后缀习惯 --> 14 <url-pattern>/</url-pattern> 15 </servlet-mapping>
spring-mvc.xml中要配置multipartResolver:
1 <bean id="multipartResolver" 2 class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 3 <property name="defaultEncoding" value="utf-8" /> 4 <property name="maxUploadSize" value="10485760000" /> 5 <property name="maxInMemorySize" value="40960" /> 6 </bean>
在spring-extends.xml中配置自动扫描的规则:
<context:component-scan base-package="com.bolingcavalry" />
上传服务的controller是UploadController.java,响应请求的upload方法如下:
1 @RequestMapping(value="/upload",method= RequestMethod.POST) 2 public void upload(HttpServletRequest request, 3 HttpServletResponse response, 4 @RequestParam("comment") String comment, 5 @RequestParam("file") MultipartFile file) throws Exception { 6 7 logger.info("start upload, comment [{}]", comment); 8 9 if(null==file || file.isEmpty()){ 10 logger.error("file item is empty!"); 11 responseAndClose(response, "文件数据为空"); 12 return; 13 } 14 15 //上传文件路径 16 String savePath = request.getServletContext().getRealPath("/WEB-INF/upload"); 17 18 //上传文件名 19 String fileName = file.getOriginalFilename(); 20 21 logger.info("base save path [{}], original file name [{}]", savePath, fileName); 22 23 //得到文件保存的名称 24 fileName = mkFileName(fileName); 25 26 //得到文件保存的路径 27 String savePathStr = mkFilePath(savePath, fileName); 28 29 logger.info("real save path [{}], real file name [{}]", savePathStr, fileName); 30 31 File filepath = new File(savePathStr, fileName); 32 33 //确保路径存在 34 if(!filepath.getParentFile().exists()){ 35 logger.info("real save path is not exists, create now"); 36 filepath.getParentFile().mkdirs(); 37 } 38 39 String fullSavePath = savePathStr + File.separator + fileName; 40 41 //存本地 42 file.transferTo(new File(fullSavePath)); 43 44 logger.info("save file success [{}]", fullSavePath); 45 46 responseAndClose(response, "Spring MVC环境下,上传文件成功"); 47 }
前面的分析中,我们已知道入参的MultipartFile对象是apache的commons-fileupload库解析出来的,这里直接用就好了;
7. 在pom.xml文件的目录执行以下命令,即可编译构建war包,并部署到Tomcat容器上去:
mvn clean package -U -Dmaven.test.skip=true tomcat7:deploy
应用部署完毕后,可以通过上一章开发的UploadFileClient类测试服务是否正常;
接下来我们创建第二个应用fileserverdemo;
创建应用fileserverdemo
应用fileserverdemo被设计成不使用spring mvc的文件处理功能,而是自己写代码调用apache的commons-fileupload库来处理上传的文件,关键代码如下:
在spring-mvc.xml中,不要配置multipartResolver,如果不配置multipartResolver的话,spring mvc是如何处理的呢?一起来看下DispatcherServlet.checkMultipart方法:
1 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { 2 if (request instanceof MultipartHttpServletRequest) { 3 logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " + 4 "this typically results from an additional MultipartFilter in web.xml"); 5 } 6 else { 7 return this.multipartResolver.resolveMultipart(request); 8 } 9 } 10 // If not returned before: return original request. 11 return request;
如果没有multipartResolver,那么该方法返回的就是传入的request,该request最终会传递给业务Controller的响应方法中去;
2. 业务controller的响应方法如下:
1 @RequestMapping("/upload") 2 public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception{ 3 logger.info("start upload"); 4 //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全 5 String savePath = request.getServletContext().getRealPath("/WEB-INF/upload"); 6 //上传时生成的临时文件保存目录 7 String tempPath = request.getServletContext().getRealPath("/WEB-INF/temp"); 8 9 logger.info("savePath [{}], tempPath [{}]", savePath, tempPath); 10 11 File file = new File(tempPath); 12 if(!file.exists()&&!file.isDirectory()){ 13 logger.info("临时文件目录不存在logger.info,现在创建。"); 14 file.mkdir(); 15 } 16 17 //消息提示 18 String message = ""; 19 try { 20 //使用Apache文件上传组件处理文件上传步骤: 21 //1、创建一个DiskFileItemFactory工厂 22 DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); 23 //设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。 24 diskFileItemFactory.setSizeThreshold(1024*100); 25 //设置上传时生成的临时文件的保存目录 26 diskFileItemFactory.setRepository(file); 27 //2、创建一个文件上传解析器 28 ServletFileUpload fileUpload = new ServletFileUpload(diskFileItemFactory); 29 //解决上传文件名的中文乱码 30 fileUpload.setHeaderEncoding("UTF-8"); 31 //监听文件上传进度 32 fileUpload.setProgressListener(new ProgressListener(){ 33 public void update(long pBytesRead, long pContentLength, int arg2) { 34 logger.debug("total [{}], now [{}]", pContentLength, pBytesRead); 35 } 36 }); 37 38 //3、判断提交上来的数据是否是上传表单的数据 39 if(!fileUpload.isMultipartContent(request)){ 40 logger.error("this is not a file post"); 41 responseAndClose(response, "无效的请求参数,请提交文件"); 42 //按照传统方式获取数据 43 return; 44 } 45 46 //设置上传单个文件的大小的最大值,目前是设置为1024*1024*1024字节,也就是1G 47 fileUpload.setFileSizeMax(1024*1024*1024); 48 //设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10GB 49 fileUpload.setSizeMax(10*1024*1024*1024); 50 //4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项 51 List<FileItem> list = fileUpload.parseRequest(request); 52 53 logger.info("after parse request, file item size [{}]", list.size()); 54 55 for (FileItem item : list) { 56 //如果fileitem中封装的是普通输入项的数据 57 if(item.isFormField()){ 58 String name = item.getFieldName(); 59 //解决普通输入项的数据的中文乱码问题 60 String value = item.getString("UTF-8"); 61 String value1 = new String(name.getBytes("iso8859-1"),"UTF-8"); 62 logger.info("form field, name [{}], value [{}], name after convert [{}]", name, value, value1); 63 }else{ 64 //如果fileitem中封装的是上传文件,得到上传的文件名称, 65 String fileName = item.getName(); 66 logger.info("not a form field, file name [{}]", fileName); 67 if(fileName==null||fileName.trim().equals("")){ 68 logger.error("invalid file name"); 69 continue; 70 } 71 //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt 72 //处理获取到的上传文件的文件名的路径部分,只保留文件名部分 73 fileName = fileName.substring(fileName.lastIndexOf(File.separator)+1); 74 75 //得到上传文件的扩展名 76 String fileExtName = fileName.substring(fileName.lastIndexOf(".")+1); 77 78 logger.info("ext name [{}], file name after cut [{}]", fileExtName, fileName); 79 80 if("zip".equals(fileExtName)||"rar".equals(fileExtName)||"tar".equals(fileExtName)||"jar".equals(fileExtName)){ 81 logger.error("this type can not upload [{}]", fileExtName); 82 responseAndClose(response, "上传文件的类型不符合"); 83 return; 84 } 85 86 //获取item中的上传文件的输入流 87 InputStream is = item.getInputStream(); 88 //得到文件保存的名称 89 fileName = mkFileName(fileName); 90 //得到文件保存的路径 91 String savePathStr = mkFilePath(savePath, fileName); 92 System.out.println("保存路径为:"+savePathStr); 93 //创建一个文件输出流 94 FileOutputStream fos = new FileOutputStream(savePathStr+File.separator+fileName); 95 //创建一个缓冲区 96 byte buffer[] = new byte[1024]; 97 //判断输入流中的数据是否已经读完的标识 98 int length = 0; 99 //循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据 100 while((length = is.read(buffer))>0){ 101 //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 102 fos.write(buffer, 0, length); 103 } 104 //关闭输入流 105 is.close(); 106 //关闭输出流 107 fos.close(); 108 //删除处理文件上传时生成的临时文件 109 item.delete(); 110 message = "文件上传成功"; 111 } 112 } 113 } catch (FileUploadBase.FileSizeLimitExceededException e) { 114 logger.error("1. upload fail, ", e); 115 responseAndClose(response, "单个文件超出最大值!!!"); 116 return; 117 }catch (FileUploadBase.SizeLimitExceededException e) { 118 logger.error("2. upload fail, ", e); 119 responseAndClose(response, "上传文件的总的大小超出限制的最大值!!!"); 120 return; 121 }catch (FileUploadException e) { 122 // TODO Auto-generated catch block 123 logger.error("3. upload fail, ", e); 124 message = "文件上传失败:" + e.toString(); 125 } 126 127 responseAndClose(response, message); 128 logger.info("finish upload"); 129 }
上述代码有以下两点需要注意:
a. 由于没有multipartResolver 这个bean的配置,因此upload方法入参中没有FileItem对象,request没有做过文件相关的解析和处理;
b. upload方法中创建了ServletFileUpload对象,并调用parseRequest方法解析出所有FileItem对象,然后按照业务需求做各种处理;
3. 其他的配置和springmvcfileserver工程一样,可以参考GitHub上的源码;
4. 在pom.xml文件的目录执行以下命令,即可编译构建war包,并部署到Tomcat容器上去:
mvn clean package -U -Dmaven.test.skip=true tomcat7:deploy
1
应用部署完毕后,可以通过上一章开发的UploadFileClient类测试服务是否正常,记得修改POST_URL变量为“http://192.168.119.155:8088/fileserverdemo/upload”,把“192.168.119.155”换成你的ubuntu电脑的IP地址;
至此,两个部署在Tomcat容器上的应用都完成了,接下来我们创建springbootfileserver应用,体验springboot工程提供的文件服务;
创建应用springbootfileserver
springbootfileserver应用对文件的处理,本质上还是spring mvc的处理流程,和springmvcfileserver工程的差异在于:不需要我们配置multipartResolver这个bean,springboot启动的时候已经在应用中默认创建了multipartResolver对象,以下是该工程的几处关键代码:
pom.xml中新增一个插件,用来将工程构建成docker镜像:
1 <!--新增的docker maven插件--> 2 <plugin> 3 <groupId>com.spotify</groupId> 4 <artifactId>docker-maven-plugin</artifactId> 5 <version>0.4.12</version> 6 <!--docker镜像相关的配置信息--> 7 <configuration> 8 <!--镜像名,这里用工程名--> 9 <imageName>bolingcavalry/${project.artifactId}</imageName> 10 <!--TAG,这里用工程版本号--> 11 <imageTags> 12 <imageTag>${project.version}</imageTag> 13 </imageTags> 14 <!--镜像的FROM,使用java官方镜像--> 15 <baseImage>java:8u111-jdk</baseImage> 16 <!--该镜像的容器启动后,直接运行spring boot工程--> 17 <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> 18 <!--构建镜像的配置信息--> 19 <resources> 20 <resource> 21 <targetPath>/</targetPath> 22 <directory>${project.build.directory}</directory> 23 <include>${project.build.finalName}.jar</include> 24 </resource> 25 </resources> 26 </configuration> 27 </plugin>
业务controller的响应方法如下:
1 @RequestMapping(value="/upload",method= RequestMethod.POST) 2 public void upload(HttpServletRequest request, 3 HttpServletResponse response, 4 @RequestParam("comment") String comment, 5 @RequestParam("file") MultipartFile file) throws Exception { 6 7 logger.info("start upload, comment [{}]", comment); 8 9 if(null==file || file.isEmpty()){ 10 logger.error("file item is empty!"); 11 responseAndClose(response, "文件数据为空"); 12 return; 13 } 14 15 //上传文件路径 16 String savePath = request.getServletContext().getRealPath("/WEB-INF/upload"); 17 18 //上传文件名 19 String fileName = file.getOriginalFilename(); 20 21 logger.info("base save path [{}], original file name [{}]", savePath, fileName); 22 23 //得到文件保存的名称 24 fileName = mkFileName(fileName); 25 26 //得到文件保存的路径 27 String savePathStr = mkFilePath(savePath, fileName); 28 29 logger.info("real save path [{}], real file name [{}]", savePathStr, fileName); 30 31 File filepath = new File(savePathStr, fileName); 32 33 //确保路径存在 34 if(!filepath.getParentFile().exists()){ 35 logger.info("real save path is not exists, create now"); 36 filepath.getParentFile().mkdirs(); 37 } 38 39 String fullSavePath = savePathStr + File.separator + fileName; 40 41 //存本地 42 file.transferTo(new File(fullSavePath)); 43 44 logger.info("save file success [{}]", fullSavePath); 45 46 responseAndClose(response, "SpringBoot环境下,上传文件成功"); 47 }
可见和springmvcfileserver工程的业务controller基本一致;
3. 在pom.xml所在目录执行以下命令,即可将当前工程编译构建,并制作成docker镜像:
mvn clean package -DskipTests docker:build
注意:由于要制作docker镜像,所以要求当前电脑同时安装了maven和docker,推荐在ubuntu电脑上执行,因为做出来的镜像稍后就会用到了;
4. 在ubuntu电脑上运行以下命令,就能将springbootfileserver工程在容器中运行起来:
docker run --name fileserver001 -p 8080:8080 -v /usr/local/work/fileupload/upload:/usr/Downloads -idt bolingcavalry/springbootfileserver:0.0.1-SNAPSHOT
注意:此命令在上一章执行过,不同的是用的镜像是刚刚构建的;
5. 用UploadFileClient类测试服务是否正常;
至此三个web应用就开发完成了,这三个应用在处理文件服务的过程中,有的简单易用,有的能把控更多细节用来满足相对复杂的业务需求,希望能对您在实现业务需求时提供一些参考;
基本的开发和部署工作已经全部完成,下一章我们通过wireshark抓包,来看看HTTP的POST请求和响应的细节,看下一章请点击《Docker下Java文件上传服务三部曲之三:wireshar抓包分析》;
欢迎关注我的公众号:程序员欣宸
浙公网安备 33010602011771号