SpringMVC的很多特性使用略少,些许内容有些遗忘,开篇对SpringMVC进行系统总结。
IDEA创建项目
一般我会使用IntelliJ IDEA来创建Maven的web项目(File -> New -> Project -> Maven -> Create from archetype -> maven-archetype-webapp), 用该模版创建出来的是一个空的maven web项目。有两个操作需要注意:
1. src/main下创建java文件夹,并在Project Structure下标注为Sources。
2. Add framework support, 增加web.xml, applicationContext.xml, dispatcher-servlet.xml文件。
操作完上述两部,才是一个完整的Web项目,可以创建Package以及Java Class等,同时需要简单修改web.xml以及dispatcher-servlet.xml文件内容。
SpringMVC基础
1. SpringMVC中的@RequestMapping可以修饰类和方法,支持的属性有value(缺省), method, params, header,并且支持value路径使用ant-stype通配符(?/*/**)
2. @PathVariable注解:映射URL中绑定占位符到目标方法的参数中。
@Controller
public class TestHelloWorld {
private final static String SUCCESS = "success";
@RequestMapping("/hello")
public String sayHello() {
return SUCCESS;
}
@RequestMapping(value = "/testMethod", method = RequestMethod.POST)
public String testMethod() {
return SUCCESS;
}
@RequestMapping(value = "/testPathVariable/{id}", method = RequestMethod.GET)
public String testPathVariable(@PathVariable Integer id) {
System.out.println(id);
return SUCCESS;
}
@RequestMapping(value = "/testParams", method = RequestMethod.GET, params = {"name", "age!=10"})
public String testParams() {
return SUCCESS;
}
/**
* ?通配一个字符
* *通配0或者任意数量的字符
* **通配0或者更多的目录
*
* @return
*/
@RequestMapping(value = "/testWildcards/?/test", method = RequestMethod.GET)
public String testWildcards() {
return SUCCESS;
}
}
3. Spring3.0后使用HiddenHttpMethodFilter过滤器支持RESTFUL API。
web.xml配置
<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>
@RequestMapping("/restSupport")
@Controller
public class TestRestfulAPI {
private final static String SUCCESS = "success";
@RequestMapping(value = "/testRestGet/{id}", method = RequestMethod.GET)
public String testRestGet(@PathVariable("id") Integer id) {
System.out.println("testRestGet: " + id);
return SUCCESS;
}
@RequestMapping(value = "/testRestPost", method = RequestMethod.POST)
public String testRestPost() {
System.out.println("testRestPost");
return SUCCESS;
}
@RequestMapping(value = "/testRestPut/{id}", method = RequestMethod.PUT)
@ResponseBody()
public String testRestPut(@PathVariable("id") Integer id) {
System.out.println("testRestPut: " + id);
return SUCCESS;
}
@RequestMapping(value = "/testRestDelelte/{id}", method = RequestMethod.DELETE)
@ResponseBody()
public String testRestDelete(@PathVariable("id") Integer id) {
System.out.println("testRestDelete: " + id);
return SUCCESS;
}
}
备注:在Tomcat8.x以及Spring4.x下调用PUT和DELETE的RESTFUL API时,报错:SpringMVC: HTTP Status 405 - JSPs only permit GET POST or HEAD,通过添加@RestponseBody()解决该问题。
4. @RequestParam 映射URL的请求参数(value, required, defalutValue)
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(value = "username") String un, @RequestParam(value = "age", required = false) Integer age) {
System.out.println("username: " + un + ", age: " + age);
return SUCCESS;
}
5. @RequestHeader 映射请求头信息
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "Accept-Language") String header) {
System.out.println(">>>>>>>>>>>>>>" + header);
return SUCCESS;
}
6. @CookieValue 可以把Request header中关于cookie的值绑定在方法的参数上
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID") String sessionID) {
System.out.println("CookieValue: " + sessionID);
return SUCCESS;
}
7. SpringMVC支持使用POJO作为参数[常用]:SpringMVC会按传递的参数与POJO参数的属性一一对应,进行匹配,并且支持级联属性。【备注】如果不使用SpingMVC的该属性,使用@RequestParam需要一个参数一个参数的获取,使用Servlet API也需要通过request.getParameter("key")来进行传递。
8. 使用Servlet原生API作为参数,支持HttpServletRequest, HttpServletResponse, HttpSession, java.security.Principal, Locale, InputStream, OutputStream, Reader, Writer作为参数传递。
9. ModelAndView:在MVC设计模式中,发送一个请求到目标处理器,目标处理器调用业务方法,业务方法会有返回值(例如:一个对象或者一个集合),然后转发到页面(需要把业务方法的返回值在页面上显示出来)。
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
String viewPage = SUCCESS;
ModelAndView mv = new ModelAndView(viewPage);
mv.addObject("time", new Date());
return mv;
}
通过ModelAndView将数据放进requestScope中,然后在页面中通过${requestScope.time}获取,如果无法解析${requestScope.time},可以设置jsp的isELIgnored="false"参数->表示在此JSP页面中执行EL表达式。
10. 模型Model以及Map,使用Map或者Model作为参数,将数据传递给页面。解析:将Model或者Map作为参数传递给目标方法,然后把数据存入其中,可以轻松的传给页面。SpringMVC会将他们转化为ModelAndView,viewName为SUCCESS,Model就是这里的Map或者Model.
@RequestMapping("/testModel")
public String testModel(Model model) {
model.addAttribute("time", new Date());
return SUCCESS;
}
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
map.put("time", new Date());
return SUCCESS;
}
11. @SessionAttributes, 将数据放进session域中。
@SessionAttributes(value = "student", types = Student.class)
@RequestMapping("/restSupport")
@Controller
public class TestRestfulAPI {
private final static String SUCCESS = "success";
@RequestMapping("/testSessionAttribute")
public ModelAndView testSessionAttribute() {
ModelAndView modelAndView = new ModelAndView(SUCCESS);
Student student = new Student();
student.setId(100);
student.setName("Tom");
student.setAge(20);
modelAndView.addObject("student", student);
return modelAndView;
}
}
<h2>${requestScope.student}</h2>
<h2>${sessionScope.student}</h2>
12. @ModelAttribute: 被@ModelAttribute注解的方法会在此Controller每个方法执行前被调用。常用的几种方式如下:
(1). @ModelAttribute注解修饰返回值为void的方法
@ModelAttribute
public void testModelAttribute001(@RequestParam String abc, Model model) {
model.addAttribute("testAttribute", abc);
}
(2). @ModelAttribute注解修饰返回值为具体类的方法
@ModelAttribute
public Student getStudent() {
System.out.println("getStudent method...");
Student student = new Student();
student.setId(2001);
student.setName("Jobs");
student.setAge(23);
return student;
}
(3). @ModelAttribute(value = "xxx")注解修饰返回值为具体类的的方法
@ModelAttribute("testAttribute")
public Student testModelAttribute002(@RequestParam Student student) {
return student;
}
(4). @ModelAttribute和@RequestMapping注解修饰同一个方法:在调用/helloWorld的时候,会将"testAttribute"和返回值“helloworld”放进model中。
@RequestMapping("/helloWorld")
@ModelAttribute("testAttribute")
public String sayHelloWorld() {
System.out.println("sayHelloWorld method....");
return "helloworld";
}
(5). @ModelAttribute注解修饰一个方法的参数
@ModelAttribute
public void sayHello(Map<String, Object> map) {
//模拟操作
Student student = new Student();
student.setId(100);
student.setName("Cat");
student.setAge(25);
map.put("student", student);
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("student") Student student) {
System.out.println(student);
return SUCCESS;
}
【备注】执行流程如下:
(1). 执行@ModelAttribute注解修饰的方法:从数据库中(此处模拟)取出对象,把对象放入到了Map中,键为“student”.
(2). SpringMVC从Map中取出Student对象,并把表单的请求参数赋给该Student对象的对应属性。
(3). SpringMVC把上述对象传入目标方法的参数。
注意:在@ModelAttribute修饰的方法中,放入到Map时的键需要和目标方法入参类型的“第一个字母小写的字符串”一致。
13. SpringMVC视图解析器ViewResolver : 当对SpringMVC的资源发起请求时,这些请求会被SpringMVC的DispatcherServlet处理,然后Spring会通过分析HandlerMapping定义的所有请求映射,并找到合适的映射,然后通过该HandlerMapping取得其对应的handler,接着通过HandlerAdapter处理该handler。该HandlerAdapter处理handler后会返回一个ModelAndView对象,获得了ModelAndView对象后,Spring需要把该view渲染给用户,即返回给浏览器。在该过程中,发挥作用的是ViewResolver,常用的InternalResourceViewResolver为常用的接口ViewResolver的实现。
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
14. 国际化:只要在classpath下添加了jstl.jar,view即从InternalResourceViewResolver变为JstlView.
需要在SpringMVC配置文件中配置国际化资源文件。
<bean id="resourceBundleMessageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
15. 在SpringMVC配置文件中使用mvc:view-controller标签:在springMVC中使用mvc:view-controller标签可以直接访问URL和视图进行映射,而无需通过控制器。
<mvc:view-controller path="/testController" view-name="success"/>
采用该配置,默认其他使用@Controller修饰的URL路径都不可访问,如果两者兼容,那么需要配置
<mvc:annotation-driven />
16. 自定义视图类 implements View接口 :
17. SpringMVC的重定向 : redirect关键字
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("testRedirect method...");
return "redirect:hello";
}
@RequestMapping("/hello")
public String sayHello() {
System.out.println("hello method...");
return SUCCESS;
}
18.<mvc:annotation-driven /> : 会自动注册DefaultAnnotationHandlerMapping, AnnotationMethodHandlerAdapter与ExceptionHandlerExceptionResolver三个bean, 还提供以下支持:
(1). 支持使用ConversionService实例对表单参数进行类型转换
(2). 支持使用@NumberFormatannotaion、@DataTimeFormat注解完成数据类型的格式化
(3). 支持使用@valid注解对javabean实例进行JSR303验证
(4). 支持使用@RequestBody和@ResponseBody注解
19. 数据转换 & 数据格式化 & 数据校验
数据转换:
例如:表单传入的数据为String, 而JavaBean对应的类型可能为Integer, Long, Boolean等,SpringMVC是如何将表单String类型转换为JavaBean的Integer等类型的呢?这里就讲解了数据转换的过程。
流程解析:SpringMVC将ServletRequest对象以及目标方法入参实例(例如:JavaBean实例或者Controller方法的参数等)传递给WebDataBinderFactory实例,以创建dataBinder实例。而dataBinder调用装配在SringMVC上下文中的ConversionServife组件进行数据类型的转换、数据格式化工作。dataBinder调用Validator组件进行数据合法性校验,并最终生成数据绑定结果。SpringMVC从BindingResult中抽取结果。
SpringMVC已经默认内建了很多类型转换器。
数据格式化:
需求1: 表单提交String类型的2017-08-08,格式化为JavaBean的Date类型。
需求2: 表单提交String类型的123,345.9, 转换为JavaBean的Float类型。
分别在JavaBean对应属性上添加注解
@DateTimeFormat(pattern="yyyy-MM-dd")和@NumberFormat(pattern="#,###,###.#")解决该问题。
数据校验:
使用JSR 303 以及hibernate validator框架做校验。
20. 自定义类型转换器
例如:将表单提交的字符串转换为JavaBean实例?
Step1. 自定义一个转换器类(例如:MyConversionService)并实现Converter接口,重写converter()方法。注意给该转换器类添加@Conponent注解,让其注入Spring IOC容器中。
Step2. SpringMVC中配置ConversionServiceFactoryBean
<mvc:annotation-driven conversion-service="conversionService" />
<bean name="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myConversionService"></ref>
</set>
</property>
21. @InitBinder:解决SpringMVC中类型转换问题。最常见的问题是表单提交的日期字符串和JavaBean的Date类型的转换,Spring默认不支持这种转换,需要手动配置,自定义数据绑定。
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("userId");
}
22. 错误消息显示以及国际化:
使用<form:errors path="xxx"></form:errors>在前端显示错误消息。
国际化:配置国际化资源文件i1n8.properties以及SpringMVC中配置国际化bean:ResourceBundleMessageSource。
23. 返回Json & HttpMessageConverter
添加Jackson的dependency -> 使用MappingJackon2HttpMessageConverter这个HttpMessageConverter接口的实现
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.3</version>
</dependency>
同时添加@ResponseBody注解,完成javabean到json的转换。
@RequestMapping(value = "testJson", method = RequestMethod.GET)
public @ResponseBody Student testJson() {
Student student = new Student();
student.setId(100011);
student.setAge(29);
student.setName("maliang");
return student;
}
24. 自定义拦截器:Intercepter
step1. 自定是Intercepter
package com.maliang;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by maliang on 2017/8/8.
*/
public class MyHandlerIntercepter implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandler...");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandler...");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompetion...");
}
}
step2. 配置dispatcher-servlet.xml
<mvc:interceptors>
<bean id="myHandlerIntercepter" class="com.maliang.MyHandlerIntercepter"/>
</mvc:interceptors>
如果配置多个拦截器,执行顺序?
first PreHandler -> second PreHandler -> second PostHandler -> first PostHandler -> second afterCompetion -> first afterCompetion.
25. SpringMVC异常处理ExceptionHandler
在程序中需要捕捉异常,防止程序异常退出。可以通过try..catch, 也可以通过SpringMVC提供的ExceptionHander处理。
@RequestMapping("/testExceptionHandler/{id}")
public String testExceptionHandler(@PathVariable("id") Integer id) {
System.out.println(10/id);
return SUCCESS;
}
当id传入为0时,该Controller接口会抛出ArithmeticException异常,用SpringMVC可以有两种方式处理该异常。
局部处理:处理该Controller的异常:
@ExceptionHandler(ArithmeticException.class)
public String exceptionHandler() {
return ERROR;
}
全局异常:新建异常处理类,并用@ControllerAdvice修饰,该异常会处理全局ArithmeticException异常。
package com.maliang;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* Created by maliang on 2017/8/10.
*/
@ControllerAdvice
public class MyHandlerForException {
private static final String ERROR = "error";
@ExceptionHandler({ArithmeticException.class})
public String exceptionForHandler() {
System.out.println("ArithmeticException...");
return ERROR;
}
}
@ResponseStatus: 使用该注解修饰自己创建的类,在异常界面显示自定义异常信息。
package com.maliang;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Created by maliang on 2017/8/10.
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "User not exist.")
public class UserNotMatchException extends RuntimeException {
}
package com.maliang;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Created by maliang on 2017/8/10.
*/
@Controller
public class TestResponseStatus {
private static final String SUCCESS = "success";
@RequestMapping("/testResponseStatus/{id}")
public String testResponseStatus(@PathVariable("id") Integer id) {
if (id == 0) {
throw new UserNotMatchException();
}
return SUCCESS;
}
}
SimpleMappingExceptionResolver: 当异常发生时,可以将它映射到指定的界面。
@RequestMapping("/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver() {
int arrays[] = new int[10];
System.out.println(arrays[10]);
return SUCCESS;
}
当调用该Controller接口发生异常时,会根据如下的bean配置,完成exception的处理:
<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex"/>
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
26. SpringMVC运行流程
Spring工作流程描述
1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
7. ViewResolver 结合Model和View,来渲染视图
8. 将渲染结果返回给客户端。
Spring工作流程描述
为什么Spring只使用一个Servlet(DispatcherServlet)来处理所有请求?
详细见J2EE设计模式-前端控制模式
Spring为什么要结合使用HandlerMapping以及HandlerAdapter来处理Handler?
符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如HandlerAdapter可能会被用于处理多种Handler。