Spring MVC 基础

本文围绕Spring MVC展开,介绍了跟踪请求的步骤,包括请求经调度Servlet、处理映射器到控制器,再由视图解析器处理返回结果。还阐述了搭建Spring MVC的方法,如配置DispatcherServlet和启动方式。此外,讲解了编写Controller时的重定向和表单校验相关内容,包括分组校验和错误处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


主要组成:

  • 调度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"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值