标签:inf 做了 name extend freemark except map 字符 strong
Spring MVC(Model View Controller)框架的处理控制器的实现策略,与其他的请求驱动的Web框架在总体思路上是相似的。通过引入Front Controller和Page Controller的概念来分离流程控制逻辑与具体的Web请求处理逻辑。下面,我们简要介绍一下Spring MVC框架中的各个组件以及执行流程。
org.springframework.web.servlet.DispatcherServlet
就是Spring MVC中的Front Controller,它负责接收并处理所有的Web请求,只不过针对具体的处理逻辑,它会委派给它的下一级控制器Page Controller去实现,即org.springframework.web.servlet.mvc.Controller
。
既然DispatcherServlet
是整个框架的Front Controller,将它注册到web.xml
时,就注定了它要服务于规定的一组Web请求的命运,而不是单独的一个Web请求。在Spring MVC中,为了灵活处理请求的URL和对应控制器的匹配,引入了org.springframework.Web.servlet.HandlerMapping
来专门管理Web请求到具体的HandlerMapping实例,以获取对应当前Web请求的具体处理类,即Controller
。
Controller
是对应DispatcherServlet
的次级控制器,它本身实现了对应某个具体Web请求的处理逻辑。在我们所使用的HandlerMapping
查找到当前Web请求对应哪个Controller
的具体实例之后,DispatcherServlet
即可获得HandlerMapping
所返回的结果,并调用Controller
的处理方法来处理当前的Web请求。
在Controller
的处理方法执行完毕之后,将返回一个org.springframework.Web.servlet.ModelAndView
实例,ModelAndView
包含了如下两部分信息:
DispatcherServlet
将根据该视图的逻辑名称,来决定为用户显示哪个视图。DispatcherServlet
就可以着手视图的渲染工作了。我们知道,现在可用的视图技术不止JSP一家,Freemarker、Thymleaf等通用的模板引擎,都可以帮助我们构建相应的视图。鉴于此,Spring提出了一套基于ViewResolver
和View
接口的Web视图处理抽象层,以屏蔽Web框架在使用不同的Web视图技术时候的差异性。DispatcherServlet
只需要根据Controller
处理完毕后通过ModelAndView
返回的逻辑视图名称查找到具体的View
实现,然后委派该具体的View
实现类来根据模型数据,输出具体的视图内容即可。不过,DispatcherServlet
需要依赖一个ViewResolver
将根据ModelAndView
中的逻辑视图名查找相应的View
实现类,然后将查找的结果返回给DispatcherServlet
,DispatcherServlet
最终会将ModelAndView
中的模型数据交给返回的View
实例来处理最终的视图渲染工作。
我们可以通过下图来总览各个组件的流程:
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
,仅此而已。
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
接口,用以指定优先级。
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分为两类。
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
对象类型,如下所示:
<bean id="commandController" class="..AnySubClassOfBaseCommandController">
<property name="commandClass" value="..Command">
</bean>
或者直接在子类的构造方法中直接设定,如下所示:
public class BindingDemoController extends SimpleFormController {
public BindingDemoController() {
setCommandClass(Command.class);
// 进行其他必要设置
}
}
有关数据绑定的过程,可以简单概括如下:
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<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
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表单:
<input type="text" name="address"/>
<input type="text" name="zipCode"/>
<input type="text" name="phoneNumbers[0].number"/>
也就是说,参数的名称对应Command
对应的属性名称。对于绑定的表达式,可以进一步参考Spring的文档。
数据验证:Spring框架提供的数据验证支持并不只是局限于Spring MVC内部使用,从数据验证类所在的包名就能看出来,即org.springframwork.validation
。只要愿意,我们完全可以在独立运行的程序中使用Spring的数据验证功能。
Spring的数据验证框架核心类为org.springframework.validation
和org.springframework.Errors
,Validator
负责实现具体的验证逻辑,而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实现声明式的数据验证。
标签:inf 做了 name extend freemark except map 字符 strong
原文地址:https://www.cnblogs.com/muuu520/p/12899610.html