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做了再封装,实现文件上传,更加简单了

文件上传功能实现

需求
  • 上传文件,保存到服务器
步骤
  1. 导入依赖:增加commons-fileupload
  2. 创建页面,在页面上提供表单:要符合文件上传的三要素
  3. 编写控制器代码
  4. 配置 文件解析器CommonsMultipartResolver
实现
  1. 导入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>
    
  2. 创建页面,在页面上提供表单

    <form action="${pageContext.request.contextPath}/file/upload" method="post" 
          enctype="multipart/form-data">
        <input type="file" name="file"> <br>
        <input type="submit">
    </form>
    
  3. 编写控制器代码

    @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";
        }
    }
    
  4. 配置 文件解析器

    <!--配置文件解析器。注意:id必须是multipartResolver-->
    <bean id="multipartResolver" 
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--配置上传文件的最大尺寸,单位:字节; -1表示不限制-->
        <property name="maxUploadSize" value="5242880"/>
    </bean>
    

小结

  1. 在pom.xml里导入依赖:commons-fileupload
  2. 在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>
  1. 在Controller里写代码接收保存文件
//注意:方法形参名称,必须和表单项名称相同
public String upload(MultipartFile file){
    file.transferTo(new File("目标文件存储位置"));
    return "success";
}

跨服务器文件上传(了解)

开发中的服务器

  • 在实际开发中,为了提高程序效率,我们可以提供多个服务器,每个服务器负责不同的工作。
  • 常见的服务器有:
    • 应用服务器:部署web应用的服务器,我们安装了Tomcat的电脑
    • 数据库服务器:负责数据存取服务,我们安装了MySql的电脑
    • 缓存和消息服务器:负责处理高并发访问时的缓存和消息,我们安装了redis的电脑
    • 文件服务器:存储文件的服务器

跨服务器文件上传【了解】

需求
  • 用户上传文件时,把文件保存到单独的文件服务器上保存
分析
  1. 准备一个文件服务器(准备一个Tomcat),允许文件的存取
  2. 编写程序,提供文件上传功能;使用jersey把上传的文件保存到文件服务器上
实现
1. 准备一个文件服务器
  1. 拷贝一个Tomcat,在其webspps文件夹中创建项目,名称为:files

  1. 打开conf/web.xml文件,搜索DefaultServlet,设置初始化参数readonly,值为false

  1. 启动服务器(注意不要端口冲突了)

    Tomcat里files项目的访问地址是:http://localhost:8888/files

2. 编写程序,提供文件上传功能
  1. 在我们项目中导入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>
    
  2. 提供页面

    <form action="file/upload2" method="post" enctype="multipart/form-data">
        <input type="file" name="file"><br>
        <input type="submit">
    </form>
    
  3. 编写控制器的方法,实现文件上传功能

    @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";
        }
    
  4. 配置文件解析器

    <bean id="multipartResolver" 
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="50000000"/>
    </bean>
    

小结

  1. 在pom.xml里导入依赖:commons-fileupload, jersey-client

  2. 在springmvc.xml里配置文件解析器

  3. 在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
    • 不需要自己编写代码
    • 但需要进行配置,配置略微麻烦

自定义异常处理器【建议】

配置步骤
  1. 创建异常页面
  2. 创建异常处理器,实现HandlerExceptionResolver接口
  3. 在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已经定义好了异常处理器,在使用时根据项目情况,配置异常与视图的映射关系
配置步骤
  1. 创建异常页面。可以根据实际需求创建错误页面,比如:
    • 所有异常,都使用同一页面error.jsp
    • 不同异常,配置不同的错误页面
      • 类转换异常,配置castError.jsp
      • 空指定异常,配置nullError.jsp
      • ...
  2. 在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>

小结

  1. 创建一个Java类,实现HandlerExceptionResolver接口:编写代码处理异常
  2. 把创建好的异常处理器注册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;
    }
}

实现步骤

  1. 创建一个Java类,实现HandlerInterceptor接口
    • 重写接口的方法,共三个方法:preHandle, postHandle, afterCompletion
  2. 在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方法执行的越早,postHandleafterCompletion就执行的越晚

源码分析

  • DispatcherServletdoDispatch方法
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 {
			.....
		}
	}

小结

  • 拦截器的创建步骤:

    1. 创建一个Java类,实现HandlerInterceptor接口,重写接口的方法
    2. 修改springmvc.xml,配置拦截器
    <mvc:interceptors>
    	<mvc:interceptor>
        	<mvc:mapping path="/**"/>
            <bean class="拦截器的全限定类名"/>
        </mvc:interceptor>
    </mvc:interceptors>
    
  • 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格式的数据了,直接利用自带的组件来进行操作即可

posted @ 2021-10-09 00:17  雩娄的木子  阅读(207)  评论(0编辑  收藏  举报