SpringMVC开发手册

本片博客记录SpringMvc带给了我们什么?,图解运行流程,以及开发中,常用的注解

SpringMvc基于servlet设计,用于处理用户的请求,基于方法级别拦截,参数通过入参传递,处理器设计为单例模式,和Spring无缝整合, 分离了 控制器,模型对象,让它们更容易被定制

999

主要组成部分:

  • DispatcherServlet:

前端控制器:所有的请求,都会首先被他拦截到,统一给请求分发处理的Handler

  • HandlerMapping

处理器映射器: 识别控制器中的注解,目的是找到具体的和Url对应的处理方法

  • HanderAdapter

处理器适配器,实例化控制器Controller,调用具体的方法,处理用户发来的请求

  • Controller

控制器, 用来处理用户的请求,返回ModelAndView给前端控制器

  • ViewResolver

视图解析器: 解析视图(ModeAndView), 把ModelAndView里面的逻辑视图编程一个正真的View对象,并把Model从ModelAndView中取出来

宏观上看,DispatcherServilet是整个web应用的控制器, 微观上,controller是单个http请求处理的控制器

@RequestMapping()

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

	String name() default "";

	@AliasFor("path")
	String[] value() default {};

	@AliasFor("value")
	String[] path() default {};
	
   // 映射请求方式(get post put delete)
	RequestMethod[] method() default {};
   // 
	String[] params() default {};
   // 映射请求参数
	String[] headers() default {};

	String[] consumes() default {};
	
	String[] produces() default {};

}
  • 支持标注在类上和方法上
    • 最常用标注在方法上,用于映射处理前端发送过来的URL
    • 如果我们同时把它俩加载类和方法上, 那么请求路径就是两者的路径使用 / 分隔开
 //         映射url                   映射方法为POST                     指定必须包含username  并且age!=10
@RequestMapping(value = "sayHello", method = RequestMethod.POST, params = {"username", "age!=10"})
    public String sayHello() {
        System.out.println("来到了sayHello");
        return "success";
    }

@RequestMapping 路径支持通配符

  • ? 一个字符
  • *任意字符
  • ** 多层路径
/**
 * 测试 ant风格的占位符
 * @return
 */
@RequestMapping("/textAntPath/*/text") 
public String textAntPath() {
    System.out.println("v");
    return "success";
}

@PathVariable 与 @RequestParam()

  • @PathVariable 映射URL绑定的占位符(一般在我们添加在方法上的注解上通过{XXX} 占位 )

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191019192400698-1616157266.png

  • @RequestParam取出来的是请求参数,发出的url格式如下:

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191019192359494-1870161889.png


/**
 *  1. 假如:@RequestParam("password") url中并不存在的话,  报错400
 *      1.1, 设置@RequestParam的required=false
 *  2. 假如只有第一个:@RequestParam("username")String username,String password ( password同样会被装配进去值)
 *

SpringMvc的REST风格

浏览器的form表单,只是支持GET POST请求,而Delete put的方法,是不支持的, 我们使用rest风格的提交表单时, 会被SpringMvc的 HiddenHttpMethodFilter拦截下来,过滤处理成标准的http方法,从而支持了 delete put

修改配置文件 web.xml

<!--配置  HiddenHttpMethodFilter   开启RestFul风格  -->
<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>

jsp

<br>
<a href="text01/1">测试get请求</a>
<br>
<form action="text02" method="post">
    <input type="text" name="id">
    <input type="submit" value="点击发送post请求">
</form>
<br>
<form action="text03/8" method="post">
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="点击发送put请求">
</form>
<br>
<form action="text04/7" method="post">

    <%-- 我们需要添加隐藏域, name="_method"  不能不写,Spring会拿到它的信息,从而将post转换成 delete --%>
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="点击发送delete请求">
</form>

控制器

@RequestMapping(value="text01/{id}",method = RequestMethod.GET)
public String  text01(@PathVariable("id")Integer id){
    System.out.println("获取id=="+id+"的信息");
    return "success";
}

@RequestMapping(value = "text02",method = RequestMethod.POST)
public String  text02(@RequestParam Integer id){
    System.out.println("新增id=="+id+"的信息");
    return "success";
}

@RequestMapping(value = "text03/{id}",method = RequestMethod.PUT)
public String  text03(@PathVariable("id") Integer id){
    System.out.println("修改id=="+id+"的信息");
    return "success";
}

@RequestMapping(value = "text04/{id}",method = RequestMethod.DELETE)
public String  text04(@PathVariable("id") Integer id){
    System.out.println("删除id=="+id+"的信息");
    return "success";
}

@RequestHeader()

映射请求头的信息到入参位置,不同浏览器的请求头的细节可能是不同的

@RequestMapping("/text")
public void text(@RequestHeader(value="Accept-Langnage") String s1){
    //
}

@CookieValue()

很常用,在微服务的安全验证模块,安全中心会给满足条件的用户办法token, 存放到浏览器的cookie里面, 用户再次访问就会携带cookie,我们通过这个注解取出cookie的值,进行安全验证

@RequestMapping("textCookieValue")
public String textCookieValue(@CookieValue("JSESSIONID")String jessionId){
    System.out.println("cookieValue=="+jessionId);
    return "success";
}

POJO绑定请求参数

很多时候前端提交的表单对应着我们将要持久化对象,那么使用pojo绑定参数无疑是一件超赞的事

springMvc支持 按照请求参数名和pojo属性进行自定匹配,自动为该对象填充属性,支持级联属性

JSP


<form action="textPojo">
    用户名: <input type="text" name="username">
    密码: <input type="text" name="password">
    邮箱: <input type="text" name="email">
    <%-- 级联属性 --%>
    省: <input type="text" name="adress.province">
    市: <input type="text" name="adress.city">
     <input type="submit" value="提交">
</form>

pojo

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Person {
    String username;
    String password;
    String email;
    Adress adress;

}

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Adress {

    String province;
    String city;

}

控制器

springmvc 会自定为我们的入参绑定上前端表单上的数据

@RequestMapping("textPojo")
public String textPojo(Person person){
  System.out.println(person);
  return "success";
}

支持Servlet原生API

@RequestMapping("textServletAPi")
public String textServletApi(HttpServletRequest request, HttpServletResponse response) {
    return "success";
}

处理模型数据

ModelAndView

/**
 * springMvc会把ModelAndView的Model放入request域对象中
 * @return
 */
@RequestMapping("textModelAndView")
public ModelAndView textModelAndView(){
    String viewName="success";
    ModelAndView modelAndView = new ModelAndView();
    // 设置视图名
    modelAndView.setViewName(viewName);
    // 添加模型数据
    modelAndView.addObject("日期",new Date());
    return modelAndView;
}

Map

说白了,就是方法的入参位置可以添加一个 Map或者Model类型的参数,mvc会把隐藏的模型引用传递给这个入参, 从而是开发者可以通过这个入参访问模型中的所有数据,同是可以添加新数据

@RequestMapping("textMap")
public String textMap(Map<String, Object> map) {
    map.put("names", Arrays.asList("zhangsan", "lisi"));
    return "success";

}

@SessionAttributes

如果我们希望多个请求之间共享某个模型属性数据,那么我们使用@SeesionAttribute,她会把我们存放到作用域中的信息备份到Session中

// 她会把我们存放到 作用域中的数据,备份到Session
@SessionAttributes("user",types = String.class)  
public class HelloController {

@RequestMapping("textSessionAttribute")
public String textSeesionAttribute(Map<String,Object> map){
    Adress adress = new Adress("山东","XXX");
    Person zhangsan = new Person("zhangsan", "234234", "4646@qq.com", adress);
    map.put("user",zhangsan);  // 放入请求域
    return "success";
}

在前几个低版本的SpringMvc中版本中,如果本类标记上了@SessionAttribute但是却没有标记@ModelAttribute的方法,服务器报错500;

@ModelAttribute

这个注解可以帮我解决这样一种情况, 更新操作,很多时候,我们只是针对表中的其中几个字段进行更新, 另外一些字段不需要更新(比如插入时间),那怎么做? 如果自己new对象的话,前端的数据绑定到我们new的对象上,插入时间就会空着,这时已更新,原来的插入时间就会被覆盖,于是我们不new ,通常使用@ModelAttribute注解标注方法上,先查询数据库,得到有插入时间字段的对象

控制器(标注在方法上):

// 添加上这个注解的方法,会被SpringMvc拦截, 所有的方法在调用前都会先执行这个方法,把查询出来的信息放到作用域
  //  这样SpringMvc把前端传递过来的信息,赋值给 作用域里面user   --  狸猫换太子,
  //  用户得到的就是被 狸猫
@ModelAttribute
public void textModelAttribute(
        @RequestParam(value = "id",required = false) Integer id,
        Map<String,Object> map){

      // 判断,如果id不为空, 就表示用户想修改信息
      // 于是,我们的任务就是 把查数据库,把用户的信息查出来,通过Map放到作用域里面, 谁用,谁取
     if(null!=id){
         //模拟查库
         System.out.println("查询数据库,获取user信息");
         Person person = new Person("1", "lisi", "999", "8989@qq.com", new Adress("山西", "北京"));

         // 注意点: 在ModelAttribute修饰的方法中,放入作用域的 key  为  类名首字母小写
         map.put("person",person);
     }

}

  @RequestMapping("textModelAttribute2")
  public String textModelAttribute2(Person  person)  // 这里的名字也要和作用域里面的名字相同
  {
      System.out.println("修改=="+person);
      return "success";
  }

控制器: (标注在参数上)

**
 *  1. 这里的名字随便, 但是上面那个方法的的key 尽量就是类名首字母小写
 *
 *  2. 使用 添加在入参上 @ModelAttribute(value="XXX") , 则名字随便
 *
 *@ModelAttribute
 * @param user
 * @return
 */
@RequestMapping("textModelAttribute2")
public String textModelAttribute2(@ModelAttribute(value = "abc")Person  user)
{

自定义视图:

第一步: 实现View接口

public class HelloView implements View {
    /**
     * 返回内容类型
     * @return
     */
    @Override
    public String getContentType() {
        return "text/html";
    }

    /**
     * 渲染视图
     * @param map
     * @param httpServletRequest
     * @param httpServletResponse
     * @throws Exception
     */
    @Override
    public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        httpServletResponse.getWriter().print("helloViewTime"+new Date());
    }
}

第二步: 配置视图解析器

<!-- 配置视图解析器 BeanNameViewResolver 解析器: 使用视图的名字解析视图 (所以我们需要把我们的视图添加进IOC)-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
    <!-- 到现在为止,我们就有了两个视图解析器,需要指定优先顺序-->
    <!--  常用的放在后边,我们的放前边  order越小,优先级越高-->
    <property name="order" value="200"></property>
</bean>

测试

  @RequestMapping("textView")
public String textView(){
    System.out.println("textView");
    // 自定义的视图类名小写
    return "helloView";
}

重定向

控制器返回的字符串被当作逻辑视图名称处理

如果返回的字符串中带有forward:或者redirect: 会被SpringMvc当作指示符特殊处理,后面的字符串当作url路径

@RequestMapping("textRedirect")
public String textRedirect(){
    System.out.println("测试重定向!!!");
    return "redirect:/index.jsp";
}

数据校验:

当我们添加<mvc:annotation-driven/>配置,SpringMvc会自动为我们做如下处理

  • RequestMappingHandlerMapping
  • RequestMappingHanderAdapter
  • ExceptionHandlerExceptionResolver 这三个bean
  • 支持使用ConversionService对表单参数进行类型转换
  • 支持使用@NumberFormatannptation @DateTimeFormat 注解完成数据类型格式化
  • 支持使用@Valid 注解对JavaBean 实例惊醒jsr303 验证
  • 支持使用@RequestBody和@ResponseBady // 处理ajax

常使用如下连个注解对bean进行校验

@DateTimeFormat(pattern="yyyy-MM-dd")
Date birth;
@NumberFormat(pattern = "#,###,###.#")  // #表示数数字
Float salary;

返回Json

@ResponseBody

拦截器

  • 实现自己的拦截器实现HandlerInterceptor接口

重写他的三个方法

preHandle()
作用: 对用户请求request进行处理
调用时机:  在处理器处理请求之前被调用
返回: 
    true: 如果还需要调用其他拦截器或者是业务处理器
    f 何组件处理请求
 
 
postHandle()
作用: 对用户请求request进行处理
调用时机: 业务处理器处理完请求之后,DispatcherServlet向客户端相应之前


aferCompletion()

作用: 进行资源清理工作
调用时机: 在DispatcherServlet向客户端响应数据之后调用

配置文件:

<!-- 配置拦截器 -->
<mvc:interceptors>
    <bean class="com.changwu.interceptor.FirstInterceptor"/>
</mvc:interceptors>
  • 配置拦截指定请求路径的拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
    <bean class="com.changwu.interceptor.FirstInterceptor"/>
    
    // 配置 专门针对某个求情的拦截器
    <mvc:interceptor>
        <mvc:mapping path="/textView"/>
        <bean class="com.changwu.interceptor.SecondInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • 多个拦截器的执行顺序
  1. 第一个拦截器的firstInterceptor返回flase,其他拦截器不执行,目标方法不执行
  2. 第一个拦截器的firstInterceptor返回true,第二个拦截器的firstInterceptor返回false,目标方法不执行,但是第一个拦截器的afterCompletion方法会执行,回收资源

异常处理--ExceptionHandlerExceptionResolver

SpringMvc 使用HandlerExceptionResolver处理异常

5

ExceptionHandlerExceptionResolver
主要处理Handler中使用 @ExceptionHandler注解定义的方法

1. 控制器: 出现异常
@RequestMapping("textExceptionHandlerExceptionResolver")
public String textExceptionHandlerExceptionResolver(@RequestParam("i")int i){
    System.out.println("result=="+10/i);
    return   "success";
}

2. 标注有@ExceptionHandler注解的方法 会处理异常
/**
 *   捕获数学异常
 *   把异常带到页面"ModelAndView
 */
 @ExceptionHandler(value=ArithmeticException.class)
public ModelAndView handleArithmeticException(Exception e){
     // 通过ModelAndView 把错误信息带到页面
     ModelAndView modelAndView = new ModelAndView();
     modelAndView.setViewName("error");
     modelAndView.addObject("errorMsg",e);
     System.out.println("异常信息:"+e);
     return modelAndView;
}

@ExceptionHandler定义的方法优先级问题,例如发生的是 空指针异常,但是声明异常是 运行时异常和异常, 这时就会报 离空指针异常比较近的 运行时异常

1. 假如出现了数学异常, 它会优先使用下面的第一个异常

@RequestMapping("textExceptionHandlerExceptionResolver")
public String textExceptionHandlerExceptionResolver(@RequestParam("i")int i){
    System.out.println("result=="+10/i);
    return   "success";
}

     /**
    *   捕获数学异常
    *   把异常带到页面"ModelAndView
    *
    */

   @ExceptionHandler(value=ArithmeticException.class)
   public ModelAndView handleArithmeticException(Exception e){
       // 通过ModelAndView 把错误信息带到页面
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.setViewName("error");
       modelAndView.addObject("errorMsg",e);
       System.out.println("异常信息:"+e);
       return modelAndView;
   }

   @ExceptionHandler(value=RuntimeException.class)
   public ModelAndView handleRuntimeException(Exception e){
       // 通过ModelAndView 把错误信息带到页面
       ModelAndView modelAndView = new ModelAndView("error");
       modelAndView.addObject("errorMsg",e);
       System.out.println("{异常信息}:"+e);
       return modelAndView;
   }

ExceptionHandlerExceptionResolver 内部找不到@ExceptionHandler 注解的话,就会找 @ControllerAdvice 中的 @ExceptionHandler注解方法

/**
 * @Author: Changwu
 * @Date: 2019/4/14 19:04
 */
@ControllerAdvice
public class HandException {

    @ExceptionHandler(value=ArithmeticException.class)
    public ModelAndView handleArithmeticException(Exception e){
        // 通过ModelAndView 把错误信息带到页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("errorMsg",e);
        System.out.println("异常信息:"+e);
        return modelAndView;
    }
}

异常处理-- ResponseStatusExceptionResolver

通过@ResponseStatus(value="异常信息",code="错误状态码")注解,定制返回给前端的异常信息以及状态码

异常处理 -- SimpleMappingExceptionResolver

xml文件中进行配置, 指定出现什么异常,跳往哪个页面

<!-- 配置SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <!-- 统一匹配的异常的全类名                             跳往的异常页面   -->
            <prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
        </props>
    </property>
</bean>
  • 异常信息会自动存储在 作用域 通过${requestScope.exception} 可以取出来
posted @ 2019-10-19 19:29  赐我白日梦  阅读(504)  评论(0编辑  收藏  举报