主要组成:
- 调度Servlet
- 处理映射器(handler mapping)
- 控制器
- 视图解析器
跟踪Spring MVC的请求
请求:Web浏览器中点击链接或提交表单时,请求开始工作。
请求使用Spring MVC经历的所有站点:
- 步骤1:带有用户所请求内容的信息(至少包含请求的URL,可能还有用户提交的表单等信息);
DispatcherServlet
:单例的前端控制器(front controller)Servlet,将请求委托给其他的应用组件来执行实际处理。在Spring MVC中,其任务是将请求发送给Spring MVC 控制器(controller)
-
步骤2:通过查询一个或多个处理器映射(handler mapping),告知DispatcherServlet将请求发送到哪个Controller。handler mapping会根据请求携带的URL信息进行决策。
-
步骤3: 请求发送到指定的控制器,卸下负载,等待控制器处理负载信息。
⚠️:设计良好的控制器本身只处理很少甚至不处理负载信息,而是将业务逻辑委托给一个或多个服务对象进行处理。
-
步骤4:处理完成后,通常会产生一些需要返回给用户并在浏览器上显示的信息,称为
model
。一般需要将model进行格式化,处理为HTML返回,此时,信息需要发送给一个视图(view),其通常是JSP。
上述将数据模型打包并标示出用于渲染输出的视图名,然后将请求连同模型和视图名发送回DispatcherServlet的过程就是控制器所做的最后一件事。 -
步骤5:上面步骤返回的视图名并不能确定具体视图,只是一个逻辑名称。用其可以查找产生结果的真正视图。DispatcherServlet使用视图解析器
view resover
来将逻辑视图名匹配为特定的视图实现。 -
步骤6:视图实现,交付模型数据,使用模型数据渲染输出。
-
步骤7:将输出通过响应对象传递给客户端
搭建Spring MVC
配置DispatcherServlet
传统方式,在web.xml中配置DispatcherServlet。
在Servlet3规范和Spring3.1之后,可以使用Java配置,将DispatcherServlet配置在Servlet容器中,无需使用web.xml。
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[RootConfig.class];
}
//指定配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[WebConfig.class];
}
//配置DispatcherServlet映射到Controller规则,此处映射到"/"
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
扩展
AbstractAnnotationConfigDispatcherServletInitializer
的任意类都会自动地配置DispatcherServlet和Spring应用上下文。
上述子类中重写的三个方法:
- getServletMappings():将一个或多个路径映射到DispatcherServlet上;
- getServletConfigClasses():返回的带有@Configuration注解的类用来配置DispatcherServlet;
- getRootConfigClasses():返回的带有@Configuration注解的类用来配置ContextLoaderListener;
- DispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射;
- ContextLoaderListener加载应用中的其它bean,通常指驱动应用后端的中间层和数据层组件。
上述的代码样例中,RootConfig和WebConfig都是自定义java配置类,根配置声明在RootConfig中,DispatcherServlet配置声明在WebConfig中。
启动Spring MVC
传统方式:
<mvc:annotation-driven>
启用注解驱动的Spring MVC
基于java进行配置:
@Configuration
@EnableWebMvc
public class WebConfig {
}
上述类中:
- 未配置视图解析器,Spring默认使用BeanNameViewResolver查找ID与视图名称匹配的bean,且查找的bean要实现View接口,以此方式来解析视图;
- 没有启动组件扫描,Spring只能找到显式声明在配置类中的控制器(@Controller无效);
- DispatcherServlet会映射为默认Servlet,会处理所有请求,包括对静态资源的请求,影响展示效果。
如下展示了一段最小可用的Spring MVC配置
@Configuration
@EnableWebMvc //启用Spring MVC
@ComponentScan //启用注解扫描
public class WebConfig extends WebMvcConfigurerAdapter {
//配置JSP视图解析器
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
//配置静态资源的处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
扩展WebMvcConfigurerAdapter
并重写configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
,通过enable()
方法,将对静态资源的请求转发到Servlet容器中默认的Servlet上,而非使用DispatcherServlet本身来处理此类请求。
上述配置完了WepConfig,下面配置RootConfig:
@Configuration
@ComponentScan(basePackages = "com.note.demo.mvc", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {
}
上述代码使用@ComponentScan中的excludeFilters属性,不去扫描EnableWebMvc注解下的bean。
编写Controller
重定向
@RequestMapping(value = "/")
public class TestController{
@RuquestMapping(value = "test", method = POST)
public String testForm(CustomModel model){
return "redirect:/test/base/";
}
}
注意到上述内容中的redirect:
,mvc会将带有此前缀的返回,解析为你重定向规则。
除此之外,mvc还能识别forward:
前缀,请求将会前往指定的URL路径,而不再是重定向。
表单校验
spring 3.0开始,在spring mvc中支持java 校验api。需要保证在类路径下包对应java 校验api的实现,如Hibernate validator。
java 校验api定义了多个注解,将注解放到属性傻姑娘,限制属性值。
注解 | 描述 |
---|---|
@AssertFalse | 所注解的值必须为boolean类型,且值为false |
@AssertTrue | 同上,值为true |
@DecimalMax | 所注解的元素为数字,且小于等于给定的BigDecimal值 |
@DecimalMin | 大于等于给定的BigDecimal值 |
@Digits | 所注解的元素必须是数字,值必须有指定位数 |
@Future | 注解的元素的值必须是一个将来的日期 |
@Max | 所注解的元素必须为数字,且值要小于等于给定的值 |
@Min | 大于等于给定的值 |
@NotNull | 注解的元素值必须不为null |
@Null | 必须为null |
@Past | 所注解的元素值必须为一个过去的日期 |
@Pattern | 所注解的值必须匹配给定的正则表达式 |
@Size | 所注解的元素值必须是String、集合或数组,且长度要符合给定的范围 |
除了上述注解,java 校验api海会提供额外的校验注解。同时,可以自定义限制条件。
/**
* 自定义参数校验注解
* 校验 List 集合中是否有null 元素
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)////此处指定了注解的实现类为ListNotHasNullValidatorImpl
public @interface ListNotHasNull {
/**
* 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义
*/
int value() default 0;
String message() default "List集合中不能含有null元素";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* 定义List,为了让Bean的一个属性上可以添加多套规则
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@interface List {
ListNotHasNull[] value();
}
}
实现类
@Service
public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNull, List> {
private int value;
@Override
public void initialize(ListNotHasNull constraintAnnotation) {
//传入value 值,可以在校验中使用
this.value = constraintAnnotation.value();
}
@Override
public boolean isValid(List list, ConstraintValidatorContext constraintValidatorContext) {
if(this.value > 0){
for (Object object : list) {
if (object == null) {
//如果List集合中含有Null元素,校验失败
return false;
}
}
return true;
}
else{
return false;
}
}
}
上述的ConstraintValidator
接口代码如下
public interface ConstraintValidator<A extends Annotation, T> {
default void initialize(A constraintAnnotation) {
}
boolean isValid(T var1, ConstraintValidatorContext var2);
}
分组校验
对同一个Model,我们在增加和修改时对参数的校验也是不一样的,这个时候我们就需要定义分组验证,步骤如下
1、定义两个空接口,分别代表Person对象的增加校验规则和修改校验规则
/**
* 可以在一个Model上面添加多套参数验证规则,此接口定义添加Person模型新增时的参数校验规则
*/
public interface PersonAddView {
}
/**
* 可以在一个Model上面添加多套参数验证规则,此接口定义添加Person模型修改时的参数校验规则
*/
public interface PersonModifyView {
}
2、Model上添加注解时使用指明所述的分组
public class Person {
private long id;
/**
* 添加groups 属性,说明只在特定的验证规则里面起作用,不加则表示在使用Deafault规则时起作用
*/
@NotNull(groups = {PersonAddView.class, PersonModifyView.class}, message = "添加、修改用户时名字不能为空", payload = ValidateErrorLevel.Info.class)
@ListNotHasNull.List({
@ListNotHasNull(value = 2, groups = {PersonAddView.class}, message = "添加上Name不能为空"),
@ListNotHasNull(value = 3, groups = {PersonModifyView.class}, message = "修改时Name不能为空")})
private String name;
@NotNull(groups = {PersonAddView.class}, message = "添加用户时地址不能为空")
private String address;
@Min(value = 18, groups = {PersonAddView.class}, message = "姓名不能低于18岁")
@Max(value = 30, groups = {PersonModifyView.class}, message = "姓名不能超过30岁")
private int age;
//getter setter 方法......
}
3、启用校验
此时启用校验和之前的不同,需要指明启用哪一组规则
/**
* 添加一个Person对象
* 此处启用PersonAddView 这个验证规则
* 备注:此处@Validated(PersonAddView.class) 表示使用PersonAndView这套校验规则,若使用@Valid 则表示使用默认校验规则,
* 若两个规则同时加上去,则只有第一套起作用
*/
@RequestMapping(value = "/person", method = RequestMethod.POST)
public void addPerson(@RequestBody @Validated({PersonAddView.class, Default.class}) Person person) {
System.out.println(person.toString());
}
上述中@Valid
为开启校验的关键。
添加校验限制,并不能阻止表单提交。即使未通过校验,该controller中的方法仍然会被调用,此时我们需要处理校验错误,方式如下
@RequestMapping(value = "/person", method = RequestMethod.POST)
public void addPerson(@RequestBody @Validated({PersonAddView.class, Default.class}) Person person, Errors errors) {
if(errors.hasErrors()){
retrun "error";
}
System.out.println(person.toString());
}
上述Errors
对象可用于处理校验错误,需要注意,Errors必须紧跟在带有@Valid注解的参数后面。
否则,spring抛出异常,结构内容类似下述
{
"timestamp": 1476108200558,
"status": 500,
"error": "Internal Server Error",
"exception": "javax.validation.ConstraintViolationException",
"message": "No message available",
"path": "/test"
}