SpringMVC

SpringMVC简介

经典三层(代码架构):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d5e5GU7V-1592444813206)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1591780564534.png)]

  • 表现层

    表现层就是web层,它接受客户端发送的请求,向客户端响应结果,通常客户端使用http协议请求web端,web接收请求,然后响应请求。

    表现层包括展示层和控制层:

    • 展示层:负责结果的展示。
    • 控制层:负责接收请求。

表现层一般都是使用MVC模型。

MVC模式(代码的组织方式/形式):

M:model模型(数据模型[pojo、vo、po]+业务模型[业务模型])

​ V:view视图(jsp、html)

​ C:controller控制器(servlet)

  • 业务层

    也就是我们说的service层,主要负责业务逻辑的处理。

  • 持久层

    负责数据的持久化,就是负责和数据进行交互,对数据表进行增删改查。

springMVC概念

​ SpringMVc 是一种基于java语言实现的MVC设计模型的请求驱动类型的轻量级的web框架。Servlet、Struts2需要实现接口,而springMVC只需要加注解就可以让一个java类处理请求。SpringMVC通过一套简单的注解,让一个简单的java类成为处理请求的控制器,而无需实现任何接口,同时它还支持restful风格的请求。SpringMVC本质上可以看成是对servlet的封装,简化了我们对servlet的开发。

SpringMVC的作用包括接受请求、响应请求、跳转页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DUkwyDRB-1592444813208)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1591846288942.png)]

SpringMVC工作流程

开发过程

1.配置DispatcherServlet前端控制器

​ 2.开发处理具体业务逻辑的Handler(@RequestMapping 、@Controller)

​ 3.xml配置controller扫描路径、配置SpringMVC三大组件(视图解析器、处理适配器、处理适配器)

SpringMVC请求处理流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fsf3yLMU-1592444813210)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1591881545025.png)]

1、用户发送请求到前端控制器DispatcherServlet。

2、前端控制器DispatcherServlet接受到请求后调用处理映射器HandlerMapping。

3、处理映射器HandlerMapping根据url找到具体的Handler(后端控制器),生成处理器对象和处理器拦截器(如果有,则生成)一并返回给DispatcherServlet前端控制器。

4、前端控制器DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler(controller中的方法)。

5、处理器适配器HandlerAdapter执行Handler。

6、Handler处理完成之后给处理器适配器HandlerAdaper返回ModelAndView。

7、处理器适配器向前端控制器DispatcherServlet返回ModelAndView,ModelAndView是SpringMVC的一个底层对象,包括Model和View对象。

8、前端控制器请求视图解析器进行视图解析,根据逻辑视图名称来解析出真正的物理视图。

9、视图解析器向前端控制器返回视图。

10、前端控制器DispatcherServlet进行视图渲染,就是将模型数据填充的request域中。

11、前端控制器向客户端响应结果。

springMVC的九大组件

1、HandlerMapping 处理器映射器

HandlerMapping是用来查找处理器Handler的;Handler具体可以是一个类也可以是一个方法。比如标注了@RequestMapping的每一个方法都可以看成是一个Handler;类的话是最原始的定义处理器的方法比如如下:

package com.wbst.canteenorder.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author 梁科
 * date2020/6/11 21:40
 * description 实现controller类并复写方法 实现处理器Handler
 */
public class DemoController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        return null;
    }
}

Handler是负责处理具体的实际请求处理,在请求到达之后,HandlerMapping的作用就是找到对应的处理器Handler。

2、HandlerAdapter处理器适配器

HandlerAdapter的作用就是将Handler的形式转换为Servlet所认识的形式。在SpringMVC中Handler的形式是任意的。而Servlet的方法结构是doService(ServletRequest req,HttpSerletResopnse res)形式的,要让固定的Servlet处理方法调用Handler来进行处理就,就是HandlerAdapter的责任。

3、HandlerExceptionResolver 异常处理器

HandlerExceptionResolver用于处理Handler产生的异常,它的作用就是根据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将ModelAndView渲染成页面。

4、ViewResolver 视图解析器

视图解析器用于将String类型的视图名和Locale解析View类型的视图。ViewResolver只有⼀个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情:ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。

5、 RequestToViewNameTranslator

RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据
ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,
便要通过这个组件从请求中查找 ViewName。

6、LocaleResolver

ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。
LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这
个组件也是 i18n 的基础。

7、 ThemeResolver

ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。
Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图
⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资
源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有
ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,
ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和
具体的资源。

8、 MultipartResolver

MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实
现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还
可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就
是封装普通的请求,使其拥有⽂件上传的功能。

9、 FlashMapManager

FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完 post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然 可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得 呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯ 上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。

SpringMVC前端控制器Url-pattern配置

SpringMVC前端控制器的url-pattern的配置:

  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--拦截匹配规则的url请求,进入springmvc框架处理-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

url -patternde 配置方式

方式一:带后缀的方式,比如 .do、*.aaa等方式*

这种方式比较精确、方便。

**方式二:/ 的方式 **

这种方式不会拦截.jsp,但是会拦截.html、.png等静态资源(除了servlet和jsp之外的js、css等)。

1、为什么  /   会拦截静态资源?
因为Tomcat容器中有一个web.xml(父),在自己的项目中也有一个web.xml(子)。它们是一个继承关系。在父web.xml中有一个DefaultServlet(处理静态资源的),其url-pattern是一个 / ,此时我们自己的url-pattern配置的也是 / ,复写了父web.xml的配置。
2、为什么不会拦截.jsp?
因为在父web.xml中有个JSPServlet,这个Servlet会拦截.jsp文件。而在我们自己的项目中我们没有复写改Servlet,所以springMVC不会拦截.jsp。jsp文件的处理会交给父web.xml。
3、解决拦截静态资源办法
见3.2节

方式三:/* 拦截所有。

静态资源配置

1、在SpringMVC配置文件中加静态资源配置(交给Tomcat处理)

<mvc:default-servlet-handler/>

原理:

添加了这个标签之后,会在SpringMVC上下文中生成一个DefaultServletHTTPRequestHandler对象,该对象会将进入DispatcherServlet的请求资源进行筛选,如果发现是一个静态资源路径,会将请求交给Tomcat中的默认的DefaultServlet处理,如果不是静态资源请求,则将继续由SpringMVC框架处理。

缺点:

这种解决方式要求静态资源必须放在webapp文件夹下面,不能放在其他地方。

2、在SpringMVC配置文件中添加静态资源配置(由SpringMVC自己处理)

<!--静态资源放在resources下面-->
<mvc:resources location="classpath:/"  mapping="/resources/**"/>
<!--静态资源放在WEB-INF/js下面-->
 <mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>

输出数据机制至Model、Map、ModelMap

/**
 * @author 应癫
 *
 */
@Controller
@RequestMapping("/demo")
public class DemoController  {
    /**
     * url: http://localhost:8080/demo/handle01
     */
    @RequestMapping("/handle01")
    public ModelAndView handle01(@ModelAttribute("name") String name) {
        Date date = new Date();// 服务器时间
        // 返回服务器时间到前端页面
        // 封装了数据和页面信息的 ModelAndView
        ModelAndView modelAndView = new ModelAndView();
        // addObject 其实是向请求域中request.setAttribute("date",date);
        modelAndView.addObject("date",date);
        // 视图信息(封装跳转的页面信息) 逻辑视图名
        modelAndView.setViewName("success");
        return modelAndView;
    }

    /**
     * 直接声明形参ModelMap,封装数据
     * url: http://localhost:8080/demo/handle11
     *
     * modelmap:class org.springframework.validation.support.BindingAwareModelMap
     */
    @RequestMapping("/handle11")
    public String handle11(ModelMap modelMap) {
        Date date = new Date();// 服务器时间
        modelMap.addAttribute("date",date);
        System.out.println("=================modelmap:" + modelMap.getClass());
        return "success";
    }
    
    /**
     * 直接声明形参Model,封装数据
     * url: http://localhost:8080/demo/handle12
     * model:class org.springframework.validation.support.BindingAwareModelMap
     */
    @RequestMapping("/handle12")
    public String handle12(Model model) {
        Date date = new Date();
        model.addAttribute("date",date);
        System.out.println("=================model:" + model.getClass());
        return "success";
    }
    
    /**
     * 直接声明形参Map集合,封装数据
     * url: http://localhost:8080/demo/handle13
     * map:class org.springframework.validation.support.BindingAwareModelMap
     */
    @RequestMapping("/handle13")
    public String handle13(Map<String,Object> map) {
        Date date = new Date();
        map.put("date",date);
        System.out.println("=================map:" + map.getClass());
        return "success";
    }
}

从打印结果可以看出无论是使用Model还是Map还是ModelMap,运行时都是BindingAwareModelMap。

关系图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AsQoWQYu-1592444813211)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592043957395.png)]

请求参数绑定

原⽣servlet接收⼀个整型参数:

1)String ageStr = request.getParameter(“age”);

  1. Integer age = Integer.parseInt(ageStr);

SpringMVC框架对Servlet的封装,简化了servlet的很多操作 SpringMVC在接收整型参数的时候,直接在Handler⽅法中声明形参即可

@RequestMapping("xxx") 
public String handle(Integer age) { 
    System.out.println(age); 
} 
  • 绑定日期类型的参数(需要自定义类型转换器)

    • 后台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;
           }
      

      自定义处理器

      /**
      * @author 应癫
      * ⾃定义类型转换器
      * 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> 
    

对Restful请求风格的支持

什么事Restful风格?

restful风格是一种web软件架构风格,它不是标准也不是协议,它倡导的是一个资源定位及资源操作的风格。

示例:

1、没有restful的请求url:

http://localhost:8080/user/queryUserById.action?id=3

url中定义了动作(操作),参数具体锁定到操作的是谁

2、 有了rest⻛格之后 :

Restful中,认为互联⽹中的所有东⻄都是资源,既然是资源就会有⼀个唯⼀的uri标识它,代表它

http://localhost:8080/user/3 代表的是id为3的那个⽤户记录(资源)

3、 根据请求⽅式不同,代表要做不同的操作

get 查询,获取资源

post 增加,新建资源

put 更新

delete 删除资源

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

RESTful ⻛格 URL:互联⽹所有的事物都是资源,要求URL中只有表示资源的名称,没有动词。

RESTful⻛格资源操作:使⽤HTTP请求中的method⽅法put、delete、post、get来操作资源。分别对 应添加、删除、修改、查询。不过⼀般使⽤时还是 post 和 get。put 和 delete⼏乎不使⽤。

SpringMVC对Rest风格的支持

Spring MVC ⽀持 RESTful ⻛格请求,具体讲的就是使⽤ @PathVariable 注解获取 RESTful ⻛格的请求 URL中的路径变量。

  • 前端页面
<div>
    后台Handler⽅法
     <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>
     <filterclass>org.springframework.web.filter.HiddenHttpMethodFilter</filterclass>
 </filter>
 <filter-mapping>
     <filter-name>encoding</filter-name>
     <url-pattern>/*</url-pattern>
 </filter-mapping>
 <filter-mapping>
     <filter-name>hiddenHttpMethodFilter</filter-name>
     <url-pattern>/*</url-pattern>
 </filter-mapping>

Ajax JSON交互

交互:两个⽅向 1)前端到后台:前端ajax发送json格式字符串,后台直接接收为pojo参数,使⽤注解@RequstBody 2)后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串,使⽤注解 @ResponseBody

SpringMVC高级组件之监听器、过滤器及拦截器

监听器、过滤器和拦截器对⽐

  • Servlet:处理request请求和response响应。

  • 过滤器(Filter):对request请求起到过滤作用,作用在servlet之前,如果配置为/*,则会对所有资源进行拦截。

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

    作用一: 做一些初始化动作,web应用spring容器的启动ContextListener。

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

  • 拦截器(interceptor):拦截器是SpringMVC、Struts等表现层框架自己的,不会拦截静态资源的访问。只会拦截Handler。

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

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

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

在跳转⻚⾯之后拦截⼀次

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vfbmryW-1592444813213)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592101339102.png)]

拦截器(Interceptor)

单个拦截器执行流程

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E8F7oPdC-1592444813214)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592103022932.png)]

1) 程序先执⾏preHandle()⽅法,如果该⽅法的返回值为true,则程序会继续向下执⾏处理器中的⽅ 法,否则将不再向下执⾏。

2) 在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()⽅法,然后会通过 DispatcherServlet向客户端返回响应。

3) 在DispatcherServlet处理完请求后,才会执⾏afterCompletion()⽅法。

多个拦截器执行流程

多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截 器配置在前),在程序中的执⾏流程如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8BVqjfX9-1592444813214)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592103118760.png)]

从图可以看出,当有多个拦截器同时⼯作时,它们的preHandle()⽅法会按照配置⽂件中拦截器的配置 顺序执⾏,⽽它们的postHandle()⽅法和afterCompletion()⽅法则会按照配置顺序的反序执⾏。

示例代码

1、自定义SpringMVC拦截器

package com.lagou.edu.interceptor;
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......");
 }
}

2、注册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.lagou.edu.interceptor.MyIntercepter01"/>
     </mvc:interceptor>
     <mvc:interceptor>
         <mvc:mapping path="/**"/>
         <bean class="com.lagou.edu.interceptor.MyIntercepter02"/>
     </mvc:interceptor>
 </mvc:interceptors>

SpringMVC对文件上传的支持

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>

后台Handler

@RequestMapping("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";
}

SpringMVC异常处理机制

package com.lagou.edu.controller;
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;

// 可以让我们优雅的捕获所有Controller对象handler⽅法抛出的异常
@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;
 }
}

Spring MVC重定向参数传递

**请求转发:**A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A

​ url不会变,参数也不会丢失,一个请求

重定向: A 找 B 借钱400,B 说我没有钱,你找别人借去,那么A 又带着400块的借钱需求找到C

​ url会变,参数会丢失需要重新携带参数,两个请求

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

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

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

@RequestMapping("/handleRedirect")
 public String handleRedirect(String name,RedirectAttributes redirectAttributes) {
     //return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有局限
     // addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在跳转到⻚⾯之后该属性销毁
     redirectAttributes.addFlashAttribute("name",name);
     return "redirect:handle01";
 }

手写SpringMVC框架

流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-06CKleft-1592444813215)(C:\Users\18180408327\AppData\Roaming\Typora\typora-user-images\1592120043515.png)]

  • LgDispatcherServlet
package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.annotations.LagouAutowired;
import com.lagou.edu.mvcframework.annotations.LagouController;
import com.lagou.edu.mvcframework.annotations.LagouRequestMapping;
import com.lagou.edu.mvcframework.annotations.LagouService;
import com.lagou.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LgDispatcherServlet extends HttpServlet {

    
    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>(); // 缓存扫描到的类的全限定类名

    // ioc容器
    private Map<String,Object> ioc = new HashMap<String,Object>();

    // handlerMapping
    //private Map<String,Method> handlerMapping = now HashMap<>(); // 存储url和Method之间的映射关系
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1 加载配置文件 springmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2 扫描相关的类,扫描注解
        doScan(properties.getProperty("scanPackage"));

        // 3 初始化bean对象(实现ioc容器,基于注解)
        doInstance();

        // 4 实现依赖注入
        doAutoWired();

        // 5 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
        initHandlerMapping();

        System.out.println("lagou mvc 初始化完成....");

        // 等待请求进入,处理请求
    }

    
    /*
        构造一个HandlerMapping处理器映射器
        最关键的环节
        目的:将url和method建立关联
     */
    private void initHandlerMapping() {
        if(ioc.isEmpty()) {return;}

        for(Map.Entry<String,Object> entry: ioc.entrySet()) {
            // 获取ioc中当前遍历的对象的class类型
            Class<?> aClass = entry.getValue().getClass();
            if(!aClass.isAnnotationPresent(LagouController.class)) {continue;}
            String baseUrl = "";
            if(aClass.isAnnotationPresent(LagouRequestMapping.class)) {
                LagouRequestMapping annotation = aClass.getAnnotation(LagouRequestMapping.class);
                baseUrl = annotation.value(); // 等同于/demo
            }
            // 获取方法
            Method[] methods = aClass.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                //  方法没有标识LagouRequestMapping,就不处理
                if(!method.isAnnotationPresent(LagouRequestMapping.class)) {continue;}
                // 如果标识,就处理
                LagouRequestMapping annotation = method.getAnnotation(LagouRequestMapping.class);
                String methodUrl = annotation.value();  // /query
                String url = baseUrl + methodUrl;    // 计算出来的url /demo/query
                // 把method所有信息及url封装为一个Handler
                Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));
                // 计算方法的参数位置信息  // query(HttpServletRequest request, HttpServletResponse response,String name)
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];
                    if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        // 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse
                 handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j);
                    }else{
                        handler.getParamIndexMapping().put(parameter.getName(),j);  // <name,2>
                    }
                }
                // 建立url和method之间的映射关系(map缓存起来)
                handlerMapping.add(handler);
            }
        }
    }

    //  实现依赖注入
    private void doAutoWired() {
        if(ioc.isEmpty()) {return;}
        // 有对象,再进行依赖注入处理
        // 遍历ioc中所有对象,查看对象中的字段,是否有@LagouAutowired注解,如果有需要维护依赖注入关系
        for(Map.Entry<String,Object> entry: ioc.entrySet()) {
            // 获取bean对象中的字段信息
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            // 遍历判断处理
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];   //  @LagouAutowired  private IDemoService demoService;
                if(!declaredField.isAnnotationPresent(LagouAutowired.class)) {
                    continue;
                }

                // 有该注解
                LagouAutowired annotation = declaredField.getAnnotation(LagouAutowired.class);
                String beanName = annotation.value();  // 需要注入的bean的id
                if("".equals(beanName.trim())) {
                    // 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入)  IDemoService
                    beanName = declaredField.getType().getName();
                }
                // 开启赋值
                declaredField.setAccessible(true);
                try {
                    declaredField.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    // ioc容器
    // 基于classNames缓存的类的全限定类名,以及反射技术,完成对象创建和管理
    private void doInstance()  {
        if(classNames.size() == 0) return;
        try{
            for (int i = 0; i < classNames.size(); i++) {
                String className =  classNames.get(i);  // com.lagou.demo.controller.DemoController
                // 反射
                Class<?> aClass = Class.forName(className);
                // 区分controller,区分service'
                if(aClass.isAnnotationPresent(LagouController.class)) {
                    // controller的id此处不做过多处理,不取value了,就拿类的首字母小写作为id,保存到ioc中
                    String simpleName = aClass.getSimpleName();// DemoController
                    String lowerFirstSimpleName = lowerFirst(simpleName); // demoController
                    Object o = aClass.newInstance();
                    ioc.put(lowerFirstSimpleName,o);
                }else if(aClass.isAnnotationPresent(LagouService.class)) {
                    LagouService annotation = aClass.getAnnotation(LagouService.class);
                    //获取注解value值
                    String beanName = annotation.value();
                    // 如果指定了id,就以指定的为准
                    if(!"".equals(beanName.trim())) {
                        ioc.put(beanName,aClass.newInstance());
                    }else{
                        // 如果没有指定,就以类名首字母小写
                        beanName = lowerFirst(aClass.getSimpleName());
                        ioc.put(beanName,aClass.newInstance());
                    }
                    // service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (int j = 0; j < interfaces.length; j++) {
                        Class<?> anInterface = interfaces[j];
                        // 以接口的全限定类名作为id放入
                        ioc.put(anInterface.getName(),aClass.newInstance());
                    }
                }else{
                    continue;
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 首字母小写方法
    public String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }
    // 扫描类
    // scanPackage: com.lagou.demo  package---->  磁盘上的文件夹(File)  com/lagou/demo
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for(File file: files) {
            if(file.isDirectory()) { // 子package
                // 递归
                doScan(scanPackage + "." + file.getName());  // com.lagou.demo.controller
            }else if(file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    // 加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       /** // 处理请求:根据url,找到对应的Method方法,进行调用
        // 获取uri
//        String requestURI = req.getRequestURI();
//        Method method = handlerMapping.get(requestURI);// 获取到一个反射的方法
        // 反射调用,需要传入对象,需要传入参数,此处无法完成调用,没有把对象缓存起来,也没有参数!!!!改造initHandlerMapping();
//        method.invoke() //

问题抛出
*/
        // 根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list))
        Handler handler = getHandler(req);
        if(handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }
        // 参数绑定
        // 获取所有参数类型数组,这个数组的长度就是我们最后要传入的args数组的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的)
        Object[] paraValues = new Object[parameterTypes.length];
        // 以下就是为了向参数数组中塞值,而且还得保证参数的顺序和方法中形参顺序一致
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 遍历request中所有参数  (填充除了request,response之外的参数)
        for(Map.Entry<String,String[]> param: parameterMap.entrySet()) {
            // name=1&name=2   name [1,2]
            String value = StringUtils.join(param.getValue(), ",");  // 如同 1,2
            // 如果参数和方法中的参数匹配上了,填充数据
            if(!handler.getParamIndexMapping().containsKey(param.getKey())) {continue;}
            // 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
            Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置
            paraValues[index] = value;  // 把前台传递过来的参数值填充到对应的位置去
        }
        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0
        paraValues[requestIndex] = req;
        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1
        paraValues[responseIndex] = resp;
        // 最终调用handler的method属性
        try {
            handler.getMethod().invoke(handler.getController(),paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if(handlerMapping.isEmpty()){return null;}
        String url = req.getRequestURI();
        for(Handler handler: handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if(!matcher.matches()){continue;}
            return handler;
        }
        return null;
    }
}

  • Handler类
package com.lagou.edu.mvcframework.pojo;


import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;


/**
 * 封装handler方法相关的信息
 */
public class Handler {

    private Object controller; // method.invoke(obj,)

    private Method method;

    private Pattern pattern; // spring中url是支持正则的

    private Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>


    public Handler(Object controller, Method method, Pattern pattern) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.paramIndexMapping = new HashMap<>();
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public Map<String, Integer> getParamIndexMapping() {
        return paramIndexMapping;
    }

    public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
        this.paramIndexMapping = paramIndexMapping;
    }
}

  • web.xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>lgoumvc</servlet-name>
    <servlet-class>com.lagou.edu.mvcframework.servlet.LgDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>springmvc.properties</param-value>
    </init-param>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>lgoumvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

) {
this.method = method;
}

public Pattern getPattern() {
    return pattern;
}

public void setPattern(Pattern pattern) {
    this.pattern = pattern;
}

public Map<String, Integer> getParamIndexMapping() {
    return paramIndexMapping;
}

public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
    this.paramIndexMapping = paramIndexMapping;
}

}


* web.xml

```xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>lgoumvc</servlet-name>
    <servlet-class>com.lagou.edu.mvcframework.servlet.LgDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>springmvc.properties</param-value>
    </init-param>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>lgoumvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值