SpringMVC

第一章 文件下载

1.1 文件下载原理

对于文件下载,相信大家并不会陌生,因为通常在上网时所下的图片、文档和影片等都是文件下载的范畴。现在很多网站都提供了下载各类资源的功能,因此在学习Web开发过程中,有必要学习文件下载的实现方式。

实现文件下载功能比较简单,通常情况下,不需要使用第三方组件实现,而是直接使用输入/输出流实现即可。

1.2 不同浏览器对文件下载的不同

使用链接地址指向要下载的文件。此时浏览器会尽可能解析对应的文件,只要是能够在浏览器窗口展示的,就都会直接显示,而不是提示下载。

  1. <a href="download/1.docx">1.docx</a><br>
  2. <a href="download/1.pdf">1.pdf</a><br>
  3. <a href="download/1.txt">1.txt</a><br>
  4. <a href="download/d.jpg">d.jpg</a><br>

还是出现浏览器之间的差异问题,同一个文件对于不同浏览器的处理方式不同,有的浏览器直接解析,有的浏览器要求下载。

1.3 所有文件全部为下载方式

需要设置HTTP协议响应头,通知浏览器以附件的形式处理该文件,直接下载不要打开

//设定接收程序处理数据的方式
Content-Disposition: attachment; filename =
//设定实体内容的MIME类型
Content-Type:application/x-msdownload
  • ResponseEntity类:封装了HTTP响应信息对象
    • 构造方法参数:相应客户端的字节数组,响应头信息,相应状态码
  • HttpHeander类:封装响应头信息
    • 方法:setXXX()设置响应头
@RequestMapping("download")
    public ResponseEntity<byte[]> downLoad(String filename, HttpServletRequest request) throws IOException {
        //创建响应头信息对象
        HttpHeaders httpHeaders = new HttpHeaders();
        //获取要下载文件的绝对路径
        String path = request.getServletContext().getRealPath("download"+ File.separator+filename);
  //设置响应头,以附件形式处理文件
  httpHeaders.setContentDispositionFormData("attachment",filename);
        //设置响应头,文件的MIME类型
   httpHeaders.set("contentType",request.getServletContext().getMimeType(path));
        return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(new File(path)),httpHeaders, HttpStatus.OK);
    }

1.4 下载文件名的中文乱码问题

  • Firefox浏览器下载中文文件的时候采用的是Base64的编码.
  • IE浏览器,谷歌浏览器等,下载中文文件的时候采用的URL的编码.
filename="美女.jpg";
if (agent.contains("MSIE")) {
    // IE浏览器
    filename = URLEncoder.encode(filename, "utf-8");
    filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
    // 火狐浏览器
    BASE64Encoder base64Encoder = new BASE64Encoder();
    filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
} else {
    // 其它浏览器
    filename = URLEncoder.encode(filename, "utf-8");
}

第二章 文件上传

1.1 文件上传三要素

  • 表单的提交方式必须是post
  • 表单标签必须添加属性:enctype="multipart/form-data"
  • 文件域inputy标签,必须有name属性值

文件上传:浏览器将要上传的文件以二进制字节流的形式发送到服务器

1.2 MultipartFile接口-文件上传解析器

  • 接口方法
    • String getOriginalFilename()获取上传文件名
    • byte[] getBytes()文件转成字节数组
    • void transferTo(File file)转换方法,将上传文件转换到File对象
  • 配置文件上传解析器 springmvc.xml
<!--
     配置文件上传的解析器
     id=multipartResolver 固定值不可修改
 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--上传文件最大字节数-->
    <property name="maxUploadSize" value="10485760"/>
</bean>
  • handler方法实现文件上传
@RequestMapping("upload")
public String upload(MultipartFile multipartFile) throws IOException {
    //获取上传的文件名
    String originalFilename = multipartFile.getOriginalFilename();
    //获取上传文件的后缀名
    String extension = FilenameUtils.getExtension(originalFilename);
    //定义新的文件名
    String fileName = "atguigu"+UUID.randomUUID().toString()+System.currentTimeMillis()+"."+extension;
    String uploadDir = servletContext.getRealPath("uploadDir");
    //日期格式目录
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String format = sdf.format(new Date());
    File uploadDirFormat = new File(uploadDir,format);
    if (!uploadDirFormat.exists())
        uploadDirFormat.mkdirs();
    File uploadFile = new File(uploadDirFormat,fileName);
    //文件转存
    multipartFile.transferTo(uploadFile);
    return "index";
}

1.3 多文件上传

上传一个文件,和上传多个文件在本质上没有区别,在上传处理的方法参数上,使用MultipartFile接口数组即可实现。

1.4 多线程并发上传

1.5 跨越服务器上传

Web应用程序和文件混在一起

  • Web 应用重新部署时可能都会清理旧的构建结果,此时用户以前上传的文件会被删除,导致数据丢失。
  • 项目运行很长时间后,会导致上传的文件积累非常多,体积非常大,从而拖慢 Tomcat 运行速度。
  • 当服务器以集群模式运行时,文件上传到集群中的某一个实例,其他实例中没有这个文件,就会造成数据不一致。
  • 不支持动态扩容,一旦系统增加了新的硬盘或新的服务器实例,那么上传、下载时使用的路径都需要跟着变化,导致 Java 代码需要重新编写、重新编译,进而导致整个项目重新部署。

解决办法:将文件上传到一个专门存储文件的服务器中

可以使用第三方服务器:阿里云,七牛云等

  • 导入Sun公司开发的远程调用工具包
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
    <version>1.18.1</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.18.1</version>
</dependency>
@RequestMapping("fileUploadServer")
public String fileUploadServer(MultipartFile multipartFile) throws IOException {
    String originalFilename = multipartFile.getOriginalFilename();
    //获取上传文件的后缀名
    String extension = FilenameUtils.getExtension(originalFilename);
    //定义新的文件名
    String fileName = "atguigu"+ UUID.randomUUID().toString()+"."+extension;
    Client client = Client.create();
    //上传服务器的URL地址
    String url = "http://localhost:8081/xxx/upload/";
    //Web资源对象
    WebResource webResource = client.resource(url+fileName);
    String put = webResource.put(String.class, multipartFile.getBytes());
    return "index";
}

HTTP状态码403,上传到远程服务器报错403状态码,没做操作权限

<init-param>
    <param-name>readonly</param-name>
    <param-value>false</param-value>
</init-param>

第三章 拦截器

1.1 认识拦截器

  • Servlet:处理Request请求和Response响应
  • 过滤器(Filter):对Request请求起到过滤的作用,作用在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进行过滤处理
  • 监听器(Listener):实现了javax.servlet.ServletContextListener 接口的服务器端组件,它随Web应用的启动而启动,只初始化一次,然后会一直运行监视,随Web应用的停止而销毁
    • 作用一:做一些初始化工作
    • 作用二:监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线人数,利用HttpSessionLisener等。
  • 拦截器(Interceptor):是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler)。
    • 从配置的角度也能够总结发现:serlvet、filter、listener是配置在web.xml中的,而interceptor是配置在表现层框架自己的配置文件中的
    • 在Handler业务逻辑执行之前拦截一次
    • 在Handler逻辑执行完毕但未跳转页面之前拦截一次
    • 在跳转页面之后拦截一次

1.2 自定义拦截器

  • 实现接口HandlerInterceptor
  • 重写方法:
    • preHandle:handler之前执行,返回true表示放行
    • postHandle:handler逻辑真正执行完成但尚未返回页面
    • afterCompletion:返回页面之后
public class MyInterceptor implements HandlerInterceptor {
    @Override
    /**
     * 业务逻辑执行之前
     * return true 放行
     * Object handler 被拦截到的对象
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        System.out.println("handler = " + handler);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("业务逻辑完成");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("页面跳转完成");
    }
}
<mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/**"/>
        <bean class="com.atguigu.interceptor.MyInterceptor" id="interceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

1.3 拦截器链

  • 拦截器链执行时,拦截器链正常流程测试
    • preHandle顺序执行
    • postHandle倒序执行
    • afterCompletion倒序执行
  • 拦截器链中断流程测试
    • 拦截器链中有中断时,整个链中的拦截器的postHandle都不会执行
public class MyInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle222");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("业务逻辑完成222");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("页面跳转完成222");
    }
}

1.4 拦截器案例

  • 需求
    • 有一个登录页面,写一个Handler用于跳转登录页面
  • 登录页面有一提交表单的动作。需要在Controller中处理
  • 判断用户名密码是否正确(admin/admin)
  • 如果正确,向session中写入用户信息(写入用户名username)
  • 跳转到登录成功页面
  • 开发拦截器
    • 拦截用户请求,判断用户是否登录(登录页面跳转请求和登录提交请求不能拦截)
    • 如果用户已经登录则放行
    • 如果用户未登录则跳转到登录页面

登录页面

<html>
<head>
    <title>Title</title>
</head>
<body>
    <form method="post" action="/user/login.action">
        <input type="text" name="username"><br>
        <input type="password" name="password"><br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

UserController

@Controller
@RequestMapping("user")
public class UserController {
    @RequestMapping("login")
    public String login(Model model , String username, String password, HttpSession session){
        if("admin".equals(username) && "admin".equals(password)){
            session.setAttribute("username",username);
            return "redirect:/default/gotoResult.action";
        }
        return "redirect:/login.html";
    }
}

拦截器

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String username = (String)request.getSession().getAttribute("username");
        if(username == null) {
         response.sendRedirect(request.getContextPath()+"/login.html");
            return false;
        }
        return true;
    }

springmvc.xml

 <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/user/**"/>
            <bean class="com.atguigu.interceptor.RequestInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>