SpringMVC 是 Spring 的一个模块,提供 web 层解决方案;和众多其他web框架一样,它基于MVC的设计理念,此外,它采用了松散耦合可插拔组件结构,比其他MVC框架更具扩展性和灵活性。
SpringMVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,无需实现任何接口,同时,SpringMVC 还支持 REST 风格的 URL 请求。
此外,SpringMVC 在数据绑定、视图解析、本地化处理及静态资源处理上都有许多不俗的表现。
它在框架设计、扩展性、灵活性等方面全面超越了 Struts、WebWork 等 MVC 框架,从原来的追赶者一跃成为 MVC 的领跑者。
SpringMVC 框架围绕 DispatcherServlet 这个核心展开,DispatcherServlet 是 SpringMVC 框架的总导演、总策划,它负责截获请求并将其分派给相应的处理器处理。
框架流程
下面说说 SpringMVC 的执行流程,当然和传统的 MVC 模式是非常相似的:
介绍中提到过,DispatcherServlet 是前端控制器,相当于中央调度器,各个组件都和前端控制器进行交互,降低了各个组件之间耦合度;Handler 是后端控制器,当成模型(Model)。
那么一个请求的完整步骤可以概况为:
- 用户发起 request 请求,请求至 DispatcherServlet 前端控制器
- DispatcherServlet 前端控制器请求 HandlerMapping 处理器映射器查找 Handler
- HandlerMapping 处理器映射器,根据 url 及一些配置规则(xml配置、注解配置)查找 Handler,将 Handler 返回给 DispatcherServlet 前端控制器;
这里其实会以 HandlerExecutionChain 对象的形式返回,我就称它为执行链了,它包含了 Handler 对象和拦截器列表 - DispatcherServlet 前端控制器根据返回的 Handler 调用合适的适配器执行 Handler,有了适配器通过适配器去扩展对不同 Handler 执行方式(比如:原始servlet开发,注解开发)
- 适配器执行 Handler(成功获得 HandlerAdapter 后,将开始执行拦截器的 preHandler(…) 方法 ),其中会经过一些消息转换器,例如进行数据绑定相关操作(数据转换、格式化、校验等)
- Handler 执行完成后返回 ModelAndView 给适配器
ModelAndView 是 springmvc 的一个对象,对 Model (数据)和 view (仅仅是 View 的名字)进行封装。 - 适配器将 ModelAndView 返回给 DispatcherServlet
- DispatcherServlet 通过返回的 View 的名称,调用视图解析器进行视图解析,解析后生成真正的 view
也就是说视图解析器根据逻辑视图名解析出真正的视图对象。
View:springmvc 视图封装对象,提供了很多 view,比如:jsp、freemarker、pdf、excel。。。 - ViewResolver 视图解析器给前端控制器返回真正的 view 对象
- DispatcherServlet 调用 view 的渲染视图的方法(根据模型对象的数据),将模型数据填充到 request 域。
- View 返回渲染后的视图
- DispatcherServlet 向用户响应结果(jsp页面、json数据等)
以上就是 springmvc 处理一个请求的步骤了,然后再贴一张详细一点的流程图吧:
一个简单的栗子
然后就来看看具体的开发流程,首先定义 web.xml 文件,也就是定义 SpringMVC 的总导演 DispatcherServlet 这就是一个 servlet:
1 | "1.0" encoding="UTF-8" xml version= |
关于配置文件也有必要说一下,首先这里使用了 init-param 标签来指定配置文件的位置,如果不配置,默认加载的是 WEB-INF/{servlet-name}-servlet.xml
,其中大括号中指的是变量名,还记的 servlet-name 在那设置的么?看第六行!也就是说最终默认找的配置文件是 springmvc-servlet.xml 。
然后就是 url-pattern 了,匹配规则,SpringMVC 支持:*.xxx
、/
、/xxx/*
;不支持 :/*
;
是的,原来在 struts 中写的很爽的 /*
在这里是错误的;使用这种配置,最终要转发到一个 jsp 页面时,仍然会由 DispatcherServlet 解析 jsp 地址,不能根据 jsp 页面找到 handler,会报错。
不过 /
表示所有访问的地址都由 DispatcherServlet 解析,对于静态文件的解析需要配置不让 DispatcherServlet 解析,使用此种方法可以实现 RESTful 风格的 url。
按照套路接下来就应该是 springmvc 的配置文件了(先使用配置文件的方式),其实就是 spring 的配置文件,因为基本没差嘛:
1 | "1.0" encoding="UTF-8" xml version= |
这些定义其实都没必要看文档,就看上面的那个流程图,定义的这些 bean 完全就是按照图的顺序定义的,因为图中都是接口所以定义的时候要写具体实现的 bean。
视图解析器的前缀后缀是什么意思呢?从图中得知,最终要根据一个 View 的名字(在 Handler 的返回值中)返回一个具体的视图对象,视图说白了就是 jsp、json 等这种,所以视图解析器会根据前缀和后缀最后拼出一个真正的视图,也就是找到具体你定义的 jsp 文件。
视图就不写了,就是简单的一个 JSP 文件,除了视图 Handler 也需要自己写,看下 Handler 应该怎么写:
1 | //实现Controller接口的映射器 |
这里一定要实现 Controller 接口,在适配器中会检查是否有实现这个接口,一般 Handler 会放在 controller 包下。
视图解析器会根据 setViewName 设置的名字和前缀、后缀进行拼接,最后就转到了我们定义的 jsp 视图上;
这样就可以通过访问 /hello.action
来调用 Handler 了。
从命名来说,一般如果是 addXXX 那么就可以添加多个,如果是 setXXX 那么就只能设置一个了。
另外,Controller(handler)的名字和视图的名字应该是对应的。
优化配置文件
我们在 springmvc 的配置文件中配置了一些 bean,但是有些其实是不用配也是可以的(有默认值),比如前两个:处理器映射器和处理器适配器,后面两个肯定不能省了,因为有我们自己配的信息
使用注解
在最开始的介绍中也说明了,springmvc 中的注解是很重要的,大多都是用注解吧,毕竟在默认的配置文件中就已经开启了注解,所以注解我们可以直接用。
目标就是达到零配置!虽然默认配置已经开启了注解,但是已经过时了(AnnotationMethodHandlerAdapter 和 DefaultAnnotationHandlerMapping),推荐使用新的类(RequestMappingHandlerAdapter 和 RequestMappingHandlerMapping):
1 | <!-- spring 3.1之后由RequestMappingHandlerAdapter和RequestMappingHandlerMapping代替 --> |
至于注解驱动是如何将最新的映射器和适配器注入的可以去看 org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
这个类,我就先不研究了….
简单说,驱动注解内部其实包含了这些内容:
1 | <!-- HandlerMapping --> |
@Controller
在 SpringMVC 中,控制器 Controller 负责处理由 DispatcherServlet 分发的请求,它把用户请求的数据 经过业务处理层处理之后封装成一个 Model ,然后再把该 Model 返回给对应的 View 进行展示。
在 SpringMVC 中提供了一个非常简便的定义 Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是 Controller 。
然后使用 @RequestMapping 和 @RequestParam 等一些注解用以定义 URL 请求和 Controller 方法之间的映射,这样的 Controller 就能被外界访问到。
Controller 不会直接依赖于 HttpServletRequest 和 HttpServletResponse 等 HttpServlet 对象,它们可以通过 Controller 的方法参数灵活的获取到。
也就是说,@Controller 用于标记在一个类上,使用它标记的类就是一个 SpringMVC Controller 对象,然后 Spring 怎样才能找到它呢,有两种方式,当然,推荐使用扫描啦:
1 | <!--方式一--> |
@RequestMapping
首先这是一个重点!其次,它非常 NB。
RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上;访问的时候就是 类上 value + 方法上的 value 拼起来,并且可以不加斜线,会自动处理。
RequestMapping 注解有六个属性,可分为五种映射(或者说三种映射两种限制):
标准URL映射
比如:@RequestMapping("hello")
or@RequestMapping("/hello")
or@RequestMapping(value="hello")
,它们都是一样的。Ant 风格的 URL 映射
Ant 通配符有三种,?
- 匹配单个字符;*
- 匹配 0 或者任意数量的字符;**
- 匹配 0 或者更多的目录。
例如:@RequestMapping("/test/*/hello")
占位符映射
使用占位符用花括号括起来,可以使用多个占位符。例如:1
2
3
4
5
6"user/{id}") (value=
public ModelAndView show (@PathVariable("id") int pid) {
ModelAndView modelAndView = new ModelAndView("hello");
modelAndView.addObject("msg","HelloWorld!" + pid);
return modelAndView;
}这样访问
xxx/user/123.action
的时候,会自动把 123 传进 pid 这个参数,这样就又多了一种传值的方式。限制请求方法映射
也就是限制请求方式,只要添加一个属性就可以了,使用到的是 method 属性:@RequestMapping(value="hello",method = RequestMethod.GET)
需要多个的话用花括号括起来就行了,之间用逗号分割。限制参数映射
也是通过属性指定,params 属性:指定 request 中必须包含某些参数值是,才让该方法处理。
过个参数还是用花括号括起来,一般有下面的几种形式:"!user"
:参数不能包含 user;"userID != 1"
:参数必须包含 userID 但是不能为 1;{"user","age"}
:参数必须包含 user 和 age;
然后属性这样就已经说了三个了,value、method、params;其它是的:
headers: 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求;举个栗子:@RequestMapping (value= "testHeaders" , headers={ "host=localhost" , "Accept" })
,这样就表示只有当请求头包含 Accept 信息,且请求的 host 为 localhost 的时候才能正确的访问到此方法。
consumes: 指定处理请求的提交内容类型(Content-Type),例如 application/json 、 text/html;
produces: 指定返回的内容类型,仅当 request 请求头中的 (Accept) 类型中包含该指定类型才返回;
@PathVariable
用于将请求 URL 中的模板变量映射到功能处理方法的参数上,即取出 uri 模板中的变量作为参数。
上面已经使用过了,需要注意的是 value 参数不能省略!虽然省略了可以执行,但是当正常编译( IDE 一般为 debug 模式)时,方法的传入参数不会被记录,所以会报错。
许多注解都是这个机制,所以最好是写上 value ,万无一失,总之就是不要依赖变量名。
Eclipse 中的 Java Compiler 中的 add variable attributes to generated class files
默认是开启的。
@requestParam
主要用于在 SpringMVC 后台控制层获取参数,类似一种是 request.getParameter("name")
,它有三个常用参数:
defaultValue = “0”、required = false、 value = “isApp”;
defaultValue 表示设置默认值,required 通过 boolean 设置是否是必须要传入的参数(默认为 true),value 值表示接受的传入的参数类型。
具体用在哪里可以参考上面的 PathVariable ,差不多的,这样就直接把请求参数放进方法参数里了,在方法里使用非常方便了,对于基本数据类型,记得一定要写,不要省略。
它可以直接对参数进行 POJO 实体注入、转换数组、List 等集合,并且不会出现空指针异常,非常的方便!
@CookieValue
可以把 Request header 中关于 cookie 的值绑定到方法的参数上,例如获取 cookie 中的 JSESSIONID 的值:public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {}
和上面两个注解也没啥差别,都是获取数据的。
@RequestHeader
和 CookieValue 等注解类似,就是将 HttpServletRequest 头信息到 Controller 的方法参数上,比如 @RequestHeader("host")
就是获取 host 头信息;
不同的是,在 RequestHeader 中是不区分大小写的,但在 @PathVariable 、 @RequestParam 和 @CookieValue 中都是大小写敏感的。
@ResponseBody
作用:该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区。
使用时机:返回的数据不是 html 标签的页面,而是其他某种格式的数据时(如 json、xml等)使用;默认为 JSON;
补充:为什么默认是 JSON ,在注解驱动中已经定义了,如果项目中有 Jackson 的依赖,那么会自动注册其转换器做转换输出。
@RequestBody
和上面长的很像…..我都看错了,它们是一对。
该注解常用来处理请求的 Content-Type 不是application/x-www-form-urlencoded
编码的内容,例如 application/json
, application/xml
等.
它是通过使用 HandlerAdapter 配置的 HttpMessageConverters 来解析 post data body,然后绑定到相应的 bean 上的。
因为配置有 FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded
的内容,处理完的结果放在一个 MultiValueMap<String, String>
里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter API.
@ResponseStatus
业务异常可以使用 @ResponseStatus 来注解。当异常被抛出时,ResponseStatusExceptionResolver 会设置相应的响应状态码。DispatcherServlet 会默认注册一个 ResponseStatusExceptionResolver 以供使用。
它有两个属性,value 属性是 http 状态码,比如 404,500 等。reason 是错误信息:@ResponseStatus(value=HttpStatus.FORBIDDEN,reason="用户不匹配")
通常它是修饰类的,然后在抛出异常的时候就会起作用了:
1 | "用户不匹配") (value=HttpStatus.FORBIDDEN,reason= |
这样,用户看到的异常界面正是我们自己定义的异常,而不再是一大堆用户看不懂的代码,但如果用这个注解来修饰方法,那么无论它执行方法过程中有没有异常产生,用户都会得到异常的界面。而目标方法正常执行
@ModelAttribute和@SessionAttributes
该 Controller 的所有方法在调用前,先执行此 @ModelAttribute 方法;
可以把这个 @ModelAttribute 特性,应用在 BaseController 当中,所有的 Controller 继承 BaseController,即可实现在调用 Controller 时,先执行 @ModelAttribute 方法。
@SessionAttributes 即将值放到 session 作用域中,写在 class 上面有效。
SpringMVC 支持使用 @ModelAttribute 和 @SessionAttributes 在不同的模型(model)和控制器之间共享数据。 @ModelAttribute 主要有两种使用方式,一种是标注在方法上,一种是标注在 Controller 方法参数上。
当 @ModelAttribute 标记在方法上的时候,该方法将在处理器方法执行之前执行,然后把返回的对象存放在 session 或模型属性中,属性名称可以使用 @ModelAttribute("attributeName")
在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)作为属性名称。
1 |
|
当我们请求 /myTest/sayHello.do
的时候使用 @ModelAttribute 标记的方法会先执行,然后把它们返回的对象存放到模型中。最终访问到 sayHello 方法的时候,使用 @ModelAttribute 标记的方法参数都能被正确的注入值.
这时,数据都是放在模型中,而不是存放在 session 属性中,如果想要也同时放进 Session 中,那么需要加 @SessionAttributes 注解:
1 |
|
在上面代码中我们指定了属性为 intValue 或 stringValue 或者类型为 User 的都会放到 Session 中;但是当访问时,Session 中并没有值,第二次访问才有,也就是说:等处理器方法执行完成后 Spring 才会把模型中对应的属性添加到 session 中,所以第二次访问才能拿到值。
绑定Servlet内置对象
使用注解后确实简单多了,但有一个问题,怎么才能获得 Servlet 中的内置对象呢,比如 request、response、session 之类的,工具类?接口?
完全不需要,需要什么定义什么就 OK 了,直接定义在 handler 中的方法参数中即可。
1 | "test/show") (value = |
似不似非常简单方便,这种做法确实非常棒!
POJO对象绑定
对于“实体”对象,也是非常的简单,和上面一样,直接在方法参数里写就行了,会自动把请求中的参数注入到这个 POJO 中去;
例如,方法为:public ModelAndView show (User user){}
;请求的时候直接 id=1&name=abc
这样就可以了。
然后,还有一个对于我们来说最烦人的问题,中文乱码问题…..,如何解决后面再补充
如果提交的是集合,就是这样:<input name="users[0].name" />
;这样提交的不是一个集合或者数组嘛,但是接收不能使用 public ModelAndView show (List<user> users){}
的形式来接收,从上面的简单栗子也可以看得出,填充对象的时候应该是去参数对象中去找相应的 setter 方法进行注入(除非使用 @requestParam 注解,适用于基本数据类型,大概是这样),所以,你需要将 List 进行对象包装后才可以使用。
虽然这种使用形式并不会经常用到。
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~