[SpringMVC] SpringMVC

RequestMapping注解

  • SpringMVC使用@RequestMapping注解为控制器指定可以处理哪些URL请求
  • 在控制器的类定义及方法定义处都可标注
    • 类定义处:提供初步的请求映射信息,相对于WEB应用的根目录
    • 方法定义处:提供进一步的细分映射信息,相对于类定义处的URL,若类定义处未标注@RequestMapping,则方法标记处的URL相当于WEB应用的根目录
  • DispatchServlet截获请求后,就通过控制器上@RequestMapping提供的映射信息确定请求所对应的处理方法。
  • @RequestMapping除了可以使用请求URL映射请求外,还可以使用请求方法、请求参数及请求头映射请求
  • @RequestMapping的value、method、params和heads分别表示请求URL、请求方法、请求参数及请求头的映射条件,它们之间是与的关系,联合使用多个条件可以让请求映射更加精确化
  • params和headers支持简单的表达式:
    • param1:表示请求必须包含名为param1的请求参数
    • !param1:表示请求不能包含名为param1的请求参数
    • param1!=value1:表示请求包含名为param1的请求参数,但其值不能为value1
    • {“param1=value1”, “param2”}:表示请求包含名为param1和param2的两个请求参数,且param1参数的值必须为value1
  • Ant风格资源地址支持3种匹配符:
    • ?:匹配文件名中的一个字符
    • *:匹配文件名中的任意字符
    • **:匹配多层路径
  • @RequestMapping还支持Ant风格的URL:
    • /user/*/createUser:匹配/user/aaa/createUser、/user/bbb/createUser等URL
    • /user/**/createUser:匹配/user/createUser、/user/aaa/bbb/createUser等URL
    • /user/createUser??:匹配/user/createUseraa、/user/createUserbb等URL

@PathVariable注解

@PathVariable映射URL绑定的占位符,通过@PathVariable可以将URL中占位符参数绑定到控制器处理方法的入参中:URL中的{xxx}占位符可以通过@PathVariable("xxx")绑定到操作方法的入参中。

@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable(value="id") Integer id) {
    System.out.println("testPathVariable: " + id);
    return SUCCESS;
}

REST

HTTP协议里面,四个操作方式的动词:GET、POST、PUT、DELETE,它们分别对应4种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。

示例:

  • /order/1 HTTP GET:得到id=1的order
  • /order/1 HTTP DELETE:删除id=1的order
  • /order/1 HTTP PUT:更新id=1的order
  • /order HTTP POST:新增order

浏览器form表单只支持GET和POST请求,而DELETE、PUT等method并不支持,Spring添加了一个过滤器HiddenHttpMethodFilter,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求。

需在web.xml中配置HiddenHttpMethodFilter,例如:

<!--配置org.springframework.web.filter.HiddenHttpMethodFilter:可以把POST请求转为DELETE或PUT请求-->
<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>

相应的index.jsp如下:

<form action="testRest/1" method="post">
    <input type="hidden" name="_method" value="PUT"/>
    <input type="submit" value="TestRest PUT"/>
</form>
<br><br>
<form action="testRest/1" method="post">
    <input type="hidden" name="_method" value="DELETE"/>
    <input type="submit" value="TestRest DELETE"/>
</form>
<br><br>
<form action="testRest" method="post">
    <input type="submit" value="TestRest POST"/>
</form>
<br><br>
<a href="testRest/1">Test Rest GET</a>
<br><br>

相应的处理器方法如下:

@RequestMapping(value = "/testRest/{id}", method = RequestMethod.PUT)
public String testRestPut(@PathVariable("id") Integer id) {
    System.out.println("testRest PUT: " + id);
    return SUCCESS;
}

@RequestMapping(value = "/testRest/{id}", method = RequestMethod.DELETE)
public String testRestDELETE(@PathVariable("id") Integer id) {
    System.out.println("testRest DELETE: " + id);
    return SUCCESS;
}

@RequestMapping(value = "/testRest", method = RequestMethod.POST)
public String testRest() {
    System.out.println("testRest POST");
    return SUCCESS;
}

@RequestMapping(value = "/testRest/{id}", method = RequestMethod.GET)
public String testRest(@PathVariable("id") Integer id) {
    System.out.println("testRest GET: " + id);
    return SUCCESS;
}

使用@RequestParam绑定请求参数值

在处理方法入参处使用@RequestParam可以把请求参数传递给请求方法,其具有两个属性:value表示参数名,required表示是否必须,默认为true,表示请求参数中必须包含对应的参数,若不存在,将抛出异常!

@RequestMapping(value = "/testRquestParam")
public String testRquestParam(@RequestParam(value = "username") String username, @RequestParam(value = "age") Integer age) {
    System.out.println("testRquestParam, username: " + username + " age: " + age);
    return SUCCESS;
}

使用POJO对象绑定请求参数值

SpringMVC会按请求参数名和POJO属性名进行自动匹配,自动为该对象填充属性值,支持级联属性!

@RequestMapping("/testPojo")
public String testPojo(User user) {
    System.out.println("testPojo: " + user);
    return SUCCESS;
}

相应的表单文件为:

<form action="testPojo" method="post">
    username: <input type="text" name="username"/>
    <br>
    password: <input type="password" name="password"/>
    <br>
    email: <input type="text" name="email"/>
    <br>
    age: <input type="text" name="age"/>
    <br>
    city: <input type="text" name="address.city"/>
    <br>
    province: <input type="text" name="address.province"/>
    <br>
    <input type="submit" value="submit"/>
</form>

使用Servlet API作为入参

MVC的Handler方法可以接收以下这些类型的ServletAPI参数:

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • java.security.Principal
  • Locale
  • InputStream
  • OutputStream
  • Reader
  • Writer
@RequestMapping("/testServletAPI")
public void testServletAPI(HttpServletRequest request, HttpServletResponse response, Writer out) throws IOException {
    System.out.println("testServletAPI, " + request + ", " + response);
    out.write("hello Spinrmvc");
}

处理模型数据

SpringMVC提供了以下几种方法输出模型数据:

  • ModelAndView:处理方法返回值类型为ModelAndView,方法体即可通过该对象添加模型数据
  • Map和Model:入参为Model、ModelMap或Map时,处理方法返回时,Map中的数据会自动添加到模型中
  • @SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性
  • @ModelAttribute:方法入参标注该注解后,入参的对象就会放到数据模型中

ModelAndView

控制器处理方法的返回值如果是ModelAndView,则其既包含视图信息,又包含模型数据信息。

  • 添加模型数据
    • ModelAndView addObject(String attributeName, Object attributeValue)
    • ModelAndView addAllObject(Map<String, ?> modelMap)
  • 设置视图
    • void setView(View view)
    • void setViewName(String viewName)
/**
 * 目标方法的返回值可以是ModelAndView类型,其中可以包含视图和模型信息
 * SpringMVC会把ModelAndView的model中的数据放到request域对象中
 * @return
 */
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
  ModelAndView modelAndView = new ModelAndView(SUCCESS);
  modelAndView.addObject("time", new Date());
  return modelAndView;
}

正如上面注释所述,通过源码分析可知,添加到ModelAndView中的数据最终放到到了request请求域中。

Map以及Model

SpringMVC在内部使用了一个Model接口存储模型数据,具体步骤为:

  • SpringMVC在调用方法前会创建一个隐含的模型对象作为模型数据的存储数据
  • 如果方法的入参为Map或Model类型,SpringMVC会将隐含模型的引用传递给这些入参,在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。
/**
 * 目标方法可以添加到Map(实际上也可以是Model或ModelMap)类型的参数
 * @param map
 * @return
 */
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
    map.put("names", Arrays.asList("Tom", "Jerry", "Mike"));
    return SUCCESS;
}

@SessionAttributes

若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个@SessionAttributes,SpringMVC将在模型中对应的属性存到HttpSession中。

@SessionAttributes除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。

  • @SessionAttributes(types=User.class)会将隐含模型中所有类型为User.class的属性添加到会话中
  • @SessionAttributes(value={"user1", "user2"})
  • @SessionAttributes(types={User.class, Dept.class})
  • @SessionAttributes(value={"user1", "user2"}, types={Dept.class})
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Map<String, Object> map) {
    User user = new User("Tom", "123456", "tom@gmail.com", 15);
    map.put("user", user);
    map.put("school", "initialsoft");
    return SUCCESS;
}

@ModelAttribute

先看下面这个例子:

/**
 * 1. 有@ModelAttribute标记的方法会在每个目标方法执行之前被SpringMVC调用
 * 2. @ModelAttribute注解也可以修饰目标方法POJO类型的入参,其value属性值中具有如下作用:
 * 1) SpringMVC会使用value属性值在implicitModel中查找对应的对象,若存在则会直接传入到目标方法的入参中
 * 2)SpringMVC会以value为key,POJO类型的对象为value存入到request中
 * @param id
 * @param map
 */
@ModelAttribute
public void getUser(@RequestParam(value = "id", required = false) Integer id, Map<String, Object> map) {
    if (id != null) {
      User user = new User(1, "Tom", "123456", "tom@gmail.com", 12);
      map.put("user", user);
      System.out.println("从数据库中获取一个对象: " + user);
    }
}

/**
 * 1. 执行@ModelAttribue注解修饰的方法:从数据库中取出对象,将对象放入到了Map中,键为user
 * 2. SpringMVC从Map中取出User对象,并把表单的请求参数赋给该User对象的对应属性
 * 3. SpringMVC把上述对象传入目标方法的参数
 * 注意:在ModelAttribute修饰的方法中放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致
 *
 * 源代码分析的流程:
 * 1. 调用@ModelAttribute注解修饰的方法,实际上把@ModelAttribute修饰的方法中Map中的数据放到implicitModel中
 * 2. 解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder的target属性
 * 1)创建WebDataBinder对象:
 * ①. 确定objectName属性:若传入的attrName属性值为空的话则objectName为类名第一个字母小写
 * 注意:attrName,若目标方法的pojo属性使用了@ModelAttribute来修饰,则attrName值即为@ModelAttribute的value属性值
 * ②. 确定target属性
 * > 在implicitModel中查找attrName对应的属性值,若存在,ok
 * > 若不存在,则验证当前Hadnler是否使用了@SessionAttributes进行修饰,若使用了@SessionAttributes修饰,则尝试从Session中获取attrName所对应的属性值
 * 若Session中没有对应的属性值,则抛出了异常
 * > 若Handler没有使用@SessionAttribute进行修饰,或@SessionAttribute中没有attrName键则通过反射创建了pojo对象
 * 2) SpringMVC把表单的请求参数赋给了WebDataBinder的target对应的属性
 * 3) SpringMVC会把WebDataBinder的attrName和target给到implicitModel
 * 4) 把WebDataBinder的target作为参数传递给目标方法的入参
 * @param user
 * @return
 */
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
    System.out.println("修改: " + user);
    return SUCCESS;
}
SpringMVC确定目标方法POJO类型入参的过程
  • 确定一个key
    • 若目标方法的POJO类型的参数木有使用@ModelAttribute作为修饰,则key为POJO类名第一个字母小写
    • 若使用了@ModelAttribute来修饰,则key为@ModelAttribute注解的value属性值
  • 在implicitModel中查找key对应的对象,若存在,则作为入参传入
    • 若在@ModelAttribute标记的方法中在Map中保存过且key和第一步中确定的key一致则会获取到
  • 若不存在key对应的对象则检查当前的Handler是否使用@SessionAttributes注解进行修饰,若使用了该注解,且@SessionAttributes注解的value属性值中包含了key则会从HttpSession中来获取key所对应的value值,若存在则直接传入到目标方法的入参中,若不存在则将抛出异常
  • 若Handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值中不包含key,则会通过反射来创建POJO类型的参数,然后传入为目标方法的参数
  • SpringMVC会把key和POJO类型的对象保存到implicitModel中进而会保存到request中

视图和视图解析器

  • 无论目标方法返回时String、ModelAndView或者View,SpringMVC都会转换成ModelAndView对象

  • SpringMVC借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是JSP,也可能是Excel、JFreeChart等各种表现形式的视图

  • 若项目中使用了JSTL,则SpringMVC会自动把视图由InternalResourceView转为JstlView

  • 若使用JSTL的fmt标签则需要在SpringMVC的配置文件中配置国际化资源文件

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n"></property>
    </bean>
  • 若希望直接响应通过SpringMVC渲染的页面,不需要经过Handler,可以使用mvc:view-controller标签实现

    <mvc:view-controller path="testJstlView" view-name="success"/>

    但是这样配置之后,原来的那些需要经过Handler的链接就将失效,实际上在实际开发中通常需要配置mvc:annotation-driven<mvc:annotation-driven/>,此时即可解决问题。

自定义视图

自定义视图需要实现View接口,并且需要在Spring的配置文件中配置BeanNameViewResolver,如下:

  • HelloView

    @Component
    public class HelloView implements View {
      @Override
      public String getContentType() {
          return "text/html";
      }
    
      @Override
      public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
          response.getWriter().print("hello view, time: " + new Date());
      }
    }

    HelloView实现View接口,需要实现两个接口,HelloView需要加上@Component注解从而注入到IOC容器中,这是因为BeanNameViewResolver需要从IOC容器中获取该View。

  • 配置文件的配置

    <!--配置视图解析器BeanNameViewResolver,使用视图的名字来解析视图-->
    <!--通过order属性来定义视图解析器的优先级,order值越小,优先级越高-->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="100"/>
    </bean>
  • 测试

    @RequestMapping("/testView")
    public String testView() {
      System.out.println("testView");
      return "helloView";
    }

    需要注意的是testView()的返回值必须返回"helloView",这是因为HelloView在IOC容器的ID为helloView,而BeanNameViewResolver正是通过testView()方法的返回值在IOC容器中查找自定义View的。

关于重定向

  • 一般情况下控制器方法的返回字符串类型的值会被当成逻辑视图名处理
  • 如果返回的字符串中带forward:redirect:前缀时,SpringMVC会对他们进行特殊处理,将forward:和redirect:当成指示符,其后的字符串作为URL来处理。
    • redirect:success.jsp:会完成一个到success.jsp的重定向的操作
    • forward:success.jsp:会完成一个到success.jsp的转发操作

数据绑定

  • SpringMVC主框架将ServletRequest对象即目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象

  • DataBinder调用装配在SpringMVC上下文的ConversionService组件进行数据类型转换、数据格式化工作,将Servlet中的请求信息填充到入参对象中

  • 调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BingingData对象

  • SpringMVC抽取BindingResult中的入参对象和校验错误对象,将它们赋给处理方法的响应入参

  • SpringMVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中,数据绑定的核心是DataBinder,运行机制如下:

自定义类型转换器

ConversionService是Spring类型转换体系的核心接口,可以利用ConversionServiceFactoryBean在Spring的IOC容器中定义一个ConversionService,Spring将自动识别出IOC容器中的ConversionService,并在Bean属性配置及SpringMVC处理方法入参绑定等场合使用它进行数据的转换。

可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器。

Spring定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中:

  • Converter<S,T>:将S类型对象转为T类型对象
  • ConverterFactory:将相同系列多个同质的Converter封装在一起,如果希望将一种类型的对象转换为另一种类型及其子类型的对象(例如将String转换为Number及Number子类)可使用转换器工厂类
  • GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换

<mvc:annotation-driven conversion-service="conversionService"/>会将自定义的ConversionService注册到SpringMVC的上下文中,例如:

<mvc:annotation-driven converson-service="conversionService"/>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class=""com.glemontree.springmvc.converters.UserConverter></bean>
        </list>
    </property>
</bean>

annotation-driven配置

有下面几种情况需要使用annotation-driven

  • 使用<mvc:view-controller/>配置直接转发的页面,无需经过Handler方法
  • 使用<mvc:default-servlet-handler/>配置访问静态资源文件
  • 在使用自定义类型转换器时使用<mvc:annotation-driven/>conversion-service属性

在开发过程中通常需要加入该配置。

<mvc:annotation-driven/>会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter以及ExceptionHandlerExceptionResoler三个bean,还提供以下支持:

  • 支持使用ConversionService实例对表单参数进行类型转换
  • 支持使用@NumberFormatannotaion、@DateTimeFormat注解完成数据类型的格式化
  • 支持使用@Valid注解对JavaBean实例进行JSR 303验证
  • 支持使用@ResponseBody@RequestBody注解

@InitBinder

  • @InitBinder标识的方法,可以对WebDataBinder对象进行初始化,WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定。
  • @InitBinder方法不能有返回值,它必须声明为void
  • @InitBinder方法的参数值通常是WebDataBinder

例如:

// 不能自动绑定对象中的roleSet属性,另行处理
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
    dataBinder.setDisallowedFields("roleSet");
}

数据的格式化

现在有这样一种情况,在Bean中有一个Date类型的对象,此时在表单中提交数据时会报错,因为SpringMVC无法知道你需要将什么样的数据转换为Date类型,此时需要在Bean的目标属性上面通过@DateTimeFormat注解声明日期的格式,例如:

@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;

这个的前提是需要在SpringMVC的配置文件中配置:

<mvc:annotation-driven/>

另外一种情况,假设Bean中有一个float类型的属性:

private float salary;

现在在表单中输入1,234,567.8,此时也会出错,所以此时也需要在目标属性上加注解:

// 用#表示数值
@NumberFormat(pattern="#,###,###.#")
private float salary;

Spring在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。

FormattingConversionService拥有一个FormattingConversionServiceFactoryBean工厂类,后者用于在Spring上下文中构造前者。

FormattingConversionServiceFactoryBean内部已经注册了:

  • NumberFormatAnnotationFormatterFactory:支持对数字类型的属性使用@NumberFormat注解
  • JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性@DateTimeFormat注解

装配了FormattingConversionServiceFactoryBean后,就可以在Spring MVC入参绑定及模型数据输出时使用注解驱动了。

<mvc:annotation-driven/>默认创建的ConversionService实例即为FormattingConversionServiceFactoryBean。

数据校验

  • JSR 303是Java为Bean数据合法性校验提供的标准框架,它已经包含在JavaEE6.0中
  • JSR 303通过在Bean属性上标注类似于@NotNull@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
  • Hibernate Validator是JSR 303的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
    • @Email 被注解的元素必须是电子邮件地址
    • @Length 被注解的字符串的大小必须在指定的范围内
    • @NotEmpty 被注解的字符串必须非空
    • @Range 被注解的元素必须在合适的范围内
  • Spring4.0拥有自己的数据校验框架,同时支持JSR 303标准的校验框架
  • Spring在进行数据绑定时,可同时调用校验框架完成数据校验工作,在SpringMVC中,可直接通过注解驱动的方式进行数据校验
  • Spring的LocalValidatorFactoryBean既实现了Spring的Validator接口,也实现了JSR 303的Validator接口,只要在Spring容器里定义一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中
  • Spring本身并没有提供JSR 303的实现,所以需要将JSR 303的实现者的JAR包放在类路径下。
  • <mvc:annotation-driven>会默认装配一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@valid注解即可让SpringMVC在完成数据绑定后进行数据的校验
  • SpringMVC是通过对处理方法签名的规则来保存校验结果的:前一个表单/命令对象的校验结果保存在随后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型。
  • 整个校验的流程如下:
    • 使用JSR 303验证标准
    • 加入hibernate validator验证框架的Jar包
    • 在SpringMVC配置文件中加入<mvc:annotation-driven/>
    • 需要在bean的属性上添加对应的注解
    • 在目标方法bean类型的前面添加@Valid注解

错误消息

  • 显示错误消息

    在jsp中通过<form:errors/>标签进行错误消息的显示,该标签有一个属性path,若设置为*,则显示所有的错误消息。

    当然我们可以将错误消息分别显示在对应字段的后面,此时在对应字段的后面加上<form:errors/>标签,其path属性写上对应字段的名字就可以了,例如:

    LastName: <form:input path="lastName"/>
    <form-errors path="lastName"></form-errors>
  • 定制错误消息

    每个属性在数据绑定和数据校验发生错误时都会生成一个对应的FieldError对象。

    当一个属性校验失败后,校验框架会为该属性生成4和消息代码,这些代码以校验注解类名为前缀,结合modelAttribute、属性名和属性类型名生成多个对应的消息代码,例如User类中的password属性标注了一个@Pattern注解,当该属性不满足@Pattern所定义的规则时,就会产生下面4个消息代码:

    • Pattern.user.password
    • Pattern.password
    • Pattern.java.lang.String
    • Pattern

    当使用SpringMVC标签显示错误消息时,SpringMVC会查看WEB上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值