SpringMVC
第一章 文件下载
1.1 文件下载原理
对于文件下载,相信大家并不会陌生,因为通常在上网时所下的图片、文档和影片等都是文件下载的范畴。现在很多网站都提供了下载各类资源的功能,因此在学习Web开发过程中,有必要学习文件下载的实现方式。
实现文件下载功能比较简单,通常情况下,不需要使用第三方组件实现,而是直接使用输入/输出流实现即可。
1.2 不同浏览器对文件下载的不同
使用链接地址指向要下载的文件。此时浏览器会尽可能解析对应的文件,只要是能够在浏览器窗口展示的,就都会直接显示,而不是提示下载。
<a href="download/1.docx">1.docx</a><br>
<a href="download/1.pdf">1.pdf</a><br>
<a href="download/1.txt">1.txt</a><br>
<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>