简介
Spring MVC是一种基于MVC架构模式的轻量级Web框架。
Spring MVC处理流程
Spring MVC的处理过程:
- DispatcherServlet 接收用户的请求
- 找到用于处理request的 handler 和 Interceptors,构造成 HandlerExecutionChain 执行链
- 找到 handler 相对应的 HandlerAdapter
- 执行所有注册拦截器的preHandler方法
- 调用 HandlerAdapter 的 handle() 方法处理请求,返回 ModelAndView
- 倒序执行所有注册拦截器的postHandler方法
- 请求视图解析和视图渲染
处理流程中各个组件的功能:
- 前端控制器(DispatcherServlet):接收用户请求,给用户返回结果。
- 处理器映射器(HandlerMapping):根据请求的url路径,通过注解或者xml配置,寻找匹配的Handler。
- 处理器适配器(HandlerAdapter):Handler 的适配器,调用 handler 的方法处理请求。
- 处理器(Handler):执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装到ModelAndView对象中。
- 视图解析器(ViewResolver):将逻辑视图名解析成真正的视图View。
- 视图(View):接口类,实现类可支持不同的View类型(JSP、FreeMarker、Excel等)。
Spring MVC和Struts的区别
Spring MVC是基于方法开发,Struts2是基于类开发的。
- Spring MVC会将用户请求的URL路径信息与Controller的某个方法进行映射,所有请求参数会注入到对应方法的形参上,生成Handler对象,对象中只有一个方法;
- Struts每处理一次请求都会实例一个Action,Action类的所有方法使用的请求参数都是Action类中的成员变量,随着方法增多,整个Action也会变得混乱。
Spring MVC支持单例开发模式,Struts只能使用多例
- Struts由于只能通过类的成员变量接收参数,故只能使用多例。
Struts2 的核心是基于一个Filter即StrutsPreparedAndExcuteFilter,Spring MVC的核心是基于一个Servlet即DispatcherServlet(前端控制器)。
Struts处理速度稍微比Spring MVC慢,Struts使用了Struts标签,加载数据较慢。
Spring MVC环境搭建
导入jar包:
1 |
|
新建logback.xml用来配置日志:
1 |
|
web.xml文件中添加Spring MVC的前端控制器,用于拦截符合配置的url请求。
1 | <?xml version="1.0" encoding="UTF-8"?> |
编写核心配置文件springmvc.xml。
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
Handler类
1 | import org.springframework.web.servlet.ModelAndView; |
处理器映射器和适配器
在Spring MVC核心jar包中有一个默认的配置文件DispatcherServlet.properties(org.springframework.wen.servlet包下),当核心配置文件springmvc.xml没有配置处理器映射器和适配器时,会使用默认配置。
非注解的处理器映射器和适配器
常用的处理器映射器有BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping,ControllerClassNameHandlerMapping。
1 | <!--/User/findUser.action 映射到UserController的findUser()方法上--> |
1 | <!--通过内部参数配置请求的url和handler的映射关系--> |
常用的处理器适配器有SimpleControllerHandlerAdapter,HttpRequestHandlerAdapter,AnnotationMethodHandlerAdapter。
1 | <!--Handler需实现HttpRequestHandler接口--> |
注解的处理器映射器和适配器
方式一(常用的配置方式):annotation-driven标签会自动注册RequestMappingHandlerMapping与RequestMappingHandlerAdapter两个Bean,这是Spring MVC为@Controller分发请求所必需的,并且提供了数据绑定支持,@NumberFormat支持,@DateTimeFormat支持,@Valid支持读写XML的支持(JAXB)和读写JSON的支持(默认Jackson)等功能。
1 | <mvc:annotation-driven/> |
方式二:必须保证基于注解的处理器映射器和适配器成对配置,否则没有效果。
1 | <!--注解映射器 --> |
使用了注解的处理器映射器和适配器,则Handler无需实现任何接口,也不用在xml中配置任何信息,只需在Handler处理器类上添加相应的注解即可(@Controller,@RequestMapping)。
为了让注解的处理器映射器和适配器找到注解的Handler,需要在springmvc.xml中声明相关的bean信息。有两种配置方式:
1 | <bean class="com.tyson.controller.UserController"></bean> |
扫描配置,对包下所有的类进行扫描,找出所有使用了@Controller注解的Handler控制器类。
1 | <!-- 可以扫描controller、service等--> |
Handler类
1 | import com.tyson.po.User; |
前端控制器
前端控制器DispatcherServlet类最核心的方法是doDispatch()。
在web.xml中配置了名为”springmvc”的Servlet,拦截以”.action”结尾的url请求。当Web应用接收到这种请求时,会调用前端控制器。
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
对静态资源的处理
web.xml中DispatcherServlet的配置如下:
1 | <servlet> |
经测试,<url-pattern>/</url-pattern>
会拦截*.html请求,不会拦截*.jsp请求。”/“优先级最低,故*.jsp请求会先被默认的jsp Servlet处理,不会被dispatcherServlet拦截。而*.html没有默认的Servlet可以处理,会被dispatcherServlet拦截。
而<url-pattern>/*</url-pattern>
则会拦截所有请求,包括*.html和*.jsp。
<url-pattern>*.action</url-pattern>
则只会拦截后缀为action的请求,不会拦截静态资源的请求。
“/“和”/*“的区别在于”/*“的优先级高于”/路径”和”*.后缀”的路径,而”/“在所有的匹配路径中,优先级最低,即当别的路径都无法匹配时,”/“所匹配的servlet才会进行相应的请求资源处理。
1 | <servlet> |
1 | <servlet> |
1 | <!-- The mapping for the default servlet --> |
静态资源的处理:
方法一:激活Tomcat的default Servlet来处理静态资源
1 | <!--Tomcat, Jetty, JBoss, and GlassFish默认Servlet的名字为“default”--> |
default Servlet无法解析jsp页面,直接输出html源码。
方式二:使用<mvc:default-servlet-handler/>
请求的url若是静态资源请求,则转由org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler 处理并返回,否则才由DispatcherServlet处理。DefaultServletHttpRequestHandler使用的是各个Servlet容器自己默认的Servlet(如jsp servlet)。
方式三:Spring3.0.4以后版本<mvc:resources/>
1 | <mvc:annotation-driven /> |
<mvc:default-servlet-handler/>
将静态资源的处理经由 Spring MVC 框架交回 Web 应用服务器处理。而 <mvc:resources/>
更进一步,由 Spring MVC 框架自行处理静态资源。
<mvc:resources/>
允许静态资源放在任何地方,如 WEB-INF 目录下、类路径,甚至 JAR 包中。可通过 cache-period 设置客户端数据缓存时间。
使用<mvc:resources/>
元素,把 mapping 的 URI 注册到 SimpleUrlHandlerMapping的urlMap 中,
key 为 mapping 的 URI pattern值,而 value为 ResourceHttpRequestHandler,
这样就巧妙的把对静态资源的访问由 HandlerMapping 转到 ResourceHttpRequestHandler 处理并返回,所以就支持classpath目录和jar包内静态资源的访问。
视图解析器
视图解析器ViewResolver的作用是把逻辑视图名称解析成具体的View对象,让View对象去解析视图,并将带有数据的视图反馈给客户端。
常用的视图解析器类有AbstractCachingViewResolver,UrlBasedViewResolver,InternalResourceViewResolver,XmlViewResolver,BeanNameViewResolver,ResorceBundleViewResolver,FreeMarkerViewResolver和VelocityViewResolver。
AbstractCachingViewResolver
抽象类,实现了该抽象类的视图解析器会将其曾经解析过的视图进行缓存。
UrlBasedViewResolver
继承了AbstractCachingViewResolver,通过拼接资源的uri路径来展示视图。
1 | <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver"> |
UrlBasedViewResolver支持返回的视图名称中含有”redirect:”和”forward:”前缀,支持视图的重定向和内部跳转设置。
InternalResourceViewResolver
内部资源视图解析器,最常用的视图解析器类型。它是UrlBasedViewResolver的子类。
特点:它会把返回的视图名称自动解析为InternalResourceView类型的对象,而InternalResourceView会把Controller处理器方法返回的模型属性都存放到对应的request属性中,然后通过RequestDispatcher在服务端把请求以forward的方式跳转到目标url。
1 | <!--无需单独指定viewClass对象--> |
当Controller处理器方法返回名为”login”的视图时,InternalResourceViewResolver会将”login”解析成一个InternalResourceView对象,然后将返回的模型数据存放到对应的HttpServletRequest属性中,最后利用RequestDispatcher把请求forward到”/WEB-INF/jsp/login.jsp”上。
XmlViewResolver
继承了AbstractCachingViewResolver,使用XmlViewResolver需要添加一个xml配置文件,用来定义视图的bean对象。当获得Controller方法返回的视图名称后,XmlViewResolver会在指定的配置文件中寻找对应名称的bean配置,解析并处理该视图。
1 | <!--默认配置文件为/WEB-INF/view.xml--> |
/WEB-INF/config/view.xml,其遵循的DTD规则和Spring的bean工厂配置文件相同。
1 | <?xml version="1.0" encoding="UTF-8"?> |
BeanNameViewResolver
通过把返回的逻辑视图名称去匹配定义好的视图bean对象。BeanNameViewResolver要求视图bean对象都定义在Spring的application context中。
1 | <!--通过把返回的逻辑视图名称去匹配定义好的视图bean对象--> |
MyView 定义:
1 | import javax.servlet.http.HttpServletRequest; |
MyViewController
1 | import org.springframework.stereotype.Controller; |
Spring MVC 根据返回的逻辑视图名去寻找视图 bean 对象。
ResourceBundleViewResolver
继承了AbstractCachingViewResolver,需要一个properties文件定义逻辑视图名和View对象的对应关系,配置文件需放在classpath根目录下。
1 | <!--默认配置文件是classpath:view.properties--> |
classpath:viewResource.properties
1 | login.(class)=org.springframework.web.servlet.view.InternalResourceView |
FreeMarkerViewResolver
FreeMarkViewResolver会将Controller返回的逻辑视图信息解析成FreeMarkerView类型。它是UrlBasedViewResolver的子类。
FreeMarkerViewResolver解析逻辑视图后,返回FreeMarker模板,该模板负责将模型数据合并到模板中,从而生成标准输出(html、xml等)。
springmvc.xml配置
1 | <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> |
模板文件:web\WEB-INF\freemarker\template\fm_freemarker.ftl
1 | <html> |
建议在ViewResolver中,将InternalResourceViewResolver解析器优先级设置为最低,因为该解析器能解析所有类型的视图,并返回一个不为空的View对象。
请求映射
在使用annotation-driven标签时,处理器Handler的类型要符合annotation-driven标签指定的处理器映射器和适配器的类型。annotation-driven标签指定的默认处理器映射器和适配器在Spring3.1之前为DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter,在Spring3.1之后为RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
每个处理器适配器都实现了HandlerAdapter接口。
1 | public interface HandlerAdapter { |
RequestMappingHandlerAdapter本身没有重写supports方法,它的supports方法定义在父类AbstractHandlerMethodAdapter中。
1 | public final boolean supports(Object handler) { |
RequestMappingHandlerAdapter支持HandlerMethod类型的Handler,HandlerMethod可以访问方法参数、方法返回值和方法的注解,所以它支持带有注解信息的Handler类。
@RequestMapping作用是为控制器指定可以处理那些url请求,该注解可以放在类上或者方法上。method用于指定处理哪些HTTP方法。
1 | @RequestMapping(value = "/findUser", method={RequestMethod.GET, RequestMethod.POST}) |
@RequestMapping的param属性可以指定某一种参数名类型,当请求数据中包含该名称的请求参数时,才能进行相应,否则拒绝此次请求。
1 | @RequestMapping(value = "/findUser", param = "username") |
@RequestMapping的headers属性可以指定某一种请求头类型。
1 | @RequestMapping(value = "/findUser", headers = "Content-Type:text/html;charset=UTF-8") |
consumes属性表示处理请求的提交内容类型(Content-Type),例如”application/json,text/html”。而produces表示返回的内容类型,仅当request请求头中的Accept包含该指定类型时才会返回。
1 | //仅处理reqeust的Content-Type为"application/json"类型的请求 |
参数绑定
当用户发送请求时,前端控制器会请求处理器映射器返回一个处理器链,然后请求处理器适配器执行相应的Handler。此时,HandlerAdapter会调用Spring MVC提供的参数绑定组件将请求的key/value数据绑定到Controller处理器方法对应的形参上。
简单类型参数绑定
通过RequestParam将某个请求参数绑定到方法的形参上。value属性不指定时,则请求参数名称要与形参名称相同。required参数表示是否必须传入。defaultValue参数可以指定参数的默认值。
1 | @RequestMapping("/findUser") |
路径变量。
1 | @RequestMapping(value = "/insertUser/{id}") |
包装类型参数绑定
1 | <form action="/springmvcdemo/user/findUserByCondition.action" method="post"> |
name属性名称与User类属性对应,Spring MVC的HandlerAdapter会解析请求参数生成具体的实体类,将相关的属性值通过set方法绑定到实体类中。
1 | @RequestMapping("/findUserByCondition") |
集合类型参数绑定
1 | @RequestMapping("/findUsers") |
1 | <form action="/springmvcdemo/user/findUsers.action" method="post"> |
包装类中定义的List属性的名称要与前端页面的集合名一致。
1 | public class UserList { |
Converter和Formatter
Converter
将字符串转化成日期格式,可通过编写Converter接口的实现类来实现。
1 | import org.springframework.core.convert.converter.Converter; |
在Spring MVC配置文件编写一个ConversionService bean。这个bean需包含一个converters属性。
1 | <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> |
然后给annotation-driven标签的converter-service属性赋bean名称。
1 | <mvc:annotation-driven conversion-service="conversionService"/> |
Formatter
Formatter和Converter一样,也是将一种类型转化为另一种类型。但Formatter的源类型必须是String,而Converter适用于各种类型的源类型。Formatter更适合于web层。
1 | import org.springframework.format.Formatter; |
spring配置文件。
1 | <mvc:annotation-driven conversion-service="formattingConversionService"/> |
验证器
本节内容参考:SpringMVC介绍之Validation
使用Validator接口进行验证
需要进行验证的实体类
1 | public class User { |
Spring MVC提供了Validator接口,我们可以通过实现该接口来定义自己对实体对象的验证。
1 | import com.tyson.po.User; |
使用UserValidator校验User对象,使用DataBinder设定当前Controller由哪个Validator校验。
1 | import com.tyson.po.User; |
在Controller类中通过@InitBinder标记的方法只有在请求当前Controller的时候才会被执行,所以其中定义的Validator也只能在当前Controller中使用,如果我们希望一个Validator对所有的Controller都起作用的话,我们可以通过WebBindingInitializer的initBinder方法来设定了。另外,在SpringMVC的配置文件中通过mvc:annotation-driven的validator属性也可以指定全局的Validator。
1 |
|
非注解方式编写的适配器。
1 | <bean id="userValidator" class="com.tyson.validator.UserValidator"/> |
使用JSR-303 Validation进行验证
JSR-303是一个数据验证的规范,Spring没有对这一规范进行实现,当我们在Spring MVC使用JSR-303的时候需要提供一个对JSR-303规范的实现,Hibernate Validator实现了这一规范。
JSR303的校验是基于注解的,它的内部定义了一系列限制注解,我们只需要把这些注解标注在需要进行校验的实体类的属性或者对应的getter方法上面。
首先引入依赖。
1 | <!--validation--> |
在SpringMVC的配置文件中引入MVC Namespace,并加上<mvn:annotation-driven/>
,此时便可以使用JSR-303来进行实体对象的验证。
1 |
|
实体类,其中@NotBlank是Hibernate Validator的扩展。
1 | import org.hibernate.validator.constraints.NotBlank; |
Controller类
1 | import com.tyson.po.User; |
JSR-303原生支持的限制有如下几种:
限制 | 说明 |
---|---|
@Null | 限制只能为null |
@NotNull | 限制必须不为null |
@AssertFalse | 限制必须为false |
@AssertTrue | 限制必须为true |
@DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
@DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
@Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
@Future | 限制必须是一个将来的日期 |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
@Past | 限制必须是一个过去的日期 |
@Pattern(value) | 限制必须符合指定的正则表达式 |
@Size(max,min) | 限制字符长度必须在min到max之间 |
自定义限制类型的注解
除了JSR-303原生支持的限制类型之外我们还可以定义自己的限制类型。定义自己的限制类型首先我们得定义一个该种限制类型的注解,而且该注解需要使用@Constraint标注。下面定义一个表示金额的限制类型。
1 | package com.tyson.validator; |
@Constraint注解的validatedBy属性用于指定我们定义的当前限制类型需要被哪个ConstraintValidator进行校验。在定义自己的限制类型的注解时有三个属性是必须定义的,message、groups和payload属性。
接下来定义限制类型校验类MoneyValidator,限制类型校验类必须实现接口javax.validation.ConstraintValidator,并实现它的initialize和isValid方法。
1 | import javax.validation.ConstraintValidator; |
同样的方法定义自己的@Min限制类型和对应的MinValidator校验器。
1 | import javax.validation.Constraint; |
isValid方法的第一个参数正是对应的当前需要校验的数据的值,而它的类型也正是对应的我们需要校验的数据的数据类型。这两者的数据类型必须保持一致,否则Spring会提示找不到对应数据类型的ConstraintValidator。
1 | import javax.validation.ConstraintValidator; |
下面是使用了@Min和@Money限制的一个实体类
1 | import com.tyson.validator.Min; |
WorkerController类
1 | import javax.validation.Valid; |
另外Spring对自定义JSR-303限制类型支持的新特性,那就是Spring支持往ConstraintValidator里面注入bean对象。
1 | public class MoneyValidator implements ConstraintValidator<Money, Double> { |
分组校验
POJO有多个属性,分组校验可以使得Controller的方法只校验POJO的某个属性,而不是校验所有的属性。
1 | import com.tyson.validator.Min; |
分组接口ValidationGroup1、ValidationGroup2,不需要写实现。
1 | public interface ValidationGroup1 {} |
WorkerController类,@Validated(value = {ValidationGroup1.class})使得addWorker方法只校验ValidationGroup1这个分组的校验注解,即只校验年龄,不校验工资格式。
1 | import com.tyson.po.Worker; |
补充:@Valid和@Validated的区别:@Valid可以用于对象属性上,可嵌套验证,@Validated不可以嵌套验证;@Validated提供分组功能,而@Valid不支持分组功能。
1 | public class Item { |
1 | public class Prop { |
RequestBody和RequestParam
@RequestBody一般处理的是在ajax请求中声明contentType: “application/json; charset=utf-8”时候。也就是json数据或者xml数据。
@RequestParam一般就是在ajax里面没有声明contentType的时候,为默认的x-www-form-urlencoded
格式时。