Spring MVC各组件近距离接触--中--03

2022-08-23 10:58:54 浏览数 (1)

Spring MVC各组件近距离接触--中--03

  • 前言
  • SimpleFormController
    • 数据绑定
    • 数据校验
    • 实例演示
      • 细节解释
    • 深入表单form处理流程
      • BaseCommandController---将数据绑定和校验结合在一起
      • AbstractFormController---表单处理流程模板化
  • 其他可用的Controller实现
    • AbstractCommandController---增强版本的AbstractController
    • ParameterizableViewController
    • Urlfilenameviewcontroller
    • ServletForwardingController和ServletWrappingController

前言

前面一节,我们已经把自由挥洒派的两个类进行了详细的介绍,下面我们来看看规范操作派。

Spring MVC各组件近距离接触–上–02


SimpleFormController

作为规范操作派当门大弟子,SimpleFromController首先继承了BaseCommandController的自动数据绑定和通过Validator的数据验证功能。

其次AbstractFormController在BaseCommandController的基础上,发展了一套模板化的form处理流程。

至此,从数据的封装,验证,到处理流程的模板化,整个规范化体系基本建立完成。

而SimpleFromController和AbstractWizardFormController就被纳入了这个体系之中。

SimpleFromController面向单一表单的处理,而AbstractWizardFormController则提供多页面的交互能力。

要使用SimpleFromController来简化Web请求处理,就需要先了解SimpleFromController具有的数据绑定,验证和流程模板化的三样功能。


数据绑定

在Web环境下使用数据绑定的最主要的好处就是,可以免于自己手动去request中取出请求参数,然后转换为自己需要的类型。

Spring提供了一套完整的数据绑定机制,来帮我们自动提取HttpServletRequest中的相应参数,然后转换为需要的对象类型。

我们唯一需要做的就是为Spring提供一个目标对象,这个目标对象在Spring中被称为Command对象,此后Web处理程序直接同数据绑定完成的command对象打交道即可。

对于BaseCommandController及其子类来说,我们可以通过它们的commandClass属性设置数据绑定的目标Command对象类型:


Spring mvc中关于数据绑定的工作,是由DataBinder及其子类负责完成的:

Spring数据绑定之DataBinder篇—01

Spring数据绑定之 WebDataBinder、ServletRequestDataBinder、WebBindingInitializer…—02

这里就不多展开了


数据校验

Spring数据校验的支持并不仅仅局限于Spring mvc内部使用,从数据验证类所在的包名: org.springframework.validation. 只要原因,我们完全可以在独立运行的应用程序中使用Spring的数据验证功能。

Spirng数据验证框架核心类为org.springframework.validation.Validator和org.springframework.validation.Errors.

Validator负责实现具体的验证逻辑,而Errors负责承载验证过程中出现的错误信息,二者之间的纽带则是Validator接口定义的主要验证方法:

代码语言:javascript复制
// 注意:它可不是Spring3后才推出的  最初就有
public interface Validator {
	// 此clazz是否可以被validate
	boolean supports(Class<?> clazz);
	// 执行校验,错误消息放在Errors 装着
	// 可以参考ValidationUtils这个工具类,它能帮助你很多
	void validate(Object target, Errors errors);
}

Validator具体实现类可以在执行验证逻辑的过程中,随时将验证中的错误信息添加到Errors对象内部,这样,在验证逻辑执行完成之后,就可以通过Errors检索验证结果了。

至于Validator接口中的support方法定义,是为了进一步限定Validator实现类的职责,避免所有验证逻辑都交给一个Validator实现类完成。

通常情况下,Spring提供的这个Validator接口,都是为了适配原生Java Bean Validation规范而产生的,而Java Bean Validation规范实现中,我们最常使用的就是hibernate-validator。


实例演示

下面我们先通过一个完整的例子,演示一遍数据绑定和数据校验的工作流程:

  • 准备待校验的对象
代码语言:javascript复制
@Data
public class Customer {
    private String address;
    private String name;
    private List<ShopCard> shopCard;
}

@Data
public class ShopCard {
    private Integer money;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endTime;
}
  • 准备校验器
代码语言:javascript复制
/**
 * @author 大忽悠
 * @create 2022/7/28 11:49
 */
public class CustomerValidator implements Validator {

    private final ShopCardValidator shopCardValidator=new ShopCardValidator();

    @Override
    public boolean supports(Class<?> clazz) {
        return ClassUtils.isAssignable(clazz,Customer.class);
    }


    @Override
    public void validate(Object target, Errors  errors) {
        Customer customer = (Customer) target;
        ValidationUtils.rejectIfEmpty(errors,"name","name.empty");
        ValidationUtils.rejectIfEmpty(errors,"address","address.empty");

        for (int i = 0; i < customer.getShopCard().size(); i  ) {
            errors.pushNestedPath("shopCard[" i "]");
            ValidationUtils.invokeValidator(shopCardValidator,customer.getShopCard().get(i),errors);
            errors.popNestedPath();
        }
    }


    public static class ShopCardValidator implements Validator{
        @Override
        public boolean supports(Class<?> clazz) {
            return ClassUtils.isAssignable(clazz,ShopCard.class);
        }

        @Override
        public void validate(Object target, Errors errors) {
            ShopCard shopCard = (ShopCard) target;
            if(shopCard.getEndTime().isAfter(LocalDate.now())){
                errors.reject("errorCode.shopCard.is.error");
                errors.rejectValue("endTime","endTime.is.error","购物卡截止时间有误");
            }
            if(shopCard.getMoney()<0){
                errors.rejectValue("money","money.not.negative");
            }
        }
    }
}
  • 进行数据绑定和数据校验
代码语言:javascript复制
    @Test
    public void test() throws BindException {
        HttpServletRequest request = getRequest();
        Customer customer = new Customer();
        ServletRequestDataBinder  requestDataBinder = new ServletRequestDataBinder(customer, "顾客");
        doBind(request, requestDataBinder);
        doValidate(requestDataBinder);
        //判断是否绑定过程是否产生了错误
        requestDataBinder.close();
        System.out.println(customer);
    }

    private void doValidate(ServletRequestDataBinder requestDataBinder) {
        requestDataBinder.addValidators(new CustomerValidator());
        requestDataBinder.validate();
    }

    private void doBind(HttpServletRequest request, ServletRequestDataBinder requestDataBinder) throws BindException {
        //注册相关默认日期类型转换器---包括解析 @DateTimeFormat注解的
        requestDataBinder.setConversionService(new DefaultFormattingConversionService());
        requestDataBinder.bind(request);
    }

    private HttpServletRequest getRequest() {
        //需要添加spring-test依赖支持
        MockHttpServletRequest mockReq = new MockHttpServletRequest();
        mockReq.addParameter("address","翻斗大街--->花园村--->520号");
        mockReq.addParameter("name","胡图图");
        mockReq.addParameter("shopCard[0].money","-1");
        mockReq.addParameter("shopCard[0].endTime","2050-01-02");
        mockReq.addParameter("shopCard[1].money","-2");
        mockReq.addParameter("shopCard[1].endTime","2030-01-02");
        return mockReq;
    }

测试结果如下:

代码语言:javascript复制
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 6 errors
Error in object '顾客': codes [errorCode.shopCard.is.error.顾客,errorCode.shopCard.is.error]; arguments []; default message [null]
Field error in object '顾客' on field 'shopCard[0].endTime': rejected value [2050-01-02]; codes [endTime.is.error.顾客.shopCard[0].endTime,endTime.is.error.顾客.shopCard.endTime,endTime.is.error.shopCard[0].endTime,endTime.is.error.shopCard.endTime,endTime.is.error.endTime,endTime.is.error.java.time.LocalDate,endTime.is.error]; arguments []; default message [购物卡截止时间有误]
Field error in object '顾客' on field 'shopCard[0].money': rejected value [-1]; codes [money.not.negative.顾客.shopCard[0].money,money.not.negative.顾客.shopCard.money,money.not.negative.shopCard[0].money,money.not.negative.shopCard.money,money.not.negative.money,money.not.negative.java.lang.Integer,money.not.negative]; arguments []; default message [null]
Error in object '顾客': codes [errorCode.shopCard.is.error.顾客,errorCode.shopCard.is.error]; arguments []; default message [null]
Field error in object '顾客' on field 'shopCard[1].endTime': rejected value [2030-01-02]; codes [endTime.is.error.顾客.shopCard[1].endTime,endTime.is.error.顾客.shopCard.endTime,endTime.is.error.shopCard[1].endTime,endTime.is.error.shopCard.endTime,endTime.is.error.endTime,endTime.is.error.java.time.LocalDate,endTime.is.error]; arguments []; default message [购物卡截止时间有误]
Field error in object '顾客' on field 'shopCard[1].money': rejected value [-2]; codes [money.not.negative.顾客.shopCard[1].money,money.not.negative.顾客.shopCard.money,money.not.negative.shopCard[1].money,money.not.negative.shopCard.money,money.not.negative.money,money.not.negative.java.lang.Integer,money.not.negative]; arguments []; default message [null]

完美 !


细节解释

这里对上面的过程进行一下简单的解释:

CustomerValidator实现很简单,具体的校验逻辑通常是针对两种数据实体,一种就是被验证对象本身,另一种就是被验证对象的相应属性。

如果被验证对象本身都不能通过验证,那么,这种错误被称为Global Error,这时,我们使用Errors的reject(String…)这组方法,向Errors中添加相应的错误信息。

如果被验证对象的某个属性域不能够通过验证,那么,我们称这种错误为Field Error,这时,我们使用Errors的rejectValue(String,String…)这组方法向Errors中添加相应的错误信息。

reject(String…)方法第一个参数是错误信息对应的errorCode,而rejectValue(String,String…)方法第一个参数是未能通过验证的属性域的名称,第二个参数才是对应错误信息的errorCode.

ShopCardValidator中有两个比较重要的点,需要我们的关注:

  • 对于不能通过数据验证逻辑的属性域,最基本的做法是通过Errors对象的rejectValue方法将其添加到Errors对象,不过,如果对应的某个对象域的验证仅限于是否为空的话,我们也可以使用ValidatorUtils这个工具类提供的rejectIfEmpty方法来达到同样的目的。
  • 如果要对当前对象的嵌套属性域进行验证,我们需要在调用对应的嵌套对象的Validator实现类之前,调用Errors的pushNestedPath方法来明确当前被验证对象的上下文路径,并且在调用之后,通过popNestedPath恢复之前的上下文路径。否则,当Errors对象绑定对应的嵌套对象属性的错误信息的时候,会认为该属性是上层目标对象上的属性,这时就会出现绑定上的异常了。

如果我们不使用pushNestedPath方法,Errors在记录money对应的错误信息的时候,同时需要记录对应该属性的值,那么它就会根据当前属性域对应的表达式到Command对象上获取。可以当它根据money到Customer上查找时,发现Customer对象上不存在一个叫做money的属性域,自然就会抛出异常。

可以,如果在此之前,我们通过pushNestedPath方法改变Errors注册属性域错误信息所使用的上下文路径,比如,变成shopCard[0],那么,当Errors注册money对应的错误信息的时候,就会以shopCard[0].money到Customer获取对应的属性值,那么自然就没有问题了。


在Spring mvc中,以上Validator实现类的执行以及后继错误信息的处理,将由BaseCommandController或者其子类接管,用户不需要操心,我们需要做的,就是设置相关的Validator到BaseCommandController


深入表单form处理流程

SimpleFormController及其父类AbstractFormController最主要的一个特定就是对表单的处理流程进行了统一。

AbstractFormController以模板方法模式从顶层界定了主体上的流程处理逻辑,而处理流程中某些特定动作则留给了子类实现。

以模板方法模式实现的整个流程控制,并非真得就像模板那样死板,我们可以通过覆写其中某些方法以天津自定义的行为逻辑,体现了整个流程的可扩展性和灵活性。

整个规范操作派还是起始于BaseCommandController,因此还是从该顶层类开始讲起:

无论是规范操作派,还是自由挥洒派,在Spring 4之后,基本都被移除了,转而被更加高效和现代化的controller体系所替代,我们后面再说


BaseCommandController—将数据绑定和校验结合在一起

BaseCommandController内部提供了对DataBinder进行配置的一些选项和目标对象,以及校验器管理了:

代码语言:javascript复制
   //目标对象名和对应class对象,用来反射初始化 
   	public static final String DEFAULT_COMMAND_NAME = "command";

	private String commandName = DEFAULT_COMMAND_NAME;

	private Class commandClass;
  
  //校验器数组
	private Validator[] validators;
 //是否在数据绑定结束后,进行数据校验
	private boolean validateOnBinding = true;
 
 //关于DataBinder一些配置
	private MessageCodesResolver messageCodesResolver;

	private BindingErrorProcessor bindingErrorProcessor;

	private PropertyEditorRegistrar[] propertyEditorRegistrars;

	private WebBindingInitializer webBindingInitializer;

BaseCommandController中只有一个核心方法bindAndValidate,该方法也是一个模板方法,大家值得学习:

BaseCommandController并没有覆写父类的handleRequestInternal方法,对应的方法会由子类覆写,因此父类提供的bindAndValidate核心方法,会在子类中被调用

代码语言:javascript复制
    //同时完成数据绑定和校验 
	protected final ServletRequestDataBinder bindAndValidate(HttpServletRequest request, Object command)
			throws Exception {
        //创建DataBinder
		ServletRequestDataBinder binder = createBinder(request, command);
		//BindException就是一个简单的代理类,所有方法的实现全部由传入的BindingResult完成,只不过该类实现了Exception接口
		BindException errors = new BindException(binder.getBindingResult());
		//是否阻止数据绑定,默认是flase
		if (!suppressBinding(request)) {
		    //进行数据绑定
			binder.bind(request);
			//留给子类的扩展接口
			onBind(request, command, errors);
			//isValidateOnBinding判断是否要在数据绑定结束后进行数据校验,默认为true
			//suppressValidation是否阻止当前的数据校验,默认false
			if (this.validators != null && isValidateOnBinding() && !suppressValidation(request, command, errors)) {    
			   //进行数据校验
				for (int i = 0; i < this.validators.length; i  ) {
					ValidationUtils.invokeValidator(this.validators[i], command, errors);
				}
			}
			//扩展接口
			onBindAndValidate(request, command, errors);
		}
		return binder;
	}
  • createBinder方法创建DataBinder的过程也值得一看
代码语言:javascript复制
	protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command)
		throws Exception {
       
		ServletRequestDataBinder binder = new ServletRequestDataBinder(command, getCommandName());
		//BaseCommandController内部提供的配置属性,当然要在这里决定是否起作用喽!
		prepareBinder(binder);
		//交给WebBindingInitializer决定,是否要对Binder进行一波配置更改
		initBinder(request, binder);
		return binder;
	}
代码语言:javascript复制
	protected final void prepareBinder(ServletRequestDataBinder binder) {
		if (useDirectFieldAccess()) {
			binder.initDirectFieldAccess();
		}
		if (this.messageCodesResolver != null) {
			binder.setMessageCodesResolver(this.messageCodesResolver);
		}
		if (this.bindingErrorProcessor != null) {
			binder.setBindingErrorProcessor(this.bindingErrorProcessor);
		}
		if (this.propertyEditorRegistrars != null) {
			for (int i = 0; i < this.propertyEditorRegistrars.length; i  ) {
				this.propertyEditorRegistrars[i].registerCustomEditors(binder);
			}
		}
	}
    
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
		if (this.webBindingInitializer != null) {
			this.webBindingInitializer.initBinder(binder, new ServletWebRequest(request));
		}
	}

这部分不清楚的,说明对DataBinder数据绑定体系结构不了解,可以先去了解一下,再回看:

Spring数据绑定之DataBinder篇—01

Spring数据绑定之 WebDataBinder、ServletRequestDataBinder、WebBindingInitializer…—02

模板方法模式固定基本流程 扩展接口: 极大提高框架的可扩展性


AbstractFormController—表单处理流程模板化

AbstractFormController负责规定好表单处理的模板化流程,以及相关扩展接口。

AbstractFormController实现了handleRequestInternal方法,所以一切的一切,都要从该方法讲起,这里表单处理模板化的入口:

代码语言:javascript复制
	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
			throws Exception {

		//判断当前请求是否是表单处理请求----如果是POST请求,就默认为true
		if (isFormSubmission(request)) {
			// Fetch form object from HTTP session, bind, validate, process submission.
			try {
			//反射获取目标对象
				Object command = getCommand(request);
			//绑定然后进行校验	
				ServletRequestDataBinder binder = bindAndValidate(request, command);
		    //用于存放错误信息		
				BindException errors = new BindException(binder.getBindingResult());
			//处理表单提交---子类实现
				return processFormSubmission(request, response, command, errors);
			}
			catch (HttpSessionRequiredException ex) {
				// Cannot submit a session form if no form object is in the session.
				if (logger.isDebugEnabled()) {
					logger.debug("Invalid submit detected: "   ex.getMessage());
				}
				//如果出现异常,那么进行处理
				//这里是再次尝试去提交一遍表单
				return handleInvalidSubmit(request, response);
			}
		}

		else {
			// New form to show: render form view.
			//渲染表单----最终调用到showForm方法
			return showNewForm(request, response);
		}
	}

AbstractFormController模板化流程简单概括就是:

  • AbstractFormController有两个实现子类如下:

分别是处理单表单和多表单提交流程的。

因为AbstractFormController及其子类的实现及源码就不展开讲了,因为这部分内容已经过时了,我觉得我们重点学习一下它的设计思想就行了,而核心的设计思想其实体现在父类AbstractFormController的handleRequestInternal方法中。


其他可用的Controller实现

我们已经了解了controller两大门派的几位主要弟子,虽然这两大门派都已经被淘汰了,但是思想是不断递进了,再后面学习新的Controller体系时,可以前后对比。

Spring MVC旧的controller体系的中还有几个用于不同目的实现类,下面可以来看看:


AbstractCommandController—增强版本的AbstractController

AbstractCommandController是规范操作派里面的少数派,它继承了BaseCommandController的数据绑定以及数据验证的功能支持,自身也定义了简单的请求处理流程,但它的主要目的不是面向表单的处理(该方法的功能已经由AbstractFormController家族接管了)。

实际上,我们可以认为AbstractCommandController只是一个加强型的AbstractController,直接继承AbstractController与直接继承AbstractCommandController差不多,唯一的区别就是后者进一步规范了参数的获取和验证操作。

代码语言:javascript复制
	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
			throws Exception {
        //反射实例化对象
		Object command = getCommand(request);
		//数据绑定和验证
		ServletRequestDataBinder binder = bindAndValidate(request, command);
		//错误记录
		BindException errors = new BindException(binder.getBindingResult());
		//留给子类实现的抽象方法
		return handle(request, response, command, errors);
	}
    
    //抽象方法
	protected abstract ModelAndView handle(
			HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)throws Exception;

继承AbstractCommandController,只需要实现handle模板方法即可,因为之前数据绑定和验证都已经搞定了。

这就好比继承AbstractController,我们只需要实现一个handleRequestInternal模板方法,而像页面缓存的设置等功能,则由AbstractController这个父类完成。

总之,通过继承AbstractCommandController实现一个Controller,我们只需要关心数据绑定和验证之后的处理逻辑的实现即可。


ParameterizableViewController

ParameterizableViewController的实现逻辑十分简单,或者干脆就没有处理逻辑,只是返回一个包含指定逻辑视图名的ModelAndView实例。

代码语言:javascript复制
public class ParameterizableViewController extends AbstractController {

	private String viewName;

	public void setViewName(String viewName) {
		this.viewName = viewName;
	}

	public String getViewName() {
		return this.viewName;
	}


	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request));
	}

}

该Controller实现类的主要目的是为了帮助我们将对具体视图的直接请求纳入到Spring mvc的统一处理体系之下,这样,我们依赖于ViewResolver的映射能力向客户端屏蔽不同视图类型的差异性。

比如,如果我们在页面内只是通过超链接访问某个页面(比如/WEB-INF/jsp/help/Help4sth.jsp),期间不需要任何处理,那么我们就可以在特定于DispathcerServlet的WebApplicationContext中添加如下Controller定义:

代码语言:javascript复制
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/"/>
        <property name="suffix" value=".jsp"/>
    </bean>


    <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/hello/**">parameterizableViewController</prop>
            </props>
        </property>
    </bean>

    <bean id="parameterizableViewController" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
        <property name="viewName" value="jsp/hello"></property>
    </bean>

通过viewName指定具体的视图名,parameterizableViewController将不做任何处理,直接将对应满足/hello/**格式的请求导向指定的视图。

InternalResourceViewResolver和SimpleUrlHandlerMapping前面两节简单介绍过了,不清楚可以看一下,后面也会对各个组件源码进行详细分析


Urlfilenameviewcontroller

使用ParameterizableViewController一次只能映射一个视图文件,如果有一组视图文件都需要不做任何处理直接返回的话,我们就得使用urlfilenameviewcontroller。

假设我们的帮助页面都存放在/WEB-INF/jsp/help/目录下,为了免去一个页面配置一个Controller之苦,我们就可以在特定于DispathcerServlet的WebApplicationContext中添加一个Urlfilenameviewcontroller的定义,让它全权管理对帮助页面的访问。

我们先来欣赏一下urlfilenameviewcontroller的源码,在给出一个具体实际例子作为演示吧:


父类AbstractUrlViewController中实现了核心方法handleRequestInternal,因此对于父类方法而言,只需要阅读该核心方法,即可一览全局:

代码语言:javascript复制
	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
	    //确定当前请求的访问路径,这里路径解析涉及到是保留context路径和servlet拦截的full path路径,还是去掉的问题,因此比较麻烦
	    //但是对于spring mvc提供的DispathcerServlet来说,默认拦截路径为/,我们这里在web.xml中配置的也是'/'
	    //并且contextPath也是'/'
	    //因此不需要操心路径的问题
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		//如何获取到viewName是由子类来实现的,这也是核心逻辑
		String viewName = getViewNameForRequest(request);
		if (logger.isDebugEnabled()) {
			logger.debug("Returning view name '"   viewName   "' for lookup path ["   lookupPath   "]");
		}
		return new ModelAndView(viewName, RequestContextUtils.getInputFlashMap(request));
	}

因此对于子类UrlFilenameViewController来说,核心方法就是确定viewName:

代码语言:javascript复制
	@Override
	protected String getViewNameForRequest(HttpServletRequest request) {
		//抽取出uri请求路径
		//获取AbstractUrlHandlerMapping中exposePathWithinMapping缓存的请求路径
		//SimpleUrlHandlerMapping模糊匹配的路径为/hello/** ,而我们访问的路径为/hello/hello
		//这里抽取拿到的就是去掉匹配前缀剩余部分,即hello
		String uri = extractOperableUrl(request);
		//通过请求路径,拿到viewName
		return getViewNameForUrlPath(uri);
	}
    
	private final Map<String, String> viewNameCache = new ConcurrentHashMap<String, String>(256);
    //会缓存请求路径对应的viewName
    protected String getViewNameForUrlPath(String uri) {
		String viewName = this.viewNameCache.get(uri);
		if (viewName == null) {
			viewName = extractViewNameFromUrlPath(uri);
			viewName = postProcessViewName(viewName);
			this.viewNameCache.put(uri, viewName);
		}
		return viewName;
	}
    
    //如果uri是 "/index.html" ,那么抽取后返回的就是index,俗称掐头去尾 
	protected String extractViewNameFromUrlPath(String uri) {
		int start = (uri.charAt(0) == '/' ? 1 : 0);
		int lastIndex = uri.lastIndexOf(".");
		int end = (lastIndex < 0 ? uri.length() : lastIndex);
		return uri.substring(start, end);
	}

    private String prefix = "";
	private String suffix = "";
    //后处理viewName,说白了就是加上给上面得到的viewName,加上前后缀
	protected String postProcessViewName(String viewName) {
		return getPrefix()   viewName   getSuffix();
	}

下面是一些请求路径经过UrlFilenameViewController处理后得到的viewName样子:

代码语言:javascript复制
"/index" -> "index"
"/index.html" -> "index"
"/index.html"   prefix "pre_" and suffix "_suf" -> "pre_index_suf"
"/products/view.html" -> "products/view"

实战:

代码语言:javascript复制
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/"/>
        <property name="suffix" value=".jsp"/>
    </bean>


    <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/hello/**">urlfilenameviewcontroller</prop>
            </props>
        </property>
    </bean>

    <bean id="urlfilenameviewcontroller" class="org.springframework.web.servlet.mvc.UrlFilenameViewController">
        <property name="prefix" value="jsp/"/>
    </bean>

ServletForwardingController和ServletWrappingController

ServletForwardingController是将对当前Controller的请求转发给当前应用中定义的某个servlet,也就是说,将controller与servlet拉低到了同一个水平。

ServletWrappingController则是将当前应用中的某个servlet直接包装为了一个Controller,所有到ServletWrappingController的请求实际上是由它内部所包装的这个servlet来处理的。

这两种controller实现类更多的是为了集成现有的servlet,比如将包含某些现有逻辑的servlet也纳入到spring mvc的处理体系中。

0 人点赞