前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring MVC各组件近距离接触--中--03

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

作者头像
大忽悠爱学习
发布2022-08-23 10:58:54
3590
发布2022-08-23 10:58:54
举报
文章被收录于专栏:c++与qt学习

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


前言

在这里插入图片描述
在这里插入图片描述

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

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的处理体系中。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring MVC各组件近距离接触--中--03
  • 前言
  • SimpleFormController
    • 数据绑定
      • 数据校验
        • 实例演示
          • 细节解释
        • 深入表单form处理流程
          • BaseCommandController—将数据绑定和校验结合在一起
          • AbstractFormController—表单处理流程模板化
      • 其他可用的Controller实现
        • AbstractCommandController—增强版本的AbstractController
          • ParameterizableViewController
            • Urlfilenameviewcontroller
              • ServletForwardingController和ServletWrappingController
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档