springmvc之拦截器、异常处理、文件上传
所谓的文件上传,其实也就是两个计算机之间共享文件。
一个进行发送,一个进行接收,利用http协议将数据封装的时候,通常来说,会使用post方式来进行提交。
那么post方式会携带上两个特殊的头字段:上传的数据类型(Content-Dispostion)和上传的数据大小()
Content-Dispostion:内容处理,服务器告知客户端如何来对资源来进行处理。
Content-Type:内容类型,也就是文件的MIME类型;
对于文件下载来说,就是要将存在于服务器上的共享文件下载下来。也就是说要将一台计算机中的文件拷贝到另外一台计算机上而已。
SpringMVC第2天
一、文件上传【掌握】
传统文件上传
文件上传三要素
- 页面表单必须是POST方式提交
- 页面表单的enctype属性值必须是
multipart/form-data
- 页面表单里必须有文件选择框
<input type="file" name="表单项名称"/>
文件上传原理简介
原理介绍
- 如果表单form标签的
enctype="multipart/form-data"
时,request.getParameter
方法将失效- 当
enctype="application/x-www-form-urlencoded"
时,提交的表单数据格式是:name=value&name=value&...
- 当
enctype="multipart/form-data"
时,提交的表单数据格式就变成多部分形式
- 当
- 客户端提交多部分表单时,会把文件内容一并提交:
- 服务端使用
request.getInputSteam()
可以获取到客户端提交数据,包含文件数据- 数据的格式:以指定分隔符隔开了,每一部分是一个表单项的数据
- 分隔符以请求头中,提交到服务端
- 使用指定分隔符,把得到的数据进行分割,然后解析得到其中的每项数据
- 把文件项的数据保存在服务器中
- 服务端使用
工具包
- 使用第三方jar包
commons-fileupload
, 可以实现更简单的文件上传 commons-fileupload
的maven坐标如下:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
- SpringMVC又对
commons-fileupload
做了再封装,实现文件上传,更加简单了
文件上传功能实现
需求
- 上传文件,保存到服务器
步骤
- 导入依赖:增加commons-fileupload
- 创建页面,在页面上提供表单:要符合文件上传的三要素
- 编写控制器代码
- 配置 文件解析器
CommonsMultipartResolver
实现
-
导入jar包依赖
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.8.RELEASE</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> </dependencies>
-
创建页面,在页面上提供表单
<form action="${pageContext.request.contextPath}/file/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <br> <input type="submit"> </form>
-
编写控制器代码
@Controller @RequestMapping("/file") public class Demo04FileUploadController { @RequestMapping("/upload") public String upload(MultipartFile file, HttpServletRequest request) throws IOException { //设置文件的保存路径 String destPath = request.getServletContext().getRealPath("files"); File destDir = new File(destPath); if (!destDir.exists()) { destDir.mkdir(); } //文件名称转换:转换成不含中文的文件名称 String filename = file.getOriginalFilename(); filename = System.currentTimeMillis() + filename.substring(filename.lastIndexOf(".")); //保存文件 File destFile = new File(destDir, filename); file.transferTo(destFile); return "success"; } }
-
配置
文件解析器
<!--配置文件解析器。注意:id必须是multipartResolver--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--配置上传文件的最大尺寸,单位:字节; -1表示不限制--> <property name="maxUploadSize" value="5242880"/> </bean>
小结
- 在pom.xml里导入依赖:
commons-fileupload
- 在springmvc.xml里配置文件解析器,bean名称必须是
multipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--maxUploadSize:上传的文件最大尺寸,单位byte-->
<property name="maxUploadSize" value="10240000"/>
<!--maxUploadSizePerFile:每个文件最大的尺寸,单位byte-->
<property name="maxUploadSizePerFile" value="1024000"/>
</bean>
- 在Controller里写代码接收保存文件
//注意:方法形参名称,必须和表单项名称相同
public String upload(MultipartFile file){
file.transferTo(new File("目标文件存储位置"));
return "success";
}
跨服务器文件上传(了解)
开发中的服务器
- 在实际开发中,为了提高程序效率,我们可以提供多个服务器,每个服务器负责不同的工作。
- 常见的服务器有:
- 应用服务器:部署web应用的服务器,我们安装了Tomcat的电脑
- 数据库服务器:负责数据存取服务,我们安装了MySql的电脑
- 缓存和消息服务器:负责处理高并发访问时的缓存和消息,我们安装了redis的电脑
- 文件服务器:存储文件的服务器
跨服务器文件上传【了解】
需求
- 用户上传文件时,把文件保存到单独的文件服务器上保存
分析
- 准备一个文件服务器(准备一个Tomcat),允许文件的存取
- 编写程序,提供文件上传功能;使用jersey把上传的文件保存到文件服务器上
实现
1. 准备一个文件服务器
- 拷贝一个Tomcat,在其
webspps
文件夹中创建项目,名称为:files
- 打开
conf/web.xml
文件,搜索DefaultServlet
,设置初始化参数readonly
,值为false
-
启动服务器(注意不要端口冲突了)
Tomcat里files项目的访问地址是:
http://localhost:8888/files
2. 编写程序,提供文件上传功能
-
在我们项目中导入
jersey
包的依赖<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.9</version> </dependency>
-
提供页面
<form action="file/upload2" method="post" enctype="multipart/form-data"> <input type="file" name="file"><br> <input type="submit"> </form>
-
编写控制器的方法,实现文件上传功能
@RequestMapping("/upload2") public String upload2(MultipartFile file, HttpServletRequest request) throws IOException { //文件名称转换:转换成不含中文的文件名称 String filename = file.getOriginalFilename(); filename = System.currentTimeMillis() + filename.substring(filename.lastIndexOf(".")); //得到一个客户端:访问远程文件服务的客户端 Client client = Client.create(); WebResource resource = client.resource("http://localhost:8888/uploadFiles/" + filename); resource.put(file.getBytes()); return "success"; }
-
配置文件解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="50000000"/> </bean>
小结
-
在pom.xml里导入依赖:
commons-fileupload, jersey-client
-
在springmvc.xml里配置文件解析器
-
在Controller里:使用jersey把文件推送保存到文件服务器上
@RequestMapping("/upload2") public String upload2(MultipartFile file1) throws IOException { //1. 解析转换文件名称:客户端提交的文件名称可能有中文,多次上传文件名称不能重复:建议重新命名为不含中文、不重复的的名称 //1.1 获取原始文件名称 String originalFilename = file1.getOriginalFilename(); //1.2 截取得到文件后缀名:.txt String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); //1.3 生成一个新的文件名称(不含中文、不重复) String filename = UUID.randomUUID().toString().replace("-", "") + suffix; //2. 用jersey把文件上传到文件服务器 Client client = Client.create(); WebResource resource = client.resource("http://localhost:8888/files/" + filename); resource.put(file1.getBytes()); return "success"; }
二、异常处理【重点】
- 可以每个Controller里处理自己的异常
- 可以有全局的异常处理器【掌握】
异常处理的思路
-
系统中的异常分为两类:
- 编译期异常,通过try...catch捕获异常,从而获取异常信息
- 运行时异常RuntimeException,通过规范代码开发、测试等手动,减少运行时异常的发生
-
系统开发中处理异常的思路:
- dao异常通常抛给Service
- Service异常通常抛给Controller
- Controller把异常抛给前端控制器(SpringMVC框架)
- 由前端控制器把异常交给异常处理器进行处理
异常处理的方式
两种常用方式介绍
- 实现Spring的异常处理器接口
HandlerExceptionResolver
,自定义异常处理器- 需要自己编写代码
- 也需要进行配置,配置比较简单。只需要在sringmvc.xml里,配置bean对象即可
- 使用SpringMVC提供好的简单异常处理器
SimpleMappingExceptionResolver
- 不需要自己编写代码
- 但需要进行配置,配置略微麻烦
自定义异常处理器【建议】
配置步骤
- 创建异常页面
- 创建异常处理器,实现
HandlerExceptionResolver
接口 - 在springmvc.xml中配置异常处理器,把自定义异常处理器类声明成为bean对象
配置示例
- 准备异常页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常</title>
</head>
<body>
异常的默认页面 ${msg}
</body>
</html>
- 创建自定义异常处理器
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
if (ex instanceof NullPointerException) {
//如果是空指针异常,可以做针对性处理。通常不需要
modelAndView.addObject("msg", "空指针异常了");
}else if(ex instanceof ClassCastException){
//如果是类型转换异常,可以做针对性处理。通常不需要
modelAndView.addObject("msg", "类型转换异常了");
}
return modelAndView;
}
}
- 在springmvc.xml中配置异常处理器
<bean class="com.guang.resolver.MyExceptionResolver"/>
简单异常处理器【拓展了解】
- SpringMVC已经定义好了异常处理器,在使用时根据项目情况,配置异常与视图的映射关系
配置步骤
- 创建异常页面。可以根据实际需求创建错误页面,比如:
- 所有异常,都使用同一页面error.jsp
- 不同异常,配置不同的错误页面
- 类转换异常,配置castError.jsp
- 空指定异常,配置nullError.jsp
- ...
- 在springmvc.xml中,配置简单异常处理器
SimpleMappingExceptionResolver
配置示例
- 准备异常页面
error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常</title>
</head>
<body>
异常的默认页面
</body>
</html>
- 在springmvc.xml中配置
<!--配置简单异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--配置默认的错误视图-->
<property name="defaultErrorView" value="error"/>
<!--配置异常与视图的映射-->
<property name="exceptionMappings">
<map>
<!--类转换异常,显示视图error页面-->
<entry key="java.lang.ClassCastException" value="error"/>
<!--空指针异常,显示视图error页面-->
<entry key="java.lang.NullPointerException" value="error"/>
</map>
</property>
</bean>
小结
- 创建一个Java类,实现
HandlerExceptionResolver
接口:编写代码处理异常 - 把创建好的异常处理器注册bean
三、拦截器
- Filter:可以在请求到达目标资源之前,先拦截下来,处理之后再放行
- 拦截器:可在以请求到达Controller方法之前,先拦截下来,处理之后再放行
拦截器简介
什么是拦截器
- SpringMVC中的拦截器,相当于web开发中的过滤器Filter,用于对Controller进行预处理和后处理
- 多个拦截器形成的一条链,称为拦截器链(Interceptor chain)
- 当访问被拦截的方法或字段时,拦截器链中的拦截器就会按照之前定义的顺序被调用
- 拦截器也是AOP思想的具体实现
拦截器和过滤器的区别
- Servlet规范包含三项技术:Servlet、Filter(/*)、Listener
区别 | 过滤器Filter | 拦截器 |
---|---|---|
使用范围 | 是Servlet规范的一部分,任何Javaweb项目都可以使用 | 是SpringMVC自己的,只有使用了SpringMVC框架,才可以使用拦截器 |
拦截范围 | 配置了urlPatterns="/*"之后,可以对所有要访问的资源进行拦截 | 只会拦截访问的控制器方法,如果访问的是JSP、HTML、CSS、图片或者js时,不拦截 |
拦截精度 | 只能拦截某个请求,不能对Servlet里某个方法进行拦截 | 可以精细到拦截Controller里的某个方法 |
快速入门
准备工作
- 创建maven项目,准备SpringMVC的环境
- 创建控制器
DemoController
,准备一个目标方法show()
@Controller
public class DemoController {
@RequestMapping("/show")
public ModelAndView show(ModelAndView modelAndView){
System.out.println("目标方法show().....");
modelAndView.addObject("username", "tom");
modelAndView.setViewName("success");
return modelAndView;
}
}
实现步骤
- 创建一个Java类,实现
HandlerInterceptor
接口- 重写接口的方法,共三个方法:
preHandle, postHandle, afterCompletion
- 重写接口的方法,共三个方法:
- 在springmvc.xml中配置拦截器
功能实现
- 创建Java类,实现
HandlerInterceptor
接口
public class MyInterceptor implements HandlerInterceptor {
//在Controller的方法被调用之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
//在Controller的方法被调用之后,视图渲染之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
//在Controller的方法执行完毕之后(渲染视图之后),再执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
- 在springmvc.xml中配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!-- /**表示所有后代, /*表示子级 -->
<mvc:mapping path="/**"/>
<bean class="com.guang.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
小结
拦截器详解
API介绍
preHandle(request, response, handler)
- 在控制器方法被调用之前先执行
- 如果方法返回true,表示放行;如果方法返回false,表示不放行
postHandle(request, response, handler, modelAndView)
- 在控制器方法执行后、渲染视图之前执行
- 无返回值
afterCompletion(request, response, handler, exeption)
- 在Controller完全执行完毕之后(渲染视图之后),返回响应前执行,用的少
配置详解
配置语法
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.guang.interceptor.MyInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.guang.interceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
<mvc:interceptor>
一个拦截器的配置<mvc:mapping/>
配置拦截器的拦截范围<bean/>
配置拦截器类
多个拦截器配置时的执行顺序
- 执行顺序:由配置的顺序决定,谁在前谁就先拦截。
- 谁的预处理先执行,那么它的后处理就要后执行
preHandle
方法执行的越早,postHandle
和afterCompletion
就执行的越晚
源码分析
DispatcherServlet
的doDispatch
方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
........
try {
.......
try {
//1. 检查是否是上传文件的请求,如果是,就解析文件,得到request的包装类对象
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//2. 查
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//3. 查找当前Handler对应的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...........
//4. 执行拦截器的预处理方法PreHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//5. 真正的执行Handler方法,得到ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
.............
//6. 如果ModelAndView里没有设置视图名称,给提供一个默认的视图名称
applyDefaultViewName(processedRequest, mv);
//7. 执行拦截器的后处理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
...
}
catch (Throwable err) {
...
}
//8.根据视图名称查找视图对象,渲染视图,之后调用拦截器的终处理方法afterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//如果渲染视图中,出现了Exception异常,调用拦截器的终处理方法afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//如果渲染视图中,出现了Throwable异常,调用拦截器的终处理方法afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
.....
}
}
小结
-
拦截器的创建步骤:
- 创建一个Java类,实现
HandlerInterceptor
接口,重写接口的方法 - 修改springmvc.xml,配置拦截器
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="拦截器的全限定类名"/> </mvc:interceptor> </mvc:interceptors>
- 创建一个Java类,实现
-
API:
- preHandle:预处理方法,在Handler方法执行之前先执行的。如果返回false,表示不放行;true放行
- postHandle:后处理方法,在Handler方法执行之后,渲染视图之前执行的
- afterCompletion:终处理方法,在Handler方法执行之后、渲染视图之后 最终执行的
-
配置:
mvc:mapping
:要拦截的范围mvc:exclude-mapping
:要排除不拦截范围- 拦截范围的写法:
/demo04/show2
:只拦截对这个路径的请求/*
:表示拦截/xxx
,它的后代/xxx/yyy
不拦截/**
:表示拦截所有,包括后代也拦截
-
拦截器的执行顺序:
- 哪个拦截器配置在前,就先执行。
- 预处理先执行的,后处理就后执行
-
任何一个拦截器不放行,就不可能调用到目标方法
三层架构:web\service\dao
MVC模式:M是model层,V是view层,C是控制层
web层负责和前端来进行交互的;service层是负责和处理后台业务逻辑的;dao层是负责和数据库打交道的;
对于MVC模式中,MODEL负责的是数据层,VIEW是视图层,负责显示数据等等;C控制层,用来接收前端请求等等操作;
在入门阶段学习配置的东西有:
1、在springmvc中的dispatcherservlet启动的时候开始加载配置文件中得一些信息,这些信息需要进行操作的是,读取当前类中的controller下面
所有的包,将这些对象加入到springMVC容器中去;
2、既然是servlet,那么就需要来提供访问路径。当前提供的是/,而不是/*。只提供了一个缺省匹配。目的是为了在访问到jsp的时候,能够让Tomcat
提供对应的组件来进行操作,而不能够造成没有能够处理jsp页面的出现
3、访问来了之后,通过对应的请求的路径,dispatcherservlet利用处理器映射器来找到对应的controller中的method来进行处理;
用处理器适配器来进行调用具体的方法来进行调用。这个时候,已经确定的有:方法参数
调用完成之后一般将会返回ModelAndView对象,里面包含了数据和页面的一些信息;包含了返回值类型参数等等。
4、视图解析器
根据逻辑视图找到对应的物理视图
5、渲染:
将数据填充到物理页面上去;
这种是一种传统的单体架构模式了,现在基本上走到了第三步骤就不会再往下走了。现在发送的都是json格式的数据了,直接利用自带的组件来进行操作即可