SpringMVC笔记

SpringMVC笔记

SpringMVC的工作流程

SpringMVC的请求处理流程在这里插入图片描述

流程说明:

第一步:用户发送请求至前端控制器DispactherServlet

第二步:DispactherServlet收到请求调用处理器映射器HandlerMapping

第三步:HandlerMapping根据请求的URL,找到具体的Handler(后端控制器,可以是个方法,也可以是个类),生成处理器对象和拦截器(如果有),并返回给DispatcherServlet

第四步:DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler

第五步:执行Handler方法

第六步:Handler方法执行完毕,返回ModelAndView给HandlerAdapter

第七步:HandlerAdapter返回ModelAndView到DispatcherServlet

第八步:DispatcherServlet调用视图解析器,解析视图,根据视图的逻辑名来解析真正的视图

第九步:视图解析器返回解析完成的视图View给DispatcherServlet

第十步:前端控制器DispatcherServlet进行视图渲染,就是将ModelAndView对象中的数据模型,填充到request域中

第十一步:前端控制器向用户响应结果

SpringMVC的九大组件
  • HandleMapping(处理器映射器):处理器映射器通过收到的用户请求,找到处理对应请求的Handler(后端控制器和对应的拦截器),Handler可以是方法也可以是类。比如,在@Controller标注了的类中,被@RequestMapping标注了的方法都可以看出是一个Handler,handler负责具体请求的处理,在请求到达后,HandlerMapping 就根据具体的Url找到相应的处理器Handler和拦截器Interceptor
  • HandlerAdapter(处理器适配器):HandlerAdapter是一个适配器,因为SpringMVC中的Handler是可以任意形式的,只要能处理请求即可,但是把请求交给Servlet处理时,用于Servlet都是doServer(HttpServletRequest request,HttpServletResponse respose)形式的,要让固定的处理方法调用Handler来进行处理,便是HandlerAdapter的职责
  • ViewResovler(视图解析器):视图解析器用于将String类型的视图名和Locale(国际化,zh-CN)解析为View类型的视图,只有一个resovleViewName()方法。Controller层返回的String类型的视图名ViewName,会在这里解析成View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情:ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。
  • MultipartResovler:主要用于上传请求的处理,通过将普通的请求包装成MultpartHttpServletRequest来实现。
  • HandlerExceptionResovler:用于处理Handler产生的异常情况。它的作用是根据异常设置ModelAndView,之后交给渲染方法进行渲染。
  • RequestToViewNameTranslator:RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。(如果没有设置viewName,默认把请求地址当作逻辑视图名)
  • LocaleResovler:ViewResovler组件的resovleViewName方法要传两个参数,一个是视图名,一个是Locale。LocaleResovler用于从请求中解析出Locale,比如中国是zh-CN,用来表示一个区域,这个组件也是国际化的基础
  • ThemeResovler:ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和具体的资源。
  • FlashMapManager:FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通过FlashMap来传递。(具体查看后面“基于Flash属性的跨重定向请求数据传递”)

SpringMVC请求参数的绑定

  • 简单数据类型参数的绑定,使用其包装类

    整型:Integer、int

    字符串:String

    单精度:Float、flfloat

    双精度:Double、double

    布尔型:Boolean、boolean

    说明:对于布尔类型的参数,请求的参数值为truefalse。或者10

    注意:绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持⼀

    致,建议 使⽤包装类型,当形参参数名和传递参数名不⼀致时可以使⽤@RequestParam注解进⾏

    ⼿动映射)

  • 绑定Pojo类型参数

    • Get请求的处理方式
    /*
     * SpringMVC接收pojo类型参数 url:/demo/handle04?id=1&username=zhangsan
     *
     * 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名⽆所谓
     * 但是要求传递的参数名必须和Pojo的属性名保持⼀致
     */
     @RequestMapping("/handle04")
     public ModelAndView handle04(User user) {
     Date date = new Date();
     ModelAndView modelAndView = new ModelAndView();
     modelAndView.addObject("date",date);
     modelAndView.setViewName("success");
     return modelAndView;
     }
    
    • 如果是Post请求,参数名称和Pojo的属性名保持一致即可
  • 绑定Pojo包装对象参数

    • 包装类型 QueryVo

      public class QueryVo {
       private String mail;
       private String phone;
       // 嵌套了另外的Pojo对象
       private User user;
       public String getMail() {
       return mail;
       }
       public void setMail(String mail) {
       this.mail = mail;
       }
       public String getPhone() {
       return phone;
       }
       public void setPhone(String phone) {
       this.phone = phone;
       }
       public User getUser() {
       return user;
       }
       public void setUser(User user) {
       this.user = user;
       }
      }
      

      Handler⽅法

      /*
       * SpringMVC接收pojo包装类型参数 url:/demo/handle05?
      user.id=1&user.username=zhangsan
       * 不管包装Pojo与否,它⾸先是⼀个pojo,那么就可以按照上述pojo的要求来
       * 1、绑定时候直接形参声明即可
       * 2、传参参数名和pojo属性保持⼀致,如果不能够定位数据项,那么通过属性名 + "." 的
      ⽅式进⼀步锁定数据
       *
       */
       @RequestMapping("/handle05")
       public ModelAndView handle05(QueryVo queryVo) {
       Date date = new Date();
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.addObject("date",date);
       modelAndView.setViewName("success");
       return modelAndView;
       }
      
  • 绑定⽇期类型参数(需要配置⾃定义类型转换器)

    • 自定义类型转化器

      import org.springframework.core.convert.converter.Converter;
      import java.text.ParseException;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      /**
      * ⾃定义类型转换器
      * S:source,源类型
      * T:target:⽬标类型
      */
      public class DateConverter implements Converter<String, Date> {
       @Override
       public Date convert(String source) {
       // 完成字符串向⽇期的转换
       SimpleDateFormat simpleDateFormat = new
       SimpleDateFormat("yyyy-MM-dd");
       try {
       Date parse = simpleDateFormat.parse(source);
       return parse;
       } catch (ParseException e) {
       e.printStackTrace();
       }
       return null;
       }
      }
      
    • 注册⾃定义类型转换器

      <!--
       ⾃动注册最合适的处理器映射器,处理器适配器(调⽤handler⽅法)
       -->
       <mvc:annotation-driven conversionservice="conversionServiceBean"/>
       <!--注册⾃定义类型转换器-->
       <bean id="conversionServiceBean"
       	class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
       	<property name="converters">
       		<set>
       			<bean class="com.lagou.edu.converter.DateConverter"></bean>
       		</set>
       	</property>
       </bean>
      
    • 前端jsp请求

      <p>测试⽤例:SpringMVC接收⽇期类型参数</p>
      <a href="/demo/handle06?birthday=2019-10-08">点击测试</a>
      
    • 后端Handler方法

      /**
       * 绑定⽇期类型参数
       * 定义⼀个SpringMVC的类型转换器 接⼝,扩展实现接⼝接⼝,注册你的实现
       * @param birthday
       * @return
       */
       @RequestMapping("/handle06")
       public ModelAndView handle06(Date birthday) {
       Date date = new Date();ModelAndView modelAndView = new ModelAndView();
       modelAndView.addObject("date",date);
       modelAndView.setViewName("success");
       return modelAndView;
       }
      

SpringMVC对Restful风格请求的支持

  • 什么是Restful

    • Restful 是⼀种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是⼀个资源定位及资源操作的⻛格。
  • Restful 的优点

    • 它结构清晰、符合标准、易于理解、扩展⽅便,所以正得到越来越多⽹站的采⽤。
  • Restful 的特性

    • 资源(Resources):⽹络上的⼀个实体,或者说是⽹络上的⼀个具体信息。
  • RESTful 的示例

    • 获取id为1的用户信息
      • GET http://localhost:8080/user/1
    • 更新
      • PUT http://localhost:8080/user/1
    • 删除
      • DELETE http://localhost:8080/user/1

rest⻛格带来的直观体现:就是传递参数⽅式的变化,参数可以在uri中了

  • 示例代码

    • 前端JSP
    <div>
     	<h2>SpringMVC对Restful⻛格url的⽀持</h2>
     	<fieldset>
     		<p>测试⽤例:SpringMVC对Restful⻛格url的⽀持</p>
     		<a href="/demo/handle/15">rest_get测试</a>
     		<form method="post" action="/demo/handle">
    			 <input type="text" name="username"/>
     			<input type="submit" value="提交rest_post请求"/>
     		</form>
     		<form method="post" action="/demo/handle/15/lisi">
     			<input type="hidden" name="_method" value="put"/>
     			<input type="submit" value="提交rest_put请求"/>
     		</form>
     		<form method="post" action="/demo/handle/15">
     			<input type="hidden" name="_method" value="delete"/>
     			<input type="submit" value="提交rest_delete请求"/>
     		</form>
     	</fieldset>
    </div>
    
    • 后台Handler方法

      /*
       * restful get /demo/handle/15
       */
       @RequestMapping(value = "/handle/{id}",method = {RequestMethod.GET})
       public ModelAndView handleGet(@PathVariable("id") Integer id) {
       Date date = new Date();
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.addObject("date",date);
       modelAndView.setViewName("success");
       return modelAndView;
       }
       /*
       * restful post /demo/handle
       */
       @RequestMapping(value = "/handle",method = {RequestMethod.POST})
       public ModelAndView handlePost(String username) {
       Date date = new Date();
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.addObject("date",date);
       modelAndView.setViewName("success");
       return modelAndView;
       }
       /*
       * restful put /demo/handle/15/lisi
       */
       @RequestMapping(value = "/handle/{id}/{name}",method = {RequestMethod.PUT})
       public ModelAndView handlePut(@PathVariable("id") Integer
      id,@PathVariable("name") String username) {
       Date date = new Date();
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.addObject("date",date);
       modelAndView.setViewName("success");
       return modelAndView;
       }
       /*
       * restful delete /demo/handle/15
       */
       @RequestMapping(value = "/handle/{id}",method = {RequestMethod.DELETE})
       public ModelAndView handleDelete(@PathVariable("id") Integer id) {
       Date date = new Date();
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.addObject("date",date);
       modelAndView.setViewName("success");
       return modelAndView;
       }
      
    • web.xml中配置请求⽅式过滤器(将特定的post请求转换为put和delete请求)

      <!--配置springmvc请求⽅式转换过滤器,会检查请求参数中是否有_method参数,如果有就
      按照指定的请求⽅式进⾏转换-->
       <filter>
       	<filter-name>hiddenHttpMethodFilter</filter-name>
       	<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
       </filter>
       <filter-mapping>
      	 <filter-name>hiddenHttpMethodFilter</filter-name>
       	<url-pattern>/*</url-pattern>
       </filter-mapping>
      

拦截器(Interceptor)使用

过滤器、监听器、拦截器的对比
  • 过滤器(Filter):对Request请求起到过滤作用,作⽤在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进⾏过滤处理

  • 监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随Web应⽤的启动⽽启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停⽌⽽销毁

    作⽤⼀:做⼀些初始化⼯作,web应⽤中spring容器启动ContextLoaderListener

    作⽤⼆:监听web中的特定事件,⽐如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤HttpSessionLisener等

  • 拦截器(Interceptor):是SpringMVC、Struts等表现层框架⾃⼰的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器⽅法(Handler)。

    从配置的⻆度也能够总结发现:filter、listener是配置在web.xml中的,⽽interceptor是配置在表现层框架⾃⼰的配置⽂件中的

    • 在Handler业务逻辑执⾏之前拦截⼀次

    • 在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次

    • 在跳转⻚⾯之后拦截⼀次

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WZ9wLIjt-1597851658454)(SpringMVC拦截器工作图.jpg)]

拦截器的执行流程

在运⾏程序时,拦截器的执⾏是有⼀定顺序的,该顺序与配置⽂件中所定义的拦截器的顺序相关。 单个

拦截器,在程序中的执⾏流程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERT2sL4R-1597851658458)(SpringMVC拦截器执行流程.jpg)]

  • 程序先执行preHandle方法,如果该方法返回值为true,则继续向下执行处理器中的方法,否则将不再向下执行
  • 在业务处理器执行完毕后,未进行页面跳转之前,执行postHandler方法,然后通过DispatcherServlet向客户端返回相应
  • 在DispatcherServlet处理完毕,跳转完页面之后,执行afterComletion方法
多个拦截器的执行流程

多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截

器配置在前),在程序中的执⾏流程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lRK7Do0G-1597851658460)(SpringMVC多拦截器的执行流程.jpg)]

​ 从图可以看出,当有多个拦截器同时⼯作时,它们的preHandle()⽅法会按照配置⽂件中拦截器的配置

顺序执⾏,⽽它们的postHandle()⽅法和afterCompletion()⽅法则会按照配置顺序的反序执⾏。

  • 示例代码

    • 自定义拦截器

      import org.springframework.web.servlet.HandlerInterceptor;
      import org.springframework.web.servlet.ModelAndView;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      /**
      * ⾃定义springmvc拦截器
      */
      public class MyIntercepter01 implements HandlerInterceptor {
       /**
       * 会在handler⽅法业务逻辑执⾏之前执⾏
       * 往往在这⾥完成权限校验⼯作
       * @param request
       * @param response
       * @param handler
       * @return 返回值boolean代表是否放⾏,true代表放⾏,false代表中⽌
       * @throws Exception
       */
       @Override
       public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       	System.out.println("MyIntercepter01 preHandle......");
       return true;
       }
       /**
       * 会在handler⽅法业务逻辑执⾏之后尚未跳转⻚⾯时执⾏
       * @param request
       * @param response
       * @param handler
       * @param modelAndView 封装了视图和数据,此时尚未跳转⻚⾯呢,你可以在这⾥针对返回的
      数据和视图信息进⾏修改
       * @throws Exception
       */
       @Override
       public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
       	System.out.println("MyIntercepter01 postHandle......");
       }
       /**
       * ⻚⾯已经跳转渲染完毕之后执⾏
       * @param request
       * @param response
       * @param handler
       * @param ex 可以在这⾥捕获异常
       * @throws Exception
       */
       @Override
       public void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) throws Exception {
       	System.out.println("MyIntercepter01 afterCompletion......");
       }
      }
      
    • 注册SpringMVC拦截器

      <mvc:interceptors>
       <!--拦截所有handler-->
       <!--<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>-->
       
       <mvc:interceptor>
       <!--配置当前拦截器的url拦截规则,**代表当前⽬录下及其⼦⽬录下的所有url-->
       <mvc:mapping path="/**"/>
       <!--exclude-mapping可以在mapping的基础上排除⼀些url拦截-->
       <!--<mvc:exclude-mapping path="/demo/**"/>-->
       <bean class="com.xxx.xxx.interceptor.MyIntercepter01"/>
       </mvc:interceptor>
       <mvc:interceptor>
       <mvc:mapping path="/**"/>
       <bean class="com.xxx.xxx.interceptor.MyIntercepter02"/>
       </mvc:interceptor>
       
       </mvc:interceptors>
      
处理Multipart形式的数据
  • ⽂件上传所需jar包

    <!--⽂件上传所需jar坐标-->
    <dependency>
     <groupId>commons-fileupload</groupId>
     <artifactId>commons-fileupload</artifactId>
     <version>1.3.1</version>
    </dependency>
    
    
  • 配置文件上传解析器

    <!--配置⽂件上传解析器,id是固定的multipartResolver-->
    <bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
     <!--设置上传⼤⼩,单位字节-->
     <property name="maxUploadSize" value="1000000000"/>
    </bean>
    
  • 前端form

    <%--
     1 method="post"
     2 enctype="multipart/form-data"
     3 type="file"
    --%> <form method="post" enctype="multipart/form-data" action="/demo/upload">
     <input type="file" name="uploadFile"/>
     <input type="submit" value="上传"/>
    </form
    
  • 后台接收Handler

    @RequestMapping("/demo/upload")
    public String upload(MultipartFile uploadFile, HttpServletRequest request) throws IOException {
     // ⽂件原名,如xxx.jpg
     String originalFilename = uploadFile.getOriginalFilename();
     // 获取⽂件的扩展名,如jpg
     String extendName =
     originalFilename.substring(originalFilename.lastIndexOf(".") + 1,
     originalFilename.length());
     String uuid = UUID.randomUUID().toString();
     // 新的⽂件名字
     String newName = uuid + "." + extendName;
     String realPath =
     request.getSession().getServletContext().getRealPath("/uploads");
     // 解决⽂件夹存放⽂件数量限制,按⽇期存放
     String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
     File floder = new File(realPath + "/" + datePath);
     if(!floder.exists()) {
     floder.mkdirs();
     }
     uploadFile.transferTo(new File(floder,newName));
     //跳转到成功页面
     return "success";
    }
    
在控制器中处理异常
  • 自定义全局异常处理器

    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.servlet.ModelAndView;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @ControllerAdvice
    public class GlobalExceptionResolver {
     @ExceptionHandler(ArithmeticException.class)
     public ModelAndView handleException(ArithmeticException exception,HttpServletResponse response) {
     	ModelAndView modelAndView = new ModelAndView();
     	modelAndView.addObject("msg",exception.getMessage());
         //跳转到请求的错误处理页面
     	modelAndView.setViewName("error");
     	return modelAndView;
     }
    }
    
基于Flash属性的跨重定向请求数据传递

重定向时请求参数会丢失,我们往往需要重新携带请求参数,我们可以进⾏⼿动参数拼接如下:

return "redirect:handle01?name=" + name;

但是上述拼接参数的⽅法属于get请求,携带参数⻓度有限制,参数安全性也不⾼,此时,我们可以使⽤SpringMVC提供的flash属性机制,向上下⽂中添加flflash属性,框架会在session中记录该属性值,当跳转到⻚⾯之后框架会⾃动删除flash属性,不需要我们⼿动删除,通过这种⽅式进⾏重定向参数传递,参数⻓度和安全性都得到了保障,如下:

 * SpringMVC 重定向时参数传递的问题
 * 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A
 * url不会变,参数也不会丢失,⼀个请求
 * 重定向:A 找 B 借钱400,B 说我没有钱,你找别⼈借去,那么A ⼜带着400块的借钱需求找到C
 * url会变,参数会丢失需要重新携带参数,两个请求
 */
 @RequestMapping("/handleRedirect")
 public String handleRedirect(String name,RedirectAttributes redirectAttributes) {
 	//return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有局限
 	// addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在跳转到⻚⾯之后该属性销毁
 	redirectAttributes.addFlashAttribute("name",name);
 	return "redirect:handle01";
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值