SpringBoot拦截器与文件上传实现方法与源码分析_第1页
SpringBoot拦截器与文件上传实现方法与源码分析_第2页
SpringBoot拦截器与文件上传实现方法与源码分析_第3页
SpringBoot拦截器与文件上传实现方法与源码分析_第4页
SpringBoot拦截器与文件上传实现方法与源码分析_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第SpringBoot拦截器与文件上传实现方法与源码分析目录一、拦截器1、创建一个拦截器2、配置拦截器二、拦截器原理三、文件上传四、文件上传流程

一、拦截器

拦截器我们之前在springmvc已经做过介绍了

大家可以看下【SpringMVC】自定义拦截器和过滤器

为什么在这里还要再讲一遍呢?

因为springboot里面对它做了简化,大大节省了我们配置那些烦人的xml文件的时间

接下来,我们就通过一个小例子来了解一下拦截器在springboot中的使用

1、创建一个拦截器

首先我们创建一个拦截器,实现HandlerInterceptor接口

packageerceptor;

importlombok.extern.slf4j.Slf4j;

importorg.springframework.web.servlet.HandlerInterceptor;

importorg.springframework.web.servlet.ModelAndView;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

importjavax.servlet.http.HttpSession;

@Slf4j

publicclassLoginInterceptorimplementsHandlerInterceptor{

//在调用控制器接口方法之前进入,如果放回true就放行,进入下一个拦截器或者控制器,如果返回false就不继续往下走

@Override

publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{

//获取当前请求路径

finalStringrequestURL=request.getRequestURI();

("拦截到的请求为:{}",requestURL);

finalHttpSessionsession=request.getSession();

finalObjectuserSession=session.getAttribute("loginUser");

//如果session中存在用户登录信息,那么就判定为用户已登录,放行

if(null!=userSession){

returntrue;

}else{

//model和request都会往请求域中塞信息,所以这里可以使用request传递我们需要返回给前端的信息

request.setAttribute("msg","请登录!");

//转发到登录页

request.getRequestDispatcher("/").forward(request,response);

returnfalse;

//调用前提:preHandle返回true

//调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作

//执行顺序:链式Interceptor情况下,Interceptor按照声明的顺序倒着执行。

//备注:postHandle虽然post打头,但post、get方法都能处理

@Override

publicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{

("postHandle执行{}",modelAndView);

//调用前提:preHandle返回true

//调用时间:DispatcherServlet进行视图的渲染之后

//多用于清理资源

@Override

publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{

("页面渲染完成后执行");

}

2、配置拦截器

创建完之后,我们就需要将拦截器注册到容器中,并指定拦截规则

那么,我们创建一个配置类,实现WebMvcConfigurer接口,重写addInterceptors方法,将我们之前创建好的拦截器放入即可

值得注意的是,我们要放开对登录页以及静态资源的限制

packagecom.decade.config;

importerceptor.LoginInterceptor;

importorg.springframework.context.annotation.Configuration;

importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;

importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration

publicclassMyConfigimplementsWebMvcConfigurer{

@Override

publicvoidaddInterceptors(InterceptorRegistryregistry){

//addPathPatterns:设置要拦截的请求,如果是/**,那么会拦截包括静态资源在内的所有请求

//excludePathPatterns:设置不被拦截的请求,这里我们放行登录页请求和静态资源

registry.addInterceptor(newLoginInterceptor())

.addPathPatterns("/**")

.excludePathPatterns("/","/login","/css/**","/images/**","/js/**","/fonts/**");

}

我们在未登录的状态下,对主页发起一个请求,可以发现,拦截器生效,而且拦截器中的方法所执行的顺序也符合预期

二、拦截器原理

我们还是使用debug模式,通过断点来进行分析

调用之前的主页面接口,可以发现断点还是走到了DispatcherServlet类下的doDispatch()

首先,他还是会返回给我们一个处理器执行链HandlerExecutionChain

这个里面除了包含我们的请求应该由哪个控制器类的哪个方法进行处理之外,还包含了拦截器链

然后在使用mv=ha.handle(processedRequest,response,mappedHandler.getHandler());执行目标方法之前,他会调用一个applyPreHandle()方法

如果这个方法返回false,那么就会直接返回,不再继续往下走

我们进入applyPreHandle()方法可以看到,这个方法里会遍历所有的拦截器,如果preHandle()方法返回结果为true,那就继续调用下一个拦截器的preHandle()方法

只要有一个拦截器的preHandle()方法返回false,那么就会从当前遍历到的拦截器开始,倒序执行afterCompletion()方法

booleanapplyPreHandle(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{

for(inti=0;ierceptorList.size();erceptorIndex=i++){

HandlerInterceptorinterceptor=(HandlerInterceptor)erceptorList.get(i);

//如果拦截器的preHandle()返回false,那么就会调用下面的triggerAfterCompletion()

if(!interceptor.preHandle(request,response,this.handler)){

this.triggerAfterCompletion(request,response,(Exception)null);

returnfalse;

returntrue;

//这个方法里面会从当前遍历到的拦截器开始,倒序执行afterCompletion()方法

voidtriggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,@NullableExceptionex){

for(inti=erceptorIndex;i--i){

HandlerInterceptorinterceptor=(HandlerInterceptor)erceptorList.get(i);

try{

interceptor.afterCompletion(request,response,this.handler,ex);

}catch(Throwablevar7){

logger.error("HandlerInterceptor.afterCompletionthrewexception",var7);

}

执行完目标方法之后,断点又走到mappedHandler.applyPostHandle(processedRequest,response,mv);

深入这个方法,我们可以发现,这里是倒序执行了所有拦截器的postHandle()方法

voidapplyPostHandle(HttpServletRequestrequest,HttpServletResponseresponse,@NullableModelAndViewmv)throwsException{

for(inti=erceptorList.size()-1;i--i){

HandlerInterceptorinterceptor=(HandlerInterceptor)erceptorList.get(i);

interceptor.postHandle(request,response,this.handler,mv);

}

最后,页面渲染完成之后,他也会倒序执行所有拦截器的afterCompletion()方法

注意:只要在请求处理期间出现任何异常,它都会倒序执行所有拦截器的postHandle()方法

三、文件上传

之前博主也写过关于SpringMVC的文件上传和下载

使用SpringBoot之后,我们节约了很多的配置

接下来,我们就通过一个例子,了解SpringBoot中的文件上传

首先,我们先创建一个页面,这里我们只贴核心代码

默认情况下,enctype的值是application/x-www-form-urlencoded,不能用于文件上传,只有使用了multipart/form-data,才能完整的传递文件数据multiple表示可接受多个值的文件上传字段

div

formrole="form"th:action="@{/upload}"method="post"enctype="multipart/form-data"

div

labelfor="exampleInputEmail1"邮箱/label

inputtype="email"name="email"id="exampleInputEmail1"placeholder="Enteremail"

/div

div

labelfor="exampleInputPassword1"名字/label

inputtype="text"name="username"id="exampleInputPassword1"placeholder="Password"

/div

div

labelfor="exampleInputFile"头像/label

inputtype="file"name="headerImg"id="exampleInputFile"

/div

div

labelfor="exampleInputFile"生活照/label

inputtype="file"name="photos"multiple

/div

div

label

inputtype="checkbox"Checkmeout

/label

/div

buttontype="submit"提交/button

/form

/div

然后我们写一下后端的业务代码

packagecom.decade.controller;

importlombok.extern.slf4j.Slf4j;

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.GetMapping;

importorg.springframework.web.bind.annotation.PostMapping;

importorg.springframework.web.bind.annotation.RequestParam;

importorg.springframework.web.bind.annotation.RequestPart;

importorg.springframework.web.multipart.MultipartFile;

importjava.io.File;

importjava.io.IOException;

@Controller

@Slf4j

publicclassFileUploadController{

*页面跳转,跳转到文件上传页面

*@return跳转到文件上传页面

@GetMapping(value="/form_layouts")

publicStringuploadPage(){

return"form/form_layouts";

*文件上传请求

*@paramemail邮件

*@paramusername用户名

*@paramheaderImg头像文件

*@paramphotos生活照

*@return如果上传文件成功,跳转到首页

@PostMapping(value="/upload")

publicStringuploadFile(@RequestParam(name="email")Stringemail,

@RequestParam(name="username")Stringusername,@RequestPart("headerImg")MultipartFileheaderImg,

@RequestPart("photos")MultipartFile[]photos){

("请求参数email{},username{},头像headerImg大小{},生活照photos张数{}",

email,username,headerImg.getSize(),photos.length);

try{

//判断头像文件是否为空,如果不是为空,那么就保存到本地

if(!headerImg.isEmpty()){

finalStringfilename=headerImg.getOriginalFilename();

headerImg.transferTo(newFile("D:\\test1\\"+filename));

//判断生活照是否上传,循环保存到本地

if(photos.length0){

for(MultipartFilephoto:photos){

finalStringoriginalFilename=photo.getOriginalFilename();

photo.transferTo(newFile("D:\\test1\\"+originalFilename));

}catch(IOExceptione){

log.error("上传文件出错!",e);

return"redirect:/main.html";

}

如果报错信息如下,那么我们需要去SpringBoot的默认文件中添加如下配置

#单个文件最大限制

spring.servlet.multipart.max-file-size=10MB

#单次请求最大限制

spring.servlet.multipart.max-request-size=100MB

修改相关配置之后,文件上传成功

四、文件上传流程

文件上传相关配置类MultipartAutoConfiguration,相关配置类MultipartProperties

在MultipartAutoConfiguration中我们自动配置好了文件上传解析器StandardServletMultipartResolver(它在容器中的beanName为multipartResolver)

然后我们跟着上面文件上传的例子进行一个debug,分析一下流程

首先,断点还是来到DispatcherServlet下面的doDispatch()方法

protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{

HttpServletRequestprocessedRequest=request;

HandlerExecutionChainmappedHandler=null;

//设置文件解析默认值为false

booleanmultipartRequestParsed=false;

WebAsyncManagerasyncManager=WebAsyncUtils.getAsyncManager(request);

try{

try{

ModelAndViewmv=null;

ObjectdispatchException=null;

try{

//检查当前请求是否涉及文件上传

processedRequest=this.checkMultipart(request);

//将文件解析设置为true,表明当前请求涉及文件上传

multipartRequestParsed=processedRequest!=request;

这里的processedRequest=this.checkMultipart(request);

会调用StandardServletMultipartResolver类中的isMultipart()判断当前请求是否涉及文件上传

如果涉及那么就会对当前请求做一个处理,将原生的请求封装成一个StandardMultipartHttpServletRequest请求,把文件相关信息解析后放进Map中(具体可以看StandardMultipartHttpServletRequest类中的parseRequest方法)

protectedHttpServletRequestcheckMultipart(HttpServletRequestrequest)throwsMultipartException{

//如果文件上传解析器不为空,那么就调用StandardServletMultipartResolver类中的isMultipart()判断当前请求是否涉及文件上传

if(this.multipartResolver!=nullthis.multipartResolver.isMultipart(request)){

if(WebUtils.getNativeRequest(request,MultipartHttpServletRequest.class)!=null){

if(DispatcherType.REQUEST.equals(request.getDispatcherType())){

this.logger.trace("RequestalreadyresolvedtoMultipartHttpServletRequest,e.g.byMultipartFilter");

}elseif(this.hasMultipartException(request)){

this.logger.debug("Multipartresolutionpreviouslyfailedforcurrentrequest-skippingre-resolutionforundisturbederrorrendering");

}else{

try{

//将原生的请求封装成一个StandardMultipartHttpServletRequest请求,把文件相关信息解析放进Map中

returnthis.multipartResolver.resolveMultipart(request);

然后我们按照之前请求处理那篇博客里的路径,从mv=ha.handle(processedRequest,response,mappedHandler.getHandler())进入

一直走到InvocableHandlerMethod下面的getMethodArgumentValues()方法,深入断点

我们得知,使用@RequestParam注解的参数使用RequestParamMethodArgumentResolver这个解析器

而文件相关入参是使用@RequestPart注解的,它使用RequestPartMethodArgumentResolver来进行文件相关参数解析

在这个解析器中,他又会根据参数的名称去上面checkM

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论