第五章 SpringBoot实现Web的常用功能

学习目标

  • 掌握SpringBoot中MVC功能的定制和扩展

  • 掌握SpringBoot整合Servlet三大组件的实现

  • 掌握SpringBoot文件上传与下载的实现

通常再Web开发中,会涉及静态资源的访问支持,视图解析器的配置,转换器和格式化器的定制,文件上传下载等功能,甚至还需要考虑到与web服务器关联的Servlet相关组件的定制。SpringBoot框架支持整合一些常用web框架,从而实现web开发,并默认支持web开发中的一些通用功能。本章将对SpringBoot实现web开发中涉及的一些常用功能进行详细讲解。

5.1 SpringMVC的整合支持

为了实现并简化web开发,SpringBoot为一些常用的web开发框架提供了整合支持,例如SpringMVC,SpringWebFlux等框架。使用SpringBoot进行web开发时,只需要再项目中引入独赢web开发框架的依赖启动器即可。那么,SpringBoot再整合一些web框架时实现了那些默认自动化配置,同时,怎样进行web功能扩展,本节就以SpringBoot整合SpringMVC框架实现web开发为例进行讲解。

5.1.1 SpringMVC自动配置介绍

再SpringBoot项目中,一旦引入了web依赖启动器spring-boot-starter-web,那么SpringBoot整合SpringMVC框架默认实现的一些xxxAutoConfiguration自动配置类就会自动生效,几乎可以在无任何额外配置的情况下进行web开发。SpringBoot为整合SpringMVC框架实现web开发,主要提供了一i西安自动化配置的功能特性:

  1. 内置了两个视图解析器:ContentNegotiatingViewResolver和BeanNameViewResolver。

  2. 支持静态资源以及WebJars。

  3. 自动注册了转换器和格式化器

  4. 支持http消息转换器

  5. 自动注册了消息代码解析器

  6. 支持静态项目首页index.html

  7. 支持定制应用图标favicon.ico

  8. 自动初始化web数据绑定器ConfigurableWebBindingInitializer

SpringBoot整合SpringMVC进行web开发时提供了很多默认配置,而且大多数时候使用默认配置即可满足开发需求。例如,SpringBoot整合SpringMVC进行web开发时,不需要额外配置视图解析器。

5.1.2 SpringMVC功能扩展实现

SpringBoot整合SpringMVC进行web开发时提供了很多的自动化配置,但在实际开发中还需要开发者对一些功能进行扩展实现。下面我们通过一个具体的案例讲解SpringBoot整合SpringMVC框架实现web开发的扩展功能。

  1. 项目基础环境搭建

    使用SpringInitializr方式创建项目,并在依赖选择中选择web模块下的web依赖启动器和TemplateEngines模块下的Thymeleaf依赖启动器

为了更直观快速的通过页面查看整合效果,我们将上一个项目中的页面和配置信息都拷贝过来,并通过访问请求地址http://localhost:8080/toLoginPage 访问登录页面。

2.功能扩展实现

接下俩使用SpringBoot整合SpringMVC进行web开发,实现简单的页面跳转功能,这里我们将使用SpringBoot提供的WebMvcConfigurer接口编写自定义配置,并对web功能进行适当扩展。

注册视图管理器

在项目的config包中创建一个实现WebMvcConfigurer接口的配置类,用于对mvc框架功能进行扩展

java 复制代码
package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * 实现WebMvcConfigurer接口,扩展MVC功能
 */
@Configuration
public class MyMVCconfig implements WebMvcConfigurer {

    // 添加视图管理
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 请求toLoginPage映射路径或者login.html页面都会自动映射到login.html页面
        registry.addViewController("/toLoginPage").setViewName("login");
        registry.addViewController("/login.html").setViewName("login");
    }



}

接下来需要将控制器代码进行注释掉,再重新运行项目,可以实现通过浏览器地址访问/toLoginPage或者/login.html地址都能跳转到login.html页面

通过访问可以看到可以正常访问页面,但是页面上的年份却获取不到,那是因为年份原本是通过控制器经过参数传递给页面的,但是我们现在的请求并没有通过控制器,因此年份这个参数获取不到。因此以上这种请求方式只适合较为简单的无参数视图get方式的请求跳转,对于有参数或需要业务处理的跳转需求,最好还是采用控制器的方式处理请求。

注册自定义拦截器

WebMvcConfigurer接口提供了许多MVC开发相关方法,例如,添加拦截器方法addInterceptors(),添加格式化器方法addFormatters()等。接下来我们使用WebMvcConfigurer接口中的addInterceptors()方法注册自定义拦截器。

在项目的config包中创建一个自定义拦截器类,并编写拦截业务代码

java 复制代码
package com.example.demo.config;

import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;

/**
 * 自定义一个拦截器类
 */
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        // 用户请求/admin开头路径时,判断用户是否登录
        String uri = request.getRequestURI();
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (uri.startsWith("/admin") && null == loginUser) {
            response.sendRedirect("/toLoginPage");
            return false;
        }
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        request.setAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse
            response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

然后在MyMVCconfig类中重写addInterceptors()方法注册自定义的拦截器

java 复制代码
@Autowired
    private MyInterceptor myInterceptor;
    // 添加拦截器管理
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor)
                .addPathPatterns("/**")   //拦截所有请求
                .excludePathPatterns("/login.html"); //对于/login.html请求地址进行放行,不进行拦截
    }

将项目进行重启运行,当我们访问/admin地址的时候,会被拦截器进行拦截,拦截器通过重定向修改请求地址访问/toLoginPage,并在跳转请求的同时调用了postHandle()方法,将年份参数发送给页面

当我们访问请求地址是/login.html的时候,拦截器没有拦截,而是允许访问,则会直接进入MyMVCconfig的addViewControllers方法,进行访问网页,那这时候并没有获得年份参数数据,因此这个请求页面上是不会显示年份数据信息。

5.2 SpringBoot整合Servlet三大组件

进行Servlet开发时,首先自定义Servlet、Filter、Listener三大组件,然后再文件web.xml中进行配置,而SpringBoot使用的时内嵌式Servlet容器,没有提供外部配置文件web.xml,那么SpringBoot时如何整合Servlet的相关组件呢?SpringBoot提供了组件注册和路径扫描两种方式整合Servlet三大组件,接下来我们分别对这两种整合方式进行详细讲解。

5.2.1 组件注册整合Servlet三大组件

再SpringBoot中,使用组件注册方式整合内嵌Servlet容器的Servlet、Filter、Listener三大组件时,只需将这些自定义组件通过ServletRegistrationBean,FilterRegistrationBean、ServletListenerRegistrationBean类注册到容器中即可。

  1. 使用组件注册方式整合Servlet

    创建自定义Servlet类。在项目中创建servletComponent包,在该包下创建继承至HttpServlet的类MyServlet

java 复制代码
package com.example.demo.servletComponent;

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;
/**
 * 自定义Servlet类
 */
//@WebServlet("/annotationServlet")
@Component
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getWriter().write("hello MyServlet");
    }
}

使用@Component注解将MyServlet类作为组件注入Spring容器。MyServlet类继承至HttpServlet,通过HttpServletResponse对象向页面输出"hello MyServlet"。

创建Servlet组件配置类。在项目config包下创建一个Servlet组件配置类ServletConfig,用来对Servlet相关组件进行注册。

java 复制代码
package com.example.demo.config;
import com.example.demo.servletComponent.MyServlet;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

/**
 * 嵌入式Servlet容器三大组件配置
 */
@Configuration
public class ServletConfig {
    // 注册Servlet组件
    @Bean
    public ServletRegistrationBean getServlet(MyServlet myServlet){
        ServletRegistrationBean registrationBean =
                new ServletRegistrationBean(myServlet,"/myServlet");
        return registrationBean;
    }

}

使用@Configuration注解将ServletConfig标注为配置类,ServletConfig类内部的getServlet()方法用于注册自定义的MyServlet,并返回ServletRegistrationBean对象。启动项目测试,在浏览器上访问/myServlet请求地址,浏览器上能显示MyServlet类返回的文本信息。

2.使用组件注册方式整合Filter

创建自定义Filter类,在servletComponent包下创建一个类MyFilter

java 复制代码
package com.example.demo.servletComponent;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;
/**
 *  自定义Filter类
 */
//@WebFilter(value = {"/antionLogin","/antionMyFilter"})
@Component
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("hello MyFilter");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {

    }
}

使用@Component注解将当前MyFilter类作为组件注入到Spring容器中。MyFilter类实现了Filter接口,并重写了init(),doFilter()和destroy()方法,在doFilter()方法中向控制台打印了"hello MyFilter"字符串。

向Servlet组件配置类注册自定义Filter类。打开之前创建的ServletConfig,将该自定义Filter类使用组件注册方式进行注册

java 复制代码
// 注册Filter组件
@Bean
public FilterRegistrationBean getFilter(MyFilter filter){
    FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
    registrationBean.setUrlPatterns(Arrays.asList("/toLoginPage","/myFilter"));
    return registrationBean;
}

上述代码中,使用组件注册方式注册自定义的MyFilter类。在getFilter(MyFilter filter)方法中,使用setUrlPatterns(Arrays.asList("/toLoginPage","/myFilter"))方法定义了过滤的请求为/toLoginPage和/myFilter,同时使用@Bean注解将当前组装好的FilterRegistrationBean对象作为Bean组件返回。接下来可以运行项目访问以上两个请求地址,因为过滤器中并没有对以上两个请求做处理,因此浏览器上会报404错误,这里不用关注,我们只需要观察控制台是否有打印过滤器中的输出信息即可。

3.使用组件注册方式整合Listener

创建自定义Listener类,在servletComponent包下创建一个类MyListener。

java 复制代码
package com.example.demo.servletComponent;

import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 *  自定义Listener类
 */
@Component
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("contextInitialized ...");
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("contextDestroyed ...");
    }
}

使用@Component注解将MyListener类作为组件注册到Spring容器中。MyListener类实现了ServletContextListener接口,并重写了contextInitialized()和contextDestroyed()方法。需要说明的是,Servlet容器提供了很多Listener接口,我们在自定义的时候根据项目的需求实现对应的接口即可。

向Servlet组件配置类注册自定义Listener类。打开之前创建的ServletConfig,将自定义的MyListener注册。

java 复制代码
//注册Listener组件
@Bean
public ServletListenerRegistrationBean getServletListener(MyListener myListener){
    ServletListenerRegistrationBean registrationBean=new ServletListenerRegistrationBean(myListener);
    return registrationBean;
}

接下来启动项目,可以在控制台中查看到监听器初始化的文字显示,"contextInitialized ...",再点击退出按钮的时候,控制台会显示contextDestroyed ...

注意,如果直接点击红色按钮停止项目服务器,则不会打印,项目直接被强制关闭了。

5.2.2 路径扫描整合Servlet三大组件

在SpringBoot中,使用路径扫描的方式整合内嵌式Servlet容器的Servlet,Filter,Listener三大组件时,首先需要在自定义组件上分别添加@WebServlet,@WebFilter和@WebListener注解进行声明,并配置相关注解属性,然后再仙姑主程序启动类上使用@ServletComponentScan注解开启组件扫描即可。

在使用注解方式注册三大组件之前,我们需要将之前创建的ServletConfig类全部注释掉,避免互相干扰。并且需要将自定义的三大组件中的@Component注解进行注释换成@WebServlet,@WebFilter和@WebListener。

java 复制代码
package com.example.demo.servletComponent;

import org.springframework.stereotype.Component;

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;
/**
 * 自定义Servlet类
 */
@WebServlet("/annotationServlet")
//@Component
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getWriter().write("hello MyServlet");
    }
}

运行项目,在浏览器上请求访问"/annotationServlet"地址的时候,可以在浏览器上显示出"hello MyServlet"

java 复制代码
package com.example.demo.servletComponent;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
 *  自定义Filter类
 */
@WebFilter(value = {"/antionLogin","/antionMyFilter"})
//@Component
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("hello MyFilter");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {

    }
}

重新运行项目,会发现浏览器上请求访问"/antionLogin","/antionMyFilter"这两个地址的时候会被过滤器拦截,并在控制台打印出"hello MyFilter"。

java 复制代码
package com.example.demo.servletComponent;

import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 *  自定义Listener类
 */
@WebListener
//@Component
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("contextInitialized ...");
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("contextDestroyed ...");
    }
}

重新运行项目,效果和注册监听器的方式的结果是一样的。

5.3 文件上传与下载

5.3.1 文件上传

开发Web应用时,文件上传时很常见的一个需求,浏览器通过表单形式将文件以流的形式传递给服务器,服务器再对上传的数据解析处理。虾米那我通过一个案例讲解如何使用SpringBoot实现文件上传。

  1. 编写文件上传表单页面

    在项目的templates模板引擎文件夹下创建一个用来上传文件的upload.html模板页面

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>动态添加文件上传列表</title>
    <link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
    <script th:src="@{/login/js/jquery.min.js}"></script>
</head>
<body>
<div th:if="${uploadStatus}" style="color: red" th:text="${uploadStatus}">上传成功</div>
<form th:action="@{/uploadFile}" method="post" enctype="multipart/form-data">
    上传文件:&nbsp;&nbsp;<input type="button" value="添加文件" onclick="add()"/>
    <div id="file" style="margin-top: 10px;" th:value="文件上传区域">  </div>
    <input id="submit" type="submit" value="上传"
           style="display: none;margin-top: 10px;"/>
</form>
<script type="text/javascript">
    // 动态添加上传按钮
    function add(){
        var innerdiv = "<div>";
        innerdiv += "<input type='file' name='fileUpload' required='required'>" +
            "<input type='button' value='删除' onclick='remove(this)'>";
        innerdiv +="</div>";
        $("#file").append(innerdiv);
        // 打开上传按钮
        $("#submit").css("display","block");
    }
    // 删除当前行<div>
    function remove(obj) {
        $(obj).parent().remove();
        if($("#file div").length ==0){
            $("#submit").css("display","none");
        }
    }
</script>
</body>
</html>

2.在全局配置文件中添加文件上传的相关配置

在全局配置文件application.properties中添加文件上传的相关设置

java 复制代码
# 单个上传文件大小限制(默认为1MB)
spring.servlet.multipart.max-file-size=10MB
# 总上传文件大小限制(默认为10MB)
spring.servlet.multipart.max-request-size=50MB

如果上传文件超出了限制,则会报错。

3.进行文件上传处理,实现文件上传功能

在controller包下创建一个管理上传下载的控制类FileController,用于实现文件上传功能

java 复制代码
package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.UUID;
/**
 * 文件管理控制类
 */
@Controller
public class FileController {
    // 向文件上传页面跳转
    @GetMapping("/toUpload")
    public String toUpload(){
        return "upload";
    }
    // 文件上传管理
    @PostMapping("/uploadFile")
    public String uploadFile(MultipartFile[] fileUpload, Model model) {
        // 默认文件上传成功,并返回状态信息
        model.addAttribute("uploadStatus", "上传成功!");
        for (MultipartFile file : fileUpload) {
            // 获取文件名以及后缀名
            String fileName = file.getOriginalFilename();
            // 重新生成文件名(根据具体情况生成对应文件名)
            fileName = UUID.randomUUID()+"_"+fileName;
            // 指定上传文件本地存储目录,不存在需要提前创建
            String dirPath = "E:/file/";
            File filePath = new File(dirPath);
            if(!filePath.exists()){
                filePath.mkdirs();
            }
            try {
                file.transferTo(new File(dirPath+fileName));
            } catch (Exception e) {
                e.printStackTrace();
                // 上传失败,返回失败信息
                model.addAttribute("uploadStatus","上传失败: "+e.getMessage());
            }
        }
        // 携带上传状态信息回调到文件上传页面
        return "upload";
    }
}

toUpload()方法用于处理路径为"/toUpload"的GET请求,并返回上传页面的路径。uploadFile()方法用于处理路径为"/uploadFile"的POST请求,如果文件上传成功,则会将上传的文件重命名并存储在"E:/file/"目录。如果上传失败,则会提示上传失败的相关信息。需要注意的是,uploadFile()方法的参数fileUpload的名称必须与上传页面中的input的name值一致。

4.效果测试

启动项目,项目启动成功后,在浏览器上访问"http://localhost:8080/toUpload"

单击添加文件的按钮,能够动态添加多个文件

成功了

添加文件进行上传

5.3.2 文件下载

下载文件能够通过IO流实现,所以多数框架并没有对文件下载进行封装处理。文件下载时涉及不同浏览器的解析处理,可能会出现中文乱码的情况。接下来我们分别针对下载英文名文件和中文名文件进行讲解。

  1. 英文名文件下载

    添加文件下载工具依赖。再pom.xml文件中引入文件下载的一个工具依赖commons-io

XML 复制代码
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

定制文件下载页面,再项目的templates文件夹下创建一个演示文件下载的download.html模板页面

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
<div style="margin-bottom: 10px">文件下载列表:</div>
<table>
    <tr>
        <td>bloglogo.jpg</td>
        <td><a th:href="@{/download(filename='bloglogo.jpg')}">下载文件</a></td>
    </tr>
    <tr>
        <td>Spring Boot应用级开发教程.pdf</td>
        <td><a th:href="@{/download(filename='Spring Boot应用级开发教程.pdf')}">
            下载文件</a></td>
    </tr>
</table>
</body>
</html>

需要注意的是,在文件下载之前,需要保证在文件下载目录(本示例中的E:/file/)中存在文件bbb.jpg和计算机文化与组装.pptx(这只是两个测试文件而已,读者演示时可以自行存放,只要保持文件名统一即可)

编写文件下载处理方法,在之前创建的文件管理控制类FileController中编写文件下载的处理方法

java 复制代码
// 向文件下载页面跳转
@GetMapping("/toDownload")
public String toDownload(){
    return "download";
}
// 文件下载管理
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(String filename){
    // 指定要下载的文件根路径
    String dirPath = "F:/file/";
    // 创建该文件对象
    File file = new File(dirPath + File.separator + filename);
    // 设置响应头
    HttpHeaders headers = new HttpHeaders();
    // 通知浏览器以下载方式打开
    headers.setContentDispositionFormData("attachment",filename);
    // 定义以流的形式下载返回文件数据
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    try {
        return new ResponseEntity<>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK);
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<byte[]>(e.getMessage().getBytes(),HttpStatus.EXPECTATION_FAILED);
    }
}

上述代码中,toDownload()方法用来处理"/toDownload"的Get请求,并跳转到download.html页面,fileDownload()方法用来处理"/download"的Get请求并进行文件下载处理,下载的数据类型是ResponseEntity<byte[]>

效果测试,实现上述文件下载功能后,启动项目,项目启动成功后,在浏览器上访问(http://localhost:8080/toDownload)进入下载页面,这里先下载英文文件名的文件,点击下载文件,以IE浏览器为例

2.中文名文件下载

在上一步所示的文件下载页面中,单击第2个中文名文件"计算机文化与组装.pptx"后面的下载文件进行下载,对文件名进行下载时,虽然可以成功下载,但是下载后的文件中文名称统一变成了"_",这显然时不理想的,因此还需要对中文名文件下载进行额外处理,在FileController类的fileDownload()方法中添加处理中文编码的代码,修改后的代码如下

java 复制代码
// 所有类型文件下载管理
    @GetMapping("/download")
    public ResponseEntity<byte[]> fileDownload(HttpServletRequest request,
                                               String filename) throws Exception{
        // 指定要下载的文件根路径
        String dirPath = "E:/file/";
        // 创建该文件对象
        File file = new File(dirPath + File.separator + filename);
        // 设置响应头
        HttpHeaders headers = new HttpHeaders();
        // 通知浏览器以下载方式打开(下载前对文件名进行转码)
        filename=getFilename(request,filename);
        headers.setContentDispositionFormData("attachment",filename);
        // 定义以流的形式下载返回文件数据
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        try {
            return new ResponseEntity<>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<byte[]>(e.getMessage().getBytes(),HttpStatus.EXPECTATION_FAILED);
        }
    }
//     根据浏览器的不同进行编码设置,返回编码后的文件名
    private String getFilename(HttpServletRequest request, String filename)
            throws Exception {
        // IE不同版本User-Agent中出现的关键词
        String[] IEBrowserKeyWords = {"MSIE", "Trident", "Edge"};
        // 获取请求头代理信息
        String userAgent = request.getHeader("User-Agent");
        for (String keyWord : IEBrowserKeyWords) {
            if (userAgent.contains(keyWord)) {
                //IE内核浏览器,统一为UTF-8编码显示,并对转换的+进行更正
                return URLEncoder.encode(filename, "UTF-8").replace("+"," ");
            }
        }
        //火狐等其它浏览器统一为ISO-8859-1编码显示
        return new String(filename.getBytes("UTF-8"), "ISO-8859-1");
    }

上述代码中,getFilename()方法用来根据不同浏览器对下载的中文名进行转码。其中"User-Agent"用于获取用户下载文件的浏览器内核信息,不同版本的IE浏览器内核可能不同,需要特别查看,如果内核信息时IE则转码为UTF-8,其他浏览器转码为ISO-8859-1即可。

本章小结

本章主要讲解了SpringBoot框架整合SpringMVC实现web开发过程中的一些功能,包括有MVC功能扩展和定制,Servlet三大组件定制,文件上传与下载以及SpringBoot项目的打包和部署。学习完本章后,要充分掌握SpringBoot进行web开发中主要功能的一些配置和扩展,能够完成实际开发中SpringBoot项目的开发和部署工作。

习题

一、填空题

  1. SpringBoot项目中定制SpringMVC的扩展功能,需要提供实现()接口的配置类。

  2. WebMvcConfigurer接口中的()方法可以定制视图管理。

  3. WebMvcConfigurer接口中的()方法可以定制自定义的拦截器。

  4. SpringBoot中使用路径扫描方式整合Servlet组件时,需要用()注解开启组件扫描。

  5. SpringBoot整合SpringMVC实现文件上传时,默认单个文件上传大小限制为()。

二、判断题

  1. SpringBoot为整合SpringMVC实现web开发提供了欢迎页index.html支持。()

  2. SpringBoot中实现SpringMVC的扩展功能,要提供实现WebMvcConfigurer接口的配置类,并开启@EnableWebMvc注解。()

  3. SpringBoot中整合Servlet的Listener组件时,在自定义Listener上添加@Component即可生效。()

  4. SpringBoot整合SpringMVC实现中文名文件下载时,针对IE内核浏览器需要转码为UTF-8。()

  5. SpringBoot提供的打包插件spring-boot-maven-plugin可以将项目打成Jar包和War包。()

三、选择题

  1. SpringBoot为整合SpringMVC实现web开发,提供的功能特性不包括()

    A.配置视图解析器

    B.对WebJars的支持

    C.对拦截器的自动配置

    D.对HttpMessageConverters消息转换器的支持

  2. SpringBoot整合Servlet组件涉及的注册Bean组件有()。(多选)

    A.ServletRegistrationBean

    B.InterceptorRegistrationBean

    C.FilterRegistrationBean

    D.ServletListenerRegistrationBean

  3. SpringBoot中使用路径扫描的方式整合内嵌式Servlet组件时,需要使用的注解有()。(多选)

    A.@WebFilter

    B.@ServletComponentScan

    C.@WebListener

    D.@WebInterceptor

  4. 下列关于SpringBoot整合SpringMVC实现文件上传及下载的说法中,正确的是()

    A.必须使用spring.servlet.multipart.max-file-size来设置单个上传文件的大小限制

    B.处理上传文件方法中,可以使用List<MultipartFile>类型的参数来接收处理单个或多个上传文件

    C.文件上传存储目录"F:/file/"需要提前创建好

    D.对中文文件进行下载时,如果没有进行中文转换,下载的中文文件内容会出现乱码

  5. 下列关于SpringBoot项目War包方式打包部署的说法中,错误的是()

    A.必须使用<packaging>标签将SpringBoot项目默认的Jar包方式修改为War

    B.需要将spring-boot-starter-tomcat使用<scope>provided</scope>声明为已提供provided

    C.必须让主程序启动类继承SpringBootServletInitializer类并实现cinfigure()方法

    D.以War包方式部署项目进行访问,必须在访问路径上添加打包后的项目

相关推荐
小岛前端3 分钟前
Node.js 宣布重大调整,运行十年的规则要改了!
前端·node.js
OpenTiny社区4 分钟前
OpenTiny NEXT-SDK 重磅发布:四步把你的前端应用变成智能应用
前端·javascript·ai编程
梦想CAD控件20 分钟前
在线CAD开发包结构与功能说明
前端·javascript·vue.js
张拭心25 分钟前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
时光不负努力25 分钟前
typescript常用的dom 元素类型
前端·typescript
小怪点点30 分钟前
大文件切片上传
前端
子玖31 分钟前
go实现通过ip解析城市
后端·go
时光不负努力31 分钟前
TS 常用工具类型
前端·javascript·typescript
SuperEugene33 分钟前
Vue状态管理扫盲篇:Vuex 到 Pinia | 为什么大家都在迁移?核心用法对比
前端·vue.js·面试