SpringMVC 入门(一)

2021-01-19 06:14

阅读:765

标签:inf   做了   name   extend   freemark   except   map   字符   strong   

1 鸟瞰Spring MVC

Spring MVC(Model View Controller)框架的处理控制器的实现策略,与其他的请求驱动的Web框架在总体思路上是相似的。通过引入Front Controller和Page Controller的概念来分离流程控制逻辑与具体的Web请求处理逻辑。下面,我们简要介绍一下Spring MVC框架中的各个组件以及执行流程。

1.1 DispatcherServlet

org.springframework.web.servlet.DispatcherServlet就是Spring MVC中的Front Controller,它负责接收并处理所有的Web请求,只不过针对具体的处理逻辑,它会委派给它的下一级控制器Page Controller去实现,即org.springframework.web.servlet.mvc.Controller

1.2 HandlerMapping

既然DispatcherServlet是整个框架的Front Controller,将它注册到web.xml时,就注定了它要服务于规定的一组Web请求的命运,而不是单独的一个Web请求。在Spring MVC中,为了灵活处理请求的URL和对应控制器的匹配,引入了org.springframework.Web.servlet.HandlerMapping来专门管理Web请求到具体的HandlerMapping实例,以获取对应当前Web请求的具体处理类,即Controller

1.3 Controller

Controller是对应DispatcherServlet的次级控制器,它本身实现了对应某个具体Web请求的处理逻辑。在我们所使用的HandlerMapping查找到当前Web请求对应哪个Controller的具体实例之后,DispatcherServlet即可获得HandlerMapping所返回的结果,并调用Controller的处理方法来处理当前的Web请求。
Controller的处理方法执行完毕之后,将返回一个org.springframework.Web.servlet.ModelAndView实例,ModelAndView包含了如下两部分信息:

  • 视图的逻辑名称(或者具体的视图实例)。DispatcherServlet将根据该视图的逻辑名称,来决定为用户显示哪个视图。
  • 模型数据。视图渲染过程中需要将这些模型数据并入视图的显示中。
    有了这两个信息之后,DispatcherServlet就可以着手视图的渲染工作了。

1.4 ViewResolver和View

我们知道,现在可用的视图技术不止JSP一家,Freemarker、Thymleaf等通用的模板引擎,都可以帮助我们构建相应的视图。鉴于此,Spring提出了一套基于ViewResolverView接口的Web视图处理抽象层,以屏蔽Web框架在使用不同的Web视图技术时候的差异性。DispatcherServlet只需要根据Controller处理完毕后通过ModelAndView返回的逻辑视图名称查找到具体的View实现,然后委派该具体的View实现类来根据模型数据,输出具体的视图内容即可。不过,DispatcherServlet需要依赖一个ViewResolver将根据ModelAndView中的逻辑视图名查找相应的View实现类,然后将查找的结果返回给DispatcherServletDispatcherServlet最终会将ModelAndView中的模型数据交给返回的View实例来处理最终的视图渲染工作。
我们可以通过下图来总览各个组件的流程:
技术图片

2 近距离接触Spring MVC主要角色

2.1 忙碌的协调人HandlerMapping

HandlerMapping帮助DispatcherServlet进行Web请求的URL到具体处理类的匹配。之所以称为HandlderMapping是因为,Spring MVC中,并不局限于使用Controller作为次级控制器,还可以使用其他类型的次级控制器,包括Spring MVC提供的除了Controller之外的次级控制器类型,或者第三方开发框架中的Page Controller组件,所有这些次级控制器类型,在Spring MVC中都统称为Handler。

我们来看一下HandlerMapping接口的定义:

public interface HandlerMapping {
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;
}

我们可以看到返回的是一个HandlerExecutionChain对象,而不是一个Controller。目前为止,只需要大家直到,HandlerExecutionChain中确实包含了用于处理具体Web请求的Handler,仅此而已。

2 近距离接触Spring MVC主要角色

2.1 忙碌的协调人HandlerMapping

HandlerMapping帮助DispatcherServlet进行Web请求的URL到具体处理类的匹配。之所以称为HandlderMapping是因为,Spring MVC中,并不局限于使用Controller作为次级控制器,还可以使用其他类型的次级控制器,包括Spring MVC提供的除了Controller之外的次级控制器类型,或者第三方开发框架中的Page Controller组件,所有这些次级控制器类型,在Spring MVC中都统称为Handler。

我们来看一下HandlerMapping接口的定义:

public interface HandlerMapping {
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;
}

我们可以看到返回的是一个HandlerExecutionChain对象,而不是一个Controller。目前为止,只需要大家知道,HandlerExecutionChain中确实包含了用于处理具体Web请求的Handler,仅此而已。

Spring MVC默认提供了多个HandlerMapping的实现供我们选用。例如:

  • BeanNameUrlHandlerMapping。根据请求路径查询拥有与之相同的beanName的Handler。
  • SimpleUrlHandlerMapping。它解耦了请求URL与Handler的beanName之间的关系,可以进行独立映射配置。

需要注意的是,我们可以为DispatcherServlet提供多个HandlerMapping供其使用。DispatcherServlet在选用HandlerMapping的时候,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级高的HandlerMapping。如果当前的HandlerMapping可以返回可用的Handler,则不再询问其他的HandlerMapping。Spring MVC中所有的HandlerMapping都实现了Ordered接口,用以指定优先级。

2.2 我们的亲密伙伴Controller

Controller是Spring MVC框架支持的用于处理具体Web请求的handler类型之一。要实现一个具体的Controller,我们当然可以直接实现Controller接口,其定义如下:

public interface Controller {
    ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
}

但是更多时候,我们可能会寻求使用更细粒度的Controller框架类。直接实现Controller接口当然没问题,这让我们可以随心所欲地实现Web处理过程中的所有关注点,但这通常需要我们关注更多底层的细节,比如请求参数的抽取,请求编码的设定等。而实际上,这些关注点可能是所有Controller都需要的,我们就想到可以让这些通用的逻辑复用。这也是Spring MVC提供一系列Controller实现体系的原因。下图给出了Spring MVC中Controller的继承层次体系:
技术图片
为了便于理解,我们不妨把Controller分为两类。

  • 自由挥洒派:与Servlet类似的风格,从HttpServletRequest中获取参数,然后验证,调用业务层逻辑,最终返回一个ModelAndView,甚至你都可以通过HttpServletResponse输出最终视图。不过,自由虽然自由,但是如果需要处理的底层细节太多,不如求助一下下面将要介绍的规范操作派Controller!
  • 规范操作派:以BaseCommandController为首的规范操作一派,对Web处理过程中的某些通用逻辑做了进一步规范化处理,规范化的方面主要包括:1)自动抽取请求参数并绑定到指定的Command对象 2)提供了统一的数据验证方式,BaseCommandController及其子类可以接收一组org.springframework.validation.Validator以进行数据验证,我们可以根据具体数据提供相应的Validator实现。3)规范化了表单的处理流程,并且对简单的多页面表单请求处理提供支持。

题外话:你知道Spring中的数据绑定和数据验证吗?

数据绑定:在Web应用程序中使用数据绑定的最大好处就是,我们再也不用自己通过request.getParameter(String)方法遍历获取每个请求参数,然后根据需要转型为自己需要的类型了。Spring MVC提供的数据绑定功能能帮助我们自动提取HttpServletRequest中的相应参数,然后转型为需要的对象类型。我们唯一需要做的,就是为数据绑定提供一个目标对象Command(我们希望它一般是JavaBean类型),此后的Web处理逻辑直接同数据绑定完成的Command对象打交道即可。
对于BaseCommandController及其子类来说,我们可以通过它们的commandClass属性设置数据绑定的目标Command对象类型,如下所示:

或者直接在子类的构造方法中直接设定,如下所示:

public class BindingDemoController extends SimpleFormController {
    public BindingDemoController() {
        setCommandClass(Command.class);
        // 进行其他必要设置
    }
}

有关数据绑定的过程,可以简单概括如下:

  • 在Web请求到达之后,SpringMVC某个框架类将提取当前Web请求中的所有参数名称,然后遍历它,以获取对应每个参数的值,获取的参数名与参数值通常放入一个值对象(PropertyValue)中。最终我们将拥有所有需要绑定的参数和参数值的一个集合。
  • 有了即将绑定到目标Command对象的数据来源之后,我们即可将这些数据根据Command对象中的各个域属性定义的类型进行数据转型,然后设置到Command对象上。在这个过程中我们将碰到老朋友BeanWrapperImpl,它可以用来包裹一个JavaBean对象用来对其进行一些参数的设置:
BeanWrapper beanWrapper = new BeanWrapperImpl(command);

然后比照参数名与Command对象的属性对应关系,以进行参数值到Command属性的设置,而参数值与Command对象属性间类型差异性(字符串/Object)转化的工作,则由BeanWrapperImpl所持有的自定义PropertyEditor负责。如果BeanWrapperImpl所使用的默认的PropertyEditor没有提供对某一种类型的支持,我们也可以添加自定义的PropertyEditor
。我们来看一个例子,该例子阐述了参数名称与对象属性之间的对应关系:

public class CustomerMetadata {
    private String address;
    private String zipCode;
    private List phoneNumbers = new ArrayList();

    public CustomerMetadata() {
        phoneNumbers.add(new PhoneNumber());
    }
    // getter和setter方法定义
}

public class PhoneNumber {
    private String areaCode;
    private String number;
    
    public String getAreaCode() {
        return areaCode;
    }

    public void setAreaCode(String areaCode) {
        this.areaCode = areaCode;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }
    // toString()等方法定义
}

那么为了能够让请求参数对应到Command对象的相应属性,我们需要按照如下格式定义需要提交的Form表单:

也就是说,参数的名称对应Command对应的属性名称。对于绑定的表达式,可以进一步参考Spring的文档。
数据验证:Spring框架提供的数据验证支持并不只是局限于Spring MVC内部使用,从数据验证类所在的包名就能看出来,即org.springframwork.validation。只要愿意,我们完全可以在独立运行的程序中使用Spring的数据验证功能。

Spring的数据验证框架核心类为org.springframework.validationorg.springframework.ErrorsValidator负责实现具体的验证逻辑,而Errors负责承载 验证过程中出现的错误信息,二者之间的纽带则是Validator接口定义的主要验证方法validate(target, errors)。我们来看一下Validator接口的定义:

public interface Validator {
    boolean supports(Class clazz);
    void validate(Object target, Errors errors);
}

Validator具体实现类可以在执行验证逻辑的过程中,随时将验证中的错误信息添加到通过方法参数传入的Errors对象内,这样,验证逻辑执行完之后,我们就可以通过Errors检索验证结果了。至于Validator接口中的support(Class)方法定义,是为了进一步限定Validator实现类的职责,除非你想让所有类型数据的验证都让同一个Validator实现类来做!
至于Validator内部的具体实现方式,我们暂且不讨论,我们接下来关注的是如何使用Validator实现类:

CustomerMetaDataValidator validator = new CustomerMetaDataValidator(new PhoneNumberValidator());
CustomerMetadata md = new CustomerMetadata();

BindException errors = new BindException(md, "customerMd");
// 或者直接调用validator.validate()
ValidationUtils.invokeValidator(validator, md, errors);
assertTrue(errors.hasErrors());

Map map = errors.getBindingResult().getModel();
BindingResult result = (BindingResult)map.get("org.springframework.validation.BindingResult,customerMd");

我们只需要构造一个具体的Command对象示例以及一个Errors实例,然后通过ValidationUtils调用对应的Validator实现类。调用完成之后,如果存在验证错误,我们可以遍历之前传入的errors以获取相应的错误信息,根据具体场景作后续的处理。

至于这些Validator实现类的执行以及错误处理流程,将由BaseCommandController及其子类接管,我们要做的只是通过相应的setter方法为其提供Validator实现。

在实际项目开发过程中,除了通过以上编程方式实现数据验证工作,还可以借助于Commons Validator实现声明式的数据验证。

SpringMVC 入门(一)

标签:inf   做了   name   extend   freemark   except   map   字符   strong   

原文地址:https://www.cnblogs.com/muuu520/p/12899610.html


评论


亲,登录后才可以留言!