Spring MVC

146 阅读12分钟

第一部分 Spring MVC应用

1. Spring MVC简介

1.1 MVC体系结构

三层架构

  • 表现层 也就是我们常说的web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用HTTP协议请求web层,web需要接收HTTP请求,完成HTTP响应。
    表现层包括展示层和控制层,控制层负责接收请求,暂时层负责结果的展示。 表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。 表现层的设计一般都是用MVC模型。(MVC是表现层的设计模式,和其他层没有关系)
  • 业务层 也就是我们常说的service层,它负责业务逻辑处理。web层依赖业务层,但是业务层不依赖web层。 业务层在业务梳理时可能会依赖持久层,如果要对数据持久化西药保证事务一致性。
  • 持久层 也就是我们常说的Dao层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库进行增删改查。
    MVC设计模式
    MVC全称Model View Controller,是模型(model)-视图(view)-控制前(Controller)的缩写,是一种用于设计创建Web应用程序表现层的模式。
  • Model(模型):模型包含业务模型和数据模型,数据模型用于封装数据,业务模型用于处理业务。
  • View(视图):通常指的就是我们的jsp或者html。作用一般就是展示数据的。通常视图是依据模型数据创建的
  • Controller(控制器):是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。 MVC提倡:每一层只编写自己的东西,不编写任何其他的代码;分层是为了解耦,解耦是为了维护方便和放工协作。

1.2 Spring MVC 是什么

SpringMVC 全名叫 Spring Web MVC,是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 SpringFrameWork 的后续产品。

Spring 框架 image.png

Spring MVC image.png

2. Spring Web MVC工作流程

2.1 SpringMVC 请求处理流程

image.png

流程说明:
第⼀步:⽤户发送请求⾄前端控制器DispatcherServlet
第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截 器(如果 有则⽣成)⼀并返回DispatcherServlet
第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步:处理器适配器执⾏Handler
第六步:Handler执⾏完成给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个 底层对象,包括Model和View
第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
第九步:视图解析器向前端控制器返回View
第⼗步:前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到request域
第⼗⼀步:前端控制器向⽤户响应结果

2.2 Spring MVC 9大主键

  • HandlerMapping (处理器映射器) HandlerMapping 是用来查找Handler的,也就是处理器,具体的表现形式可以是类,也可以是方法。比如:标注了@RequestMapping的每个方法都可以看成是一个Handler。Handler负责具体实际的请求处理,在请求到达后,HandlerMapping的作用便是扎到请求相应的处理器Handler和interceptor
  • HandlerAdapter(处理器适配器) HandlerAdapter是一个适配器。因为Spring MVC中Handler可以是任意形式的,只要能处理请求即可。但是吧请求交个Servlet的时候,由于Servlet的方法结构都是doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的Servlet处理方法调用Handler来进行处理,便是HandlerAdapter的职责。
  • HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。
  • ViewResolver ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀ 个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回 的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情: ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到 视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个 InternalResourceViewResolver,是针对 JSP 类型视图的。
  • RequestToViewNameTranslator RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据 ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName, 便要通过这个组件从请求中查找 ViewName。
  • LocaleResolver ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。 LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这 个组件也是 i18n 的基础。
  • ThemeResolver ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。 Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图 ⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资 源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有 ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和 具体的资源。
  • MultipartResolver MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实 现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还 可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就 是封装普通的请求,使其拥有⽂件上传的功能。
  • FlashMapManager FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完 post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然 可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得 呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯ 上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。

2.3 请求参数绑定

  • 默认支持Servlet API作为方法参数 当需要使⽤HttpServletRequest、HttpServletResponse、HttpSession等原⽣servlet对象时,直 接在handler⽅法中形参声明使⽤即可。
    /**
    * url: http://localhost:8000/demo/handle01
    * @ModelAttribute("name") String name
    * */
    @RequestMapping("/handle01")
    public ModelAndView handle01(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,HttpSession session) {
        Date date = new Date();

        // 返回服务器时间到前端页面
        // 封装了数据和页面信息的ModelAndView
        ModelAndView modelAndView = new ModelAndView();
        // addObject 其实是向请求与中request.setAttribute("date",date);
        modelAndView.addObject("date",date);
        // 视图信息 (封装跳转的页面信息)
        modelAndView.setViewName("success"); ///WEB-INF/ .jsp
        return modelAndView;
    }
  • 绑定简单类型参数 参数类型推荐使⽤包装数据类型,因为基础数据类型不可以为null 整型:Integer、int
    字符串:String
    单精度:Float、float
    双精度:Double、double
    布尔型:Boolean、boolean
    /*
    * url: http://localhost:8000/demo/handle01
    * @ModelAttribute("name") String name
    * */
    @RequestMapping("/handle01")
    public ModelAndView handle01(@RequestParam("ids") Integer id,Boolean flag) {
        Date date = new Date();
        // 返回服务器时间到前端页面
        // 封装了数据和页面信息的ModelAndView
        ModelAndView modelAndView = new ModelAndView();
        // addObject 其实是向请求与中request.setAttribute("date",date);
        modelAndView.addObject("date",date);
        // 视图信息 (封装跳转的页面信息)
        modelAndView.setViewName("success"); ///WEB-INF/ .jsp
        return modelAndView;
    }
  • 绑定Pojo类型参数
/*
* 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;
}
  • 绑定Pojo包装对象参数
/*
* 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;
}
  • 绑定时间类型参数
    /**
     * 绑定日期类型参数
     * 定义一个SpringMVC的类型转换器  接口,扩展实现接口接口,注册你的实现
     * @param birthday
     * @return
     */
    @RequestMapping("/handle06")
    public ModelAndView handle06(Date birthday) {
        Date date = new Date();
        System.out.println(birthday);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

自定义时间转换器

public class DateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String source) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date date = simpleDateFormat.parse(source);
            return date;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

注册自定义转换器

    <!--处理器映射器,处理器适配器-->
    <mvc:annotation-driven conversion-service="conversionServiceBean"></mvc:annotation-driven>
    <!--注册自定义转换器-->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="cn.percent.converter.DateConverter"></bean>
            </set>
        </property>
    </bean>
  • Restful风格支持
/*
* restful get /demo/handle/15
*/
@RequestMapping(value = "/handle/{id}",method ={RequestMethod.GET})
public ModelAndView handleGet(@PathVariable("id") Integer id)

/*
* restful put /demo/handle/15/lisi
*/
@RequestMapping(value = "/handle/{id}/{name}",method ={RequestMethod.PUT})
public ModelAndView handlePut(@PathVariable("id") Integerid,@PathVariable("name") String username)

/*
* restful delete /demo/handle/15
*/
@RequestMapping(value = "/handle/{id}",method ={RequestMethod.DELETE})
public ModelAndView handleDelete(@PathVariable("id") Integer id)

/*
* restful post /demo/handle
*/
@RequestMapping(value = "/handle",method = {RequestMethod.POST})
public ModelAndView handlePost(String username)

web.xml中配置请求⽅式过滤器(将特定的post请求转换为put和delete请求)

  <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>
  • RequestEntity
    @RequestMapping("/handle07")
    public ModelAndView handle07(RequestEntity<JSONObject> requestEntity) throws IOException {

        System.out.println(requestEntity.getBody());
        System.out.println(requestEntity.getBody().get("name"));
        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

第二部分 Spring MVC 高级技术

2. 拦截器(Inteceptor)使用

1.1 监听器、过滤器和拦截器对比

  • Servlet:处理Request请求和Response响应
  • 过滤器(Filter):对Request请求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进⾏过滤处理
  • 监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随Web应⽤的启动⽽启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停⽌⽽销毁
  • 拦截器(Interceptor):是SpringMVC、Struts等表现层框架⾃⼰的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器⽅法(Handler)

image.png

1.2 拦截器的执行流程

image.png

1.3 多个拦截器的执行流程

image.png

  • springmvc.xml
   <!--注册拦截器-->
    <mvc:interceptors>
        <!--拦截所有handler-->
<!--        <bean class="cn.percent.interceptor.MyIntercepter01"></bean>-->
        <mvc:interceptor>
            <!--配置当前拦截器的url拦截规则,**代表当前目录下及其子目录下的所有url-->
            <mvc:mapping path="/**"/>
            <!--exclude-mapping可以在mapping的基础上排除一些url拦截-->
            <mvc:exclude-mapping path="/demo/**"/>
            <bean class="cn.percent.interceptor.MyIntercepter01"/>
        </mvc:interceptor>


        <mvc:interceptor>
            <mvc:mapping path="/**"/>

            <bean class="cn.percent.interceptor.MyIntercepter02"/>
        </mvc:interceptor>
    </mvc:interceptors>
  • 拦截器1
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("MyInterceptor01 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("MyInterceptor01 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("MyInterceptor01 afterCompletion");
    }
}

  • 拦截器2
public class MyIntercepter02 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor02 preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor02 postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor02 afterCompletion");
    }
}

拦截结果:

image.png 拦截器1排除了/demo url请求,所以只有拦截器2拦截到请求

2. 处理multipart形式的数据

  • 配置文件上传解析器
    <!--文件上传解析器
        id固定为multipartResolver
    -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--设置上传文件大小上线,单位是字节,-1代表没有限制也是默认的-->
        <property name="maxUploadSize" value="5000000">

        </property>
    </bean>
  • 后台接收
    @RequestMapping(value = "/upload")
    public ModelAndView upload(MultipartFile uploadFile,HttpSession session) throws IOException {
        // 处理上传文件
        // 重命名,原名123.jpg,获取后缀
        String originalFilename = uploadFile.getOriginalFilename(); // 获取原始名
        String ext = originalFilename.substring((originalFilename.lastIndexOf(".") + 1), originalFilename.length());
        String newName = UUID.randomUUID().toString() + "." + ext;

        // 存储到指定的目录 /uploads
        String realPath = session.getServletContext().getRealPath("/uploads");
        String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        File folder = new File(realPath + "/" + datePath);
        if (!folder.exists()){
            folder.mkdir();
        }
        uploadFile.transferTo(new File(folder,newName));

        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

3. 在控制前中处理异常

// 可以优雅的捕获所有Controller异对象handler方法抛出的异常 (ArithmeticException异常)
@ControllerAdvice
public class GlobalExceptionResolver {
    @ExceptionHandler(ArithmeticException.class)
    public void handleException(ArithmeticException exception, HttpServletResponse response){
        try {
            response.getWriter().write("===>" + exception.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4. 基于flash属性的跨重定向请求数据传递

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

第三部分 手写MVC框架

程序流程

image.png

``

git: https://gitee.com/FearlessYMF/spring-mvc-homework.git