Spring MVC的高级功能以及文件的上传和下载

学习视频:13001 简单异常处理器_哔哩哔哩_bilibili

目录

1.异常处理

简单异常处理

自定义异常处理器

异常处理注解

2.拦截器

什么是拦截器

配置自定义拦截器

拦截器的执行流程

单个拦截器的执行流程

多个拦截器的执行流程

3.案例:后台系统登录验证

4.文件的上传和下载

文件上传表单的满足条件

MultipartResolver接口

MultipartFile接口的常用方法

​编辑

文件下载

案例:文件上传和下载

文件上传

文件下载


1.异常处理

简单异常处理

HandlerExceptionResolver接口

        如果希望对Spring MVC中所有异常进行统一处理,可以使用Spring MVC提供的异常处理器HandlerExceptionResolver接口。Spring MVC内部提供了HandlerExceptionResolver的实现类SimpleMappingExceptionResolver。它实现了简单的异常处理,通过该实现类可以将不同类型的异常映射到不同的页面,当发生异常的时候,实现类根据发生的异常类型跳转到指定的页面处理异常信息。实现类也可以为所有的异常指定一个默认的异常处理页面,当应用程序抛出的异常没有对应的映射页面,则使用默认页面处理异常信息。

@Controller
public class ExceptionController {

    //空指针异常
    @RequestMapping("/showNullPointer")
    public void showNullPointer() {
        ArrayList<Object> list = new ArrayList<>();
        System.out.println(list.get(2));	}
    // 抛出IO异常
    @RequestMapping("/showIO")
    public void showIO() throws IOException
    {
        FileInputStream inputStream=new FileInputStream("abc.xml");
    }
    //默认异常
    @RequestMapping("/showDefault")
    public void showDefault()
    {
        int i=1/0;
    }


}
 <!--    注解扫描-->
    <context:component-scan base-package="com.it.controller"></context:component-scan>
    <!--支持json-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!--配置静态资源的访问-->
    <mvc:resources mapping="/js/**" location="/js/" />

    <!--       释放静态资源-->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>

   <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!-- 定义特殊处理的异常,类名或完全路径名作为key,对应的异常页面名作为值-->
    <property name="exceptionMappings">
        <!--配置异常和页面的映射关系-->
        <props>
            <prop key="java.lang.NullPointerException">
            nullPointerExp.jsp
            </prop>
            <prop key="java.io.IOException">
                IOExp.jsp
            </prop>
        </props>
    </property>
       <!--默认异常处理页面-->
        <property name="defaultErrorView" value="defaultExp.jsp"></property>
        <!--配置异常变量名-->
       <property name="exceptionAttribute" value="exp"></property>
   </bean>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>空指针异常处理页面</title>
</head>
<body>
空指针异常处理页面-----${exp}
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>默认异常处理页面</title>
</head>
<body>
默认异常处理页面-----${exp}
</body>
</html>


自定义异常处理器

resolveException()方法

          除了使用SimpleMappingExceptionResolver进行异常处理,还可以自定义异常处理器统一处理异常。通过实现HandlerExceptionResolver接口,重写异常处理方法resolveException()来定义自定义异常处理器。当Handler执行并且抛出异常时,自定义异常处理器会拦截异常并执行重写的resolveException()方法,该方法返回值是ModelAndView类型的对象,可以在ModelAndView对象中存储异常信息,并跳转到异常处理页面。

public class MyException  extends Exception{
    private String message;

    public MyException(String message) {
        super(message);
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
@Controller
public class ExceptionController {

    //空指针异常
    @RequestMapping("/showNullPointer")
    public void showNullPointer() {
        ArrayList<Object> list = new ArrayList<>();
        System.out.println(list.get(2));	}
    // 抛出IO异常
    @RequestMapping("/showIO")
    public void showIO() throws IOException
    {
        FileInputStream inputStream=new FileInputStream("abc.xml");
    }
    //默认异常
    @RequestMapping("/showDefault")
    public void showDefault()
    {
        int i=1/0;
    }

    @RequestMapping("/addData")
    public void addData() throws MyException
    {
        throw new MyException("新增数据异常");
    }


}
@Component//创建对象
public class MyExceptionHandler implements HandlerExceptionResolver {
    /**
     *
     * @param httpServletRequest 请求对象
     * @param httpServletResponse 响应对象
     * @param o 处理器
     * @param e 异常对象
     * @return
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

        String msg="";
        if (e instanceof MyException) {
            //自定义异常,直接告诉用户错误信息
            msg=e.getMessage();
        }else {
            //未知的系统异常
            Writer out = new StringWriter();
            PrintWriter s = new PrintWriter(out);
            e.printStackTrace(s);
            String sysMsg = out.toString();// 系统真实异常信息
            //给到用户的信息进行转换
            msg="网络异常";

        }
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error.jsp");
        modelAndView.addObject("msg", msg);



        return modelAndView;
    }

}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>异常处理页面</title>
</head>
<body>
    ${msg}
</body>
</html>


异常处理注解

@ControllerAdvice注解的作用

        从Spring 3.2开始,Spring 提供了一个新注解@ControllerAdvice, @ControllerAdvice有以下两个作用。

  •          注解作用在类上时可以增强Controller对Controller中被@RequestMapping注解标注的方法加一些逻辑处理。    
  •         @ControllerAdvice注解结合方法型注解@ExceptionHandler,可以捕获Controller中抛出的指定类型的异常,从而实现不同类型的异常统一处理

代码演示

@ControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(MyException.class)
    public ModelAndView doMyException(MyException e)
    {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error.jsp");
        modelAndView.addObject("msg", e.getMessage());
        return modelAndView;

    }

    @ExceptionHandler(Exception.class)
    public ModelAndView doOtherException(Exception e)
    {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error.jsp");
        modelAndView.addObject("msg", "网络异常");
        return modelAndView;

    }


}


2.拦截器

什么是拦截器

         拦截器(Interceptor)是一种动态拦截Controller方法调用的对象,它可以在指定的方法调用前或者调用后,执行预先设定的代码。拦截器作用类似于Filter(过滤器),但是它们的技术归属和拦截内容不同。Filter采用Servlet技术,拦截器采用Spring MVC技术;Filter会对所有的请求进行拦截,拦截器只针对Spring MVC的请求进行拦截。

拦截器的定义方式

        在Spring MVC 中定义一个拦截器非常简单,常用的拦截器定义方式有以下两种。

  •             第一种方式是通过实现HandlerInterceptor 接口定义拦截器。  
  •             第二种方式是通过继承HandlerInterceptor 接口的实现类HandlerInterceptorAdapter,定义拦截器。

         上述两种方式的区别在于,直接实现HandlerInterceptor接口需要重写HandlerInterceptor接口的所有方法;而继承HandlerInterceptorAdapter类的话,允许只重写想要回调的方法。        

自定义拦截器示例

preHandler()方法

          preHandler()方法用于对程序进行安全控制、权限校验等,它会在控制器方法调用前执行。preHandler()方法的参数request是请求对象,response是响应对象,handler是被调用的处理器对象。        

         preHandler()方法的返回值为bool类型,表示是否中断后续操作。当返回值为true时,表示继续向下执行;当返回值为false时,整个请求就结束了,后续的所有操作都会中断(包括调用下一个拦截器和控制器类中的方法执行等)。

postHandle()方法

        postHandle()方法用于对请求域中的模型和视图做出进一步的修改,它会在控制器方法调用之后且视图解析之前执行。      

       postHandle()方法的前2个参数和preHandler()方法的前2个参数一样,分别是请求对象响应对象。如果处理器执行完成有返回结果,可以通过第3个参数modelAndView读取和调整返回结果对应的数据与视图信息。

afterCompletion()方法

        afterCompletion()方法可以完成一些资源清理、日志信息记录等工作,它会在整个请求完成后执行,即视图渲染结束之后执行。    

       postHandle()方法的前2个参数和preHandler()方法的前2个参数一样,分别是请求对象和响应对象。第3个参数ex是异常对象,如果处理器执行过程中出现异常,会将异常信息封装在该异常对象中,可以在afterCompletion()方法中针对异常情况进行单独处理。需要注意的是,只有在preHandler()方法的返回值为true时,postHandle()方法和afterCompletion()方法才会按上述执行规则执行。

public class CustomInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("在handler之前执行preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("在handler之后执行postHandle,但是在视图渲染之前");

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("在视图渲染之后执行afterCompletion");
    }
}

配置自定义拦截器


@Controller
public class DemoController {
    @RequestMapping("/demo1")
    public String demo1(){
        System.out.println("demo1 running");
        return "success.jsp";
    }

    @RequestMapping
    public String demo2(){
        System.out.println("demo2 running");
        return "success.jsp";
    }
}
    <mvc:interceptors><!-- 配置拦截器 -->
        <!--拦截所有请求-->

        <mvc:interceptor>
            <mvc:mapping path="/demo1"/> <!-- 配置拦截器作用的路径 -->
            <!-- 对匹配路径的请求才进行拦截-->
            <bean class="com.it.interceptor.CustomInterceptor" />
        </mvc:interceptor></mvc:interceptors>
public class CustomInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("在handler之前执行preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("在handler之后执行postHandle,但是在视图渲染之前");

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("在视图渲染之后执行afterCompletion");
    }
}

依次访问demo1,demo2


拦截器的执行流程

单个拦截器的执行流程

单个拦截器的执行流程分析

         从单个拦截器的执行流程图中可以看出,程序收到请求后,首先会执行拦截器中的preHandle()方法,如果preHandle()方法返回的值为false,则将中断后续所有代码的执行。         如果preHandle()方法的返回值为true,则程序会继续向下执行Handler的代码。当Handler执行过程中没有出现异常时,接着会执行拦截器中的postHandle()方法。postHandle()方法执行后会通过DispatcherServlet向客户端返回响应,并且在DispatcherServlet处理完请求后,执行拦截器中的afterCompletion()方法;如果Handler执行过程中出现异常,将跳过拦截器中的postHandle()方法,直接由DispatcherServlet渲染异常页面返回响应,最后执行拦截器中的afterCompletion()方法。

<mvc:interceptors> 
<bean class="com.it.interceptor.MyInterceptor"></bean>
</mvc:interceptors>
@Controller
public class HelloController {

    @RequestMapping("/hello")
    public String sayHello() {
        System.out.println("handler执行了...");
        return "success.jsp";
    }
    @RequestMapping("/exp")
    public String exp()
    {
        System.out.println(1/0);
        return "success.jsp";
    }
}
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle running");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle running");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion running");
    }
}

这时候访问exp

由此可见先访问preHandle然后访问Handle,但是有异常,会直接到afterCompletion,省略了postHandle


多个拦截器的执行流程

多个拦截器的执行流程分析

         从多个拦截器的执行流程图中可以看出,当有程序中配置了多个拦截器时,拦截器中的preHandle()方法会按照配置文件中拦截器的配置顺序执行,而拦截器中的postHandle()方法和afterCompletion()方法则会按照拦截器的配置顺序的相反顺序执行。

  <mvc:interceptors><bean class="com.it.interceptor.MyInterceptor"></bean>
        <bean class="com.it.interceptor.MyInterceptor2"></bean>
</mvc:interceptors>

 @RequestMapping("/hello")
    public String sayHello() {
        System.out.println("handler执行了...");
        return "success.jsp";
    }
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle running");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle running");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion running");
    }
}
public class MyInterceptor2  implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle2 running");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle2 running");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion2 running");
    }
}

可以看出相应的执行顺序


3.案例:后台系统登录验证

案例要求

        本案例主要是对用户登录状态的验证,只有登录成功的用户才可以访问系统中的资源。为了保证后台系统的页面不能被客户直接请求访问,本案例中所有的页面都存放在项目的WEB-INF 文件夹下,客户需要访问相关页面时,需要在服务器端转发到相关页面。如果没有登录系统而直接访问系统首页,拦截器会将请求拦截,并转发到登录页面,同时在登录页面中给出提示信息。如果用户登录时提交的用户名或密码错误,也会在登录页面给出相应的提示信息。当已登录的用户在系统页面中单击“退出”链接时,系统同样会回到登录页面。

后台系统登录验证的流程图

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<html><head><title>后台系统</title></head>
<body>
<li>您好:${user.username }</li>
<li><a href="${ pageContext.request.contextPath }/logout">退出
</a></li>
<li><a href="${ pageContext.request.contextPath }/orderinfo">
    订单信息</a></li>
</body></html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<html><head><title>用户登录</title></head><body>
<form action="${pageContext.request.contextPath }/login"
      method="POST"><div>${msg}</div>
    用户名:<input type="text" name="username"/><br/>
    密&nbsp;&nbsp;码:
    <input type="password" name="password"/><br/>
    <input type="submit" value="登录"/>
</form></body></html>
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<html><head><title>订单信息</title></head><body>
您好:${ user.username }
<a href="${ pageContext.request.contextPath }/logout">退出</a>
<table border="1" width="80%">
    <tr align="center"><td colspan="2" >订单id:D001</td></tr>
    <tr align="center"><td>商品Id</td><td>商品名称</td></tr>
    <tr align="center"><td>P001</td><td>三文鱼</td></tr>
    <tr align="center"><td>P002</td><td>红牛</td></tr>
</table></body></html>
public class User {

    private String username;
    private String password;



    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
@Controller
public class UserController {

        @RequestMapping("/main")
        public String main()
        {
            return "main";

        }

        @RequestMapping("/toLogin")
        public String toLogin()
        {
            return "login";
        }

        @RequestMapping("/orderinfo")
        public String toOrderInfo()
        {
            return "orderinfo";
        }

        //登录
        @RequestMapping("login")
        public String login(User user, HttpSession session, Model model)
        {
            String username = user.getUsername();
            String password = user.getPassword();
            if (username!=null&&"zhangsan".equals(username)&&password!=null&&"123456".equals(password))
            {
                //登录成功,跳转到首页,并且保存用户信息
                session.setAttribute("user", user);
                return "redirect:/main";
            }else
            {
                model.addAttribute("msg","用户名或者密码错误");
                return "login";
            }

        }

        //退出成功,需要跳转到登录页面
        @RequestMapping("/logout")
        public String logOut(HttpSession session)
        {
            //清空session
            session.invalidate();

            return "login";
        }


}
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //针对登录和跳转到登录页面的请求进行放行
        String requestURI = request.getRequestURI();
        if (requestURI.indexOf("/login") >0||requestURI.indexOf("/tologin")>0)
        {
            //直接放行
            return true;
        }
        //其他请求需要做登录权限拦截判断
        HttpSession session = request.getSession();
        Object user = session.getAttribute("user");
        if (user!=null)
        {
            //登录成功,放行
            return true;
        }
        //没有登录,跳转到登录页面
        request.setAttribute("msg","您还没有登录,请先登录");
        //这里得用不了视图解析器,这里是javaweb功能
        request.getRequestDispatcher("/WEB-INF/pages/login.jsp").forward(request, response);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
    <mvc:interceptors>
        <bean class="com.it.interceptor.LoginInterceptor"></bean>
    </mvc:interceptors>
       <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="suffix" value=".jsp"></property>
        <property name="prefix" value="/WEB-INF/pages/"></property>
    </bean>



    <!--    注解扫描-->
    <context:component-scan base-package="com.it.controller"></context:component-scan>
    <!--支持json-->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!--配置静态资源的访问-->
    <mvc:resources mapping="/js/**" location="/js/" />

    <!--       释放静态资源-->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>


4.文件的上传和下载

文件上传表单的满足条件

         大多数文件上传都是通过表单形式提交给后台服务器,因此,要实现文件上传功能,就需要提供一个文件上传的表单,并且该表单必须满足以下3个条件。

  •     form表单的method属性设置为post。
  •     form表单的enctype属性设置为multipart/form-data。    
  •     提供<input type="file" name="filename" />的文件上传输入框。

文件上传表单的示例代码

<form action="uploadUrl" method="post" enctype="multipart/form-data" >
    <input type="file" name="filename" multiple="multiple" />
    <input type="submit" value="文件上传" />
</form>

上述代码中的文件上传表单除了满足了必须的3个条件之外,还在文件上传输入框中增加了一个HTML5中的新属性multiple。如果文件上传输入框中使用了multiple属性,则在上传文件时,可以同时选择多个文件进行上传,即可实现多文件上传。 


MultipartResolver接口

         当客户端提交的form表单中enctype属性为multipart/form-data时,浏览器会采用二进制流的方式来处理表单数据,服务器端会对请求中上传的文件进行解析处理。        

         Spring MVC为文件上传提供了直接的支持,这种支持是通过MultipartResolver(多部件解析器)对象实现的。MultipartResolver是一个接口,可以使用MultipartResolver的实现类CommonsMultipartResolver来完成文件上传工作。

MultipartResolver接口的使用

<bean id="multipartResolver" class=
"org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置请求编码格式,必须与JSP中的pageEncoding属性一致,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="UTF-8" />
<!-- 设置允许上传文件的最大值为2M,单位为字节 -->
<property name="maxUploadSize" value="2097152" />
</bean>

<property>元素

<property>元素可以配置文件解析器类CommonsMultipartResolver的如下属性。           

  •   maxUploadSize:上传文件最大值(以字节为单位)。  
  •   maxInMemorySize:缓存中的最大值(以字节为单位)。    
  • defaultEncoding:默认编码格式。    
  • resolveLazily:推迟文件解析,以便在Controller中捕获文件大小异常。

配置CommonsMultipartResolver时指定Beanid

        因为初始化MultipartResolver时,程序会在BeanFactory中查找名称为multipartResolver的MultipartResolver实现类,如果没有查找到对应名称的MultipartResolver实现类,将不提供多部件解析处理。所以在配置CommonsMultipartResolver时必须指定该Bean的id为multipartResolver。

CommonsMultipartResolver实现上传功能,在内部调用了Apache Commons FileUpload的组件

<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.4</version>
</dependency>
@Controller
public class FileUploadController {
      @RequestMapping("/fileUpload ")
      public String fileUpload(MultipartFile file) {
            if (!file.isEmpty()) {// 保存上传的文件,filepath为保存的目标目录
	file.transferTo(new File(filePath))return "uploadSuccess";}
            return "uploadFailure";}}

MultipartFile接口的常用方法


文件下载

什么是文件下载

          文件下载就是将文件服务器中的文件传输到到本机上。进行文件下载时,为了不以客户端默认的方式处理返回的文件,可以在服务器端对所下载的文件进行相关的配置。配置的内容包括返回文件的形式、文件的打开方式、文件的下载方式和响应的状态码。其中,文件的打开方式可以通过响应头Content-Disposition的值来设定,文件的下载方式可以通过响应头Content-Type中设置的MIME类型来设定。

使用ResponseEntity对象进行文件下载

@RequestMapping("/download")
public ResponseEntity<byte[]> fileDownload(HttpServletRequest 	request,String filename) throws Exception{
    String path = request.getServletContext().getRealPath("/upload/");// 下载文件所在路径
    File file = new File(path+File.separator+filename); // 创建文件对象
    HttpHeaders headers = new HttpHeaders(); // 设置消息头
    headers.setContentDispositionFormData(“attachment”, filename);// 打开文件
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 下载返回的文件数据
    return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),
              headers,HttpStatus.OK); // 使用ResponseEntity对象封装返回下载数据
}

上面示例中,设置响应头信息中的MediaType代表的是Interner Media Type(即互联网媒体类型),也叫做MIME类型,MediaType.APPLICATION_OCTET_STREAM的值为application/octet-stream,即表示以二进制流的形式下载数据。HttpStatus类型代表的是Http协议中的状态,示例中的HttpStatus.OK表示200,即服务器已成功处理了请求。


案例:文件上传和下载

        接下来将文件上传和下载的相关知识结合起来,实现一个文件上传和下载的案例。在实现案例之前,首先分析案例的功能需求。本案例要实现的功能为,将文件上传到项目的文件夹下,文件上传成功后将上传的文件名称记录到一个文件中,并将记录的文件列表展示在页面,单击文件列表的链接实现文件下载。

  •  搭建文件上传和下载的环境。    
  • 实现文件上传功能。    
  • 实现获取文件列表功能。    
  • 编写文件上传和下载页面。    
  • 实现文件下载。

文件上传

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<table border="1"> <tr>
    <td width="200" align="center">文件上传${msg}</td>
    <td width="300" align="center">下载列表</td> </tr>
    <tr> <td height="100">
        <form action="${pageContext.request.contextPath}/fileload"
              method="post" enctype="multipart/form-data">
            <input type="file" name="files" multiple="multiple"><br/>
            <input type="reset" value="清空" />
            <input type="submit" value="提交"/>
        </form> </td>
        <td id="files"></td> </tr>
</table>

</body>
<script>
    $(document).ready(function(){
        var url="${pageContext.request.contextPath }/getFilesName";
        $.get(url,function (files) {
            var files = eval('(' + files + ')');
            for (var i=0;i<files.length;i++){
                $("#files").append("<li><a href=${pageContext.request.contextPath }"+
                    "\\"+"download?filename="+files[i].name+">"+
                    files[i].name+"</a></li>" );
            }
        })
    })
</script>

</html>
public class JSONFileUtils {
    //用来从files.json中读取信息
    public static String readFile(String filepath) throws Exception {
        FileInputStream fis = new FileInputStream(filepath);
        return IOUtils.toString(fis); }
    //用来向files.json文件中写入字符串
    public static void writeFile(String data,String filepath)  throws Exception {
        FileOutputStream fos = new FileOutputStream(filepath);
        IOUtils.write(data,fos); }
}

@Controller
public class FileController {

    //上传
    @RequestMapping("/fileload")
    public String fileUpload(MultipartFile[] files,HttpServletRequest request) throws Exception {
        //获取保存文件信息的所在文件的根目录的绝对路径
        String path=request.getServletContext().getRealPath("/")+"/files/";

        ObjectMapper om=new ObjectMapper();
        //判断files为空,如果为空,上传失败,否则才开始进行上传的操作
        if(files!=null&&files.length>0)
        {
            //开始文件上传操作
            //文件名的集合
            List<Resource> list=new ArrayList<>();
            //遍历每个文件
            for(MultipartFile file:files)
            {
                //每个文件都需要保存,但是需要判断名字
                //如果文件重名,我们在现有的名字的后面加(1) 例如a.jpg->a(1).jpg
                String filePath=path+"files.json";
                String fileNamesJson= JSONFileUtils.readFile(filePath);

                //获取当前文件名
                String filename=file.getOriginalFilename();
                if(fileNamesJson.length()>0)
                {
                    //有其他名字要判断名字
                    //先将json字符串转换成java对象集合
                    list=om.readValue(fileNamesJson,new TypeReference<List<Resource>>(){});
                    //判断
                    for(Resource resource:list)
                    {
                    if(resource.getName().equals(filename))
                    {
                        //如果文件重名
                        String[] split=filename.split("\\.");
                        filename=split[0]+"(1)."+split[1];

                    }

                    }

                }
                //保存文件,文件的位置应该是根目录加改后的名字
                file.transferTo(new File(path+filename));
                //文件名信息要保存到files.json中
                list.add(new Resource(filename));
                //转换成json
                fileNamesJson =om.writeValueAsString(list);
                //写入到文件
                JSONFileUtils.writeFile(fileNamesJson,path+"files.json");


            }

            request.setAttribute("msg","上传成功");
            return "forward:fileload.jsp";

        }
        request.setAttribute("msg","上传失败");
        return "forward:fileload.jsp";


    }



}

 


文件下载

中文乱码问题

        在实现文件下载的功能时,还需要注意文件中文名称的乱码问题。在使用Content-Disposition设置参数信息时,如果Content-Disposition中设置的文件名称出现中文字符,需要针对不同的浏览器设置不同的编码方式。目前Content-Disposition支持的编码方式有UrlEncode编码、Base64编码、RFC2231编码和ISO编码。本案例不对全部浏览器的编码方式进行设置,只对FireFox浏览器和非FireFox浏览器(如IE)分别进行编码设置。

//获取文件列表功能
    @RequestMapping(value = "/getFilesName",produces="text/html;charset=utf-8")
    @ResponseBody
    public String getFilesName(HttpServletRequest request) throws  Exception
    {
    String path=request.getServletContext().getRealPath("/")+"/files/files.json";
    String fileNamesJson=JSONFileUtils.readFile(path);
        return fileNamesJson;


    }

    @RequestMapping("/download")
    public ResponseEntity<byte[]> download(HttpServletRequest request,String filename) throws  Exception
    {
        String path=request.getServletContext().getRealPath("/files/");
        File file=new File(path+File.separator+filename);
        HttpHeaders httpHeaders=new HttpHeaders();
        filename=getFileName(request,filename);

        httpHeaders.setContentDispositionFormData("attachment",filename);
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),httpHeaders, HttpStatus.OK);



    }

    /**
     * 将文件转换为中文,解决下载文件名的中文乱码
     * @param request
     * @param filename
     * @return
     * @throws Exception
     */
    public String getFileName(HttpServletRequest request,
                              String filename) throws Exception {
        BASE64Encoder base64Encoder = new BASE64Encoder();
        String agent = request.getHeader("User-Agent");
        if (agent.contains("Firefox")) {// 火狐浏览器
            filename = "=?UTF-8?B?" + new String
                    (base64Encoder.encode(filename.getBytes("UTF-8"))) + "?=";
        } else {// IE及其他浏览器
            filename = URLEncoder.encode(filename, "UTF-8");	}
        return filename;	}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/js/jquery-3.4.1.min.js" type="text/javascript"></script>
</head>
<body>
<table border="1"> <tr>
    <td width="200" align="center">文件上传${msg}</td>
    <td width="300" align="center">下载列表</td> </tr>
    <tr> <td height="100">
        <form action="${pageContext.request.contextPath}/fileload"
              method="post" enctype="multipart/form-data">
            <input type="file" name="files" multiple="multiple"><br/>
            <input type="reset" value="清空" />
            <input type="submit" value="提交"/>
        </form> </td>
        <td id="files"></td> </tr>
</table>

</body>
<script>
    $(document).ready(function(){
        var url="${pageContext.request.contextPath }/getFilesName";
        $.get(url,function (files) {
            var files = eval('(' + files + ')');
            for (var i=0;i<files.length;i++){
                $("#files").append("<li><a href=${pageContext.request.contextPath }"+
                    "\\"+"download?filename="+files[i].name+">"+
                    files[i].name+"</a></li>" );
            }
        })
    })
</script>

</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小吴有想法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值