11|ModelAndView :如何将处理结果返回给前端?

你好,我是郭屹。今天我们继续手写MiniSpring。这也是MVC内容的最后一节。

上节课,我们对HTTP请求传入的参数进行了自动绑定,并调用了目标方法。我们再看一下整个MVC的流程,现在就到最后一步了,也就是把返回数据回传给前端进行渲染。

图片
在现代Web开发中,后端与前端的交互方式主要有两种:返回纯数据和返回整个页面。随着前后端分离架构的流行,第一种方式逐渐成为主流。在这种模式下,后端仅负责处理业务逻辑并将数据返回给前端,前端则使用React或Vue.js等框架自行渲染界面。这种方式提高了开发效率和用户体验。 第二种方式是后端将数据整合到页面中,然后整个页面回传给前端,JSP技术是这种模式的代表。虽然这种方式在前几年较为流行,但近年来已逐渐被前后端分离模式所取代。 我们通过手写MiniSpring框架来深入理解Spring框架的工作原理和程序结构。在这个过程中,我们会分析上述两种情况。

处理返回数据

与参数绑定相反,处理返回数据是一个反向过程,即将后端方法的返回值(Java对象)按照某种字符串格式回传给前端。我们可以通过@ResponseBody注解来实现这一点。

定义接口

为了使controller能够返回格式化的字符流数据给前端,我们可以定义一个接口,增加一个功能来实现数据的格式转换。

1
2
3
4
5
6
7
8
package com.minis.web;

import java.io.IOException;
import javax.servlet.http.HttpServletResponse;

public interface HttpMessageConverter {
	void write(Object obj, HttpServletResponse response) throws IOException;
}

我们这里给一个默认的实现——DefaultHttpMessageConverter,把Object转成JSON串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.minis.web;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;

public class DefaultHttpMessageConverter implements HttpMessageConverter {
	String defaultContentType = "text/json;charset=UTF-8";
	String defaultCharacterEncoding = "UTF-8";
	ObjectMapper objectMapper;

	public ObjectMapper getObjectMapper() {
		return objectMapper;
	}
	public void setObjectMapper(ObjectMapper objectMapper) {
		this.objectMapper = objectMapper;
	}
	public void write(Object obj, HttpServletResponse response) throws IOException {
        response.setContentType(defaultContentType);
        response.setCharacterEncoding(defaultCharacterEncoding);
        writeInternal(obj, response);
        response.flushBuffer();
	}
	private void writeInternal(Object obj, HttpServletResponse response) throws IOException{
		String sJsonStr = this.objectMapper.writeValuesAsString(obj);
		PrintWriter pw = response.getWriter();
		pw.write(sJsonStr);
	}
}

这个message converter很简单,就是给response写字符串,用到的工具是ObjectMapper。我们就重点看看这个mapper是怎么做的。

定义一个接口ObjectMapper。

1
2
3
4
5
6
package com.minis.web;
public interface ObjectMapper {
	void setDateFormat(String dateFormat);
	void setDecimalFormat(String decimalFormat);
	String writeValuesAsString(Object obj);
}

最重要的接口方法就是writeValuesAsString(),将对象转成字符串。

我们给一个默认的实现——DefaultObjectMapper,在writeValuesAsString中拼JSON串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.minis.web;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class DefaultObjectMapper implements ObjectMapper{
	String dateFormat = "yyyy-MM-dd";
	DateTimeFormatter datetimeFormatter = DateTimeFormatter.ofPattern(dateFormat);

	String decimalFormat = "#,##0.00";
	DecimalFormat decimalFormatter = new DecimalFormat(decimalFormat);

	public DefaultObjectMapper() {
	}

	@Override
	public void setDateFormat(String dateFormat) {
		this.dateFormat = dateFormat;
		this.datetimeFormatter = DateTimeFormatter.ofPattern(dateFormat);
	}

	@Override
	public void setDecimalFormat(String decimalFormat) {
		this.decimalFormat = decimalFormat;
		this.decimalFormatter = new DecimalFormat(decimalFormat);
	}
	public String writeValuesAsString(Object obj) {
		String sJsonStr = "{";
		Class<?> clz = obj.getClass();

		Field[] fields = clz.getDeclaredFields();
        //对返回对象中的每一个属性进行格式转换
		for (Field field : fields) {
			String sField = "";
			Object value = null;
			Class<?> type = null;
			String name = field.getName();
			String strValue = "";
			field.setAccessible(true);
			value = field.get(obj);
			type = field.getType();

            //针对不同的数据类型进行格式转换
			if (value instanceof Date) {
				LocalDate localDate = ((Date)value).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
				strValue = localDate.format(this.datetimeFormatter);
			}
			else if (value instanceof BigDecimal || value instanceof Double || value instanceof Float){
				strValue = this.decimalFormatter.format(value);
			}
			else {
				strValue = value.toString();
			}

            //拼接Json串
			if (sJsonStr.equals("{")) {
				sField = "\"" + name + "\":\"" + strValue + "\"";
			}
			else {
				sField = ",\"" + name + "\":\"" + strValue + "\"";
			}

			sJsonStr += sField;
		}
		sJsonStr += "}";
		return sJsonStr;
	}
}

在处理返回的数据时,我们同样需要使用到LocalDate和DecimalFormatter。目前为止,我们已经支持了Date、Number和String三种类型。你可以进一步扩展以支持更多的数据类型。 为了实现这一点,我们需要回到RequestMappingHandlerAdapter这个类,增加一个属性messageConverter,通过它来转换数据。这样,在方法调用返回数据之前,我们可以使用这个工具来处理返回的数据。

1
2
3
	public class RequestMappingHandlerAdapter implements HandlerAdapter {
		private WebBindingInitializer webBindingInitializer = null;
		private HttpMessageConverter messageConverter = null;

现在既有传入的webBingingInitializer,也有传出的messageConverter。

在关键方法invokeHandlerMethod()里增加对@ResponseBody的处理,也就是调用messageConverter.write()把方法返回值转换成字符串。

1
2
3
4
5
6
7
8
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		... ...
		if (invocableMethod.isAnnotationPresent(ResponseBody.class)){ //ResponseBody
	        this.messageConverter.write(returnObj, response);
		}
		... ...
	}

同样的webBindingInitializer和messageConverter都可以通过配置注入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
	<bean id="handlerAdapter" class="com.minis.web.servlet.RequestMappingHandlerAdapter">
	 <property type="com.minis.web.HttpMessageConverter" name="messageConverter" ref="messageConverter"/>
	 <property type="com.minis.web.WebBindingInitializer" name="webBindingInitializer" ref="webBindingInitializer"/>
	</bean>

	<bean id="webBindingInitializer" class="com.test.DateInitializer" />

	<bean id="messageConverter" class="com.minis.web.DefaultHttpMessageConverter">
	 <property type="com.minis.web.ObjectMapper" name="objectMapper" ref="objectMapper"/>
	</bean>
	<bean id="objectMapper" class="com.minis.web.DefaultObjectMapper" >
	 <property type="String" name="dateFormat" value="yyyy/MM/dd"/>
	 <property type="String" name="decimalFormat" value="###.##"/>
	</bean>

最后在DispatcherServlet里,通过getBean获取handlerAdapter,当然这里需要约定一个名字,整个过程就连起来了。

1
2
3
	protected void initHandlerAdapters(WebApplicationContext wac) {
 		this.handlerAdapter = (HandlerAdapter) wac.getBean(HANDLER_ADAPTER_BEAN_NAME);
    }

测试的客户程序HelloWorldBean修改如下:

1
2
3
4
5
6
7
	@RequestMapping("/test7")
	@ResponseBody
	public User doTest7(User user) {
		user.setName(user.getName() + "---");
		user.setBirthday(new Date());
		return user;
	}

在Spring MVC框架中,处理HTTP请求返回响应的过程涉及到如何将后端的数据转换成前端可以理解的格式。当程序中使用了@ResponseBody注解时,意味着方法的返回值(例如一个User对象)将直接被序列化为JSON字符串,并作为HTTP响应体返回给客户端。这一过程通过消息转换器(Message Converter)来完成,它能够自动识别并转换多种数据类型至指定格式,比如JSON或XML等,且用户可以根据需要自定义这些格式。

接下来,我们探讨一下ModelAndView的作用。ModelAndView是Spring MVC中用来封装控制器(Controller)处理结果的一个类。它不仅包含了从控制器传递到视图的数据(Model),还指定了要渲染哪一个视图页面(View)。这种方式非常适合于那些不直接返回数据而是希望跳转到特定页面并将一些数据传入该页面以供展示的情况。简而言之,ModelAndView充当了一个桥梁的角色,连接着后端逻辑与前端展现,使得开发者能够更加方便地组织和管理应用程序中的数据流与视图选择。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.minis.web.servlet;

import java.util.HashMap;
import java.util.Map;

public class ModelAndView {
	private Object view;
	private Map<String, Object> model = new HashMap<>();

	public ModelAndView() {
	}
	public ModelAndView(String viewName) {
		this.view = viewName;
	}
	public ModelAndView(View view) {
		this.view = view;
	}
	public ModelAndView(String viewName, Map<String, ?> modelData) {
		this.view = viewName;
		if (modelData != null) {
			addAllAttributes(modelData);
		}
	}
	public ModelAndView(View view, Map<String, ?> model) {
		this.view = view;
		if (model != null) {
			addAllAttributes(model);
		}
	}
	public ModelAndView(String viewName, String modelName, Object modelObject) {
		this.view = viewName;
		addObject(modelName, modelObject);
	}
	public ModelAndView(View view, String modelName, Object modelObject) {
		this.view = view;
		addObject(modelName, modelObject);
	}
	public void setViewName(String viewName) {
		this.view = viewName;
	}
	public String getViewName() {
		return (this.view instanceof String ? (String) this.view : null);
	}
	public void setView(View view) {
		this.view = view;
	}
	public View getView() {
		return (this.view instanceof View ? (View) this.view : null);
	}
	public boolean hasView() {
		return (this.view != null);
	}
	public boolean isReference() {
		return (this.view instanceof String);
	}
	public Map<String, Object> getModel() {
		return this.model;
	}
	private void addAllAttributes(Map<String, ?> modelData) {
		if (modelData != null) {
			model.putAll(modelData);
		}
	}
	public void addAttribute(String attributeName, Object attributeValue) {
		model.put(attributeName, attributeValue);
	}
	public ModelAndView addObject(String attributeName, Object attributeValue) {
		addAttribute(attributeName, attributeValue);
		return this;
	}
}

这个类里面定义了Model和View,分别代表返回的数据以及前端表示,我们这里就是指JSP。

有了这个结构,我们回头看调用目标方法之后返回的那段代码,把类RequestMappingHandlerAdapter的方法invokeHandlerMethod()返回值改为ModelAndView。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
       			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	ModelAndView mav = null;
    //如果是ResponseBody注解,仅仅返回值,则转换数据格式后直接写到response
	if (invocableMethod.isAnnotationPresent(ResponseBody.class)){ //ResponseBody
	        this.messageConverter.write(returnObj, response);
	}
	else { //返回的是前端页面
		if (returnObj instanceof ModelAndView) {
			mav = (ModelAndView)returnObj;
		}
		else if(returnObj instanceof String) { //字符串也认为是前端页面
			String sTarget = (String)returnObj;
			mav = new ModelAndView();
			mav.setViewName(sTarget);
		}
	}

	return mav;
}

通过上面这段代码我们可以知道,调用方法返回的时候,我们处理了三种情况。

  1. 如果声明返回的是ResponseBody,那就用MessageConvert把结果转换一下,之后直接写回response。
  2. 如果声明返回的是ModelAndView,那就把结果包装成一个ModelAndView对象返回。
  3. 如果声明返回的是字符串,就以这个字符串为目标,最后还是包装成ModelAndView返回。

View

到这里,调用方法就返回了。不过事情还没完,之后我们就把注意力转移到MVC环节的最后一部分:View层。View,顾名思义,就是负责前端界面展示的部件,当然它最主要的功能就是,把数据按照一定格式显示并输出到前端界面上,因此可以抽象出它的核心方法render(),我们可以看下View接口的定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.minis.web.servlet;

import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface View {
	void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception;
	default String getContentType() {
		return null;
	}
	void setContentType(String contentType);
	void setUrl(String url);
	String getUrl();
	void setRequestContextAttribute(String requestContextAttribute);
	String getRequestContextAttribute();
}

在开发Web应用时,render()方法的作用是将后端数据渲染到前端页面。以下是render()方法的基本步骤和JSP页面渲染的详细说明:

  1. 获取HTTP请求和响应:render()方法首先需要获取HTTP请求(request)和响应(response),这两个对象由服务器在处理HTTP请求时自动创建。

  2. 处理业务数据:接着,render()方法会处理业务数据,这些数据通常被封装在一个名为ModelAndView的对象中,该对象由我们的MiniSpring框架创建。

  3. 准备数据:准备好request、response和ModelAndView中的数据后,就可以将这些数据传递给前端页面。

  4. JSP页面渲染:以JSP为例,渲染过程与手工编写JSP页面类似。首先,需要设置属性值,然后使用请求转发(forward)将请求发送到JSP页面。

  5. 示例代码:以下是render()方法的简化代码示例,展示了如何将数据设置到JSP页面:

    1
    2
    3
    4
    5
    
    // 设置属性值
    request.setAttribute("aaaaaaa", modelAndView.getModel().get("aaaaaaa"));
    
    // 请求转发
    request.getRequestDispatcher("/aaaaaaa.jsp").forward(request, response);
    

通过上述步骤,render()方法能够将后端数据有效地展示在前端界面上,实现前后端的数据交互。

1
2
3
	request.setAttribute(key1, value1);
	request.setAttribute(key2, value2);
	request.getRequestDispatcher(url).forward(request, response);

照此办理,DispatcherServlet的doDispatch()方法调用目标方法后,可以通过一个render()来渲染这个JSP,你可以看一下doDispatch()相关代码。

1
2
3
	HandlerAdapter ha = this.handlerAdapter;
	mv = ha.handle(processedRequest, response, handlerMethod);
	render(processedRequest, response, mv);

这个render()方法可以考虑这样实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	//用jsp 进行render
	protected void render( HttpServletRequest request, HttpServletResponse response,ModelAndView mv) throws Exception {
		//获取model,写到request的Attribute中:
		Map<String, Object> modelMap = mv.getModel();
		for (Map.Entry<String, Object> e : modelMap.entrySet()) {
			request.setAttribute(e.getKey(),e.getValue());
		}
        //输出到目标JSP
		String sTarget = mv.getViewName();
		String sPath = "/" + sTarget + ".jsp";
		request.getRequestDispatcher(sPath).forward(request, response);
	}

在Web开发中,程序经常需要从Model中获取数据,并将其作为属性值写入Request的attribute中,然后根据页面路径显示相应的View。这一过程类似于手工编写JSP文件,既简洁又有效。然而,当前程序存在两个主要问题:

  1. 确定目标View
  • 程序如何找到需要显示的目标View?在示例中,我们通过硬编码的方式指定了一个固定的路径/xxxx.jsp,但实际上,这些路径应该允许用户自行配置,而不是在代码中硬编码。

  • 解决方案是引入一个新的组件ViewResolver,它根据一定的规则或用户配置来确定View的位置。

  1. 扩展性问题
  • 拿到View后,程序直接使用request.forward()方法,这种方法仅适用于JSP页面,无法扩展到其他类型的页面,如Excel或PDF。

  • 因此,需要对render()方法进行改造,以支持不同类型的页面渲染。 为了解决第一个问题,即如何找到需要显示的目标View,我们可以定义ViewResolver组件如下:

  • ViewResolver定义

  • ViewResolver负责根据配置或规则解析出View的路径。

  • 它可以让用户通过配置文件或代码来指定View的路径,从而实现路径的动态解析。

  • 这样,程序就可以根据ViewResolver提供的路径来加载和渲染View,而不需要硬编码路径。 通过引入ViewResolver,我们可以提高程序的灵活性和可配置性,使其能够适应不同的开发需求和环境。

1
2
3
4
5
package com.minis.web.servlet;

public interface ViewResolver {
	View resolveViewName(String viewName) throws Exception;
}

这个ViewResolver就是根据View的名字找到实际的View,有了这个ViewResolver,就不用写死JSP路径,而是可以通过resolveViewName()方法来获取一个View。拿到目标View之后,我们把实际渲染的功能交给View自己完成。我们把程序改成下面这个样子。

1
2
3
4
5
6
	protected void render( HttpServletRequest request, HttpServletResponse response,ModelAndView mv) throws Exception {
		String sTarget = mv.getViewName();
		Map<String, Object> modelMap = mv.getModel();
		View view = resolveViewName(sTarget, modelMap, request);
		view.render(modelMap, request, response);
	}

在MiniSpring里,我们提供一个InternalResourceViewResolver,作为启动JSP的默认实现,它是这样定位到显示目标View的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.minis.web.servlet.view;

import com.minis.web.servlet.View;
import com.minis.web.servlet.ViewResolver;

public class InternalResourceViewResolver implements ViewResolver{
	private Class<?> viewClass = null;
	private String viewClassName = "";
	private String prefix = "";
	private String suffix = "";
	private String contentType;

	public InternalResourceViewResolver() {
		if (getViewClass() == null) {
			setViewClass(JstlView.class);
		}
	}

	public void setViewClassName(String viewClassName) {
		this.viewClassName = viewClassName;
		Class<?> clz = null;
		try {
			clz = Class.forName(viewClassName);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		setViewClass(clz);
	}

	protected String getViewClassName() {
		return this.viewClassName;
	}
	public void setViewClass(Class<?> viewClass) {
		this.viewClass = viewClass;
	}
	protected Class<?> getViewClass() {
		return this.viewClass;
	}
	public void setPrefix(String prefix) {
		this.prefix = (prefix != null ? prefix : "");
	}
	protected String getPrefix() {
		return this.prefix;
	}
	public void setSuffix(String suffix) {
		this.suffix = (suffix != null ? suffix : "");
	}
	protected String getSuffix() {
		return this.suffix;
	}
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}
	protected String getContentType() {
		return this.contentType;
	}

	@Override
	public View resolveViewName(String viewName) throws Exception {
		return buildView(viewName);
	}

	protected View buildView(String viewName) throws Exception {
		Class<?> viewClass = getViewClass();

		View view = (View) viewClass.newInstance();
		view.setUrl(getPrefix() + viewName + getSuffix());

		String contentType = getContentType();
		view.setContentType(contentType);

		return view;
	}
}

理解MiniSpring框架中的View解析和渲染过程

1. View实例的创建与URL定位

在MiniSpring框架中,首先会创建一个View实例。这个实例通过配置生成一个URL,用于定位到显示目标。这个过程类似于我们在JSP文件中手动编写代码实现的URL定位。通过使用resolver,框架能够根据配置从/jsp/路径下获取到指定的xxxx.jsp页面,从而解决了页面定位的问题。

2. ContentType的设置

在定位到目标页面之后,框架会设置相应的ContentType,以确保浏览器能够正确解析页面内容。

3. DispatcherServlet的角色

DispatcherServlet在MiniSpring框架中扮演的角色是控制流程,而不是实际的渲染工作。它不负责知道如何渲染前端页面,这些工作应该由具体的View实现类来完成。因此,我们不再需要在DispatcherServlet中编写request forward()这样的代码,而是应该将其写入View的render()方法中。

4. View的实现:JstlView

MiniSpring框架提供了一个默认的View实现类:JstlView。这个类负责具体的页面渲染工作,使得DispatcherServlet可以专注于控制流程,而不是渲染细节。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.minis.web.servlet.view;

import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.minis.web.servlet.View;

public class JstlView implements View{
	public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=ISO-8859-1";
	private String contentType = DEFAULT_CONTENT_TYPE;
	private String requestContextAttribute;
	private String beanName;
	private String url;

	public void setContentType(String contentType) {
		this.contentType = contentType;
	}
	public String getContentType() {
		return this.contentType;
	}
	public void setRequestContextAttribute(String requestContextAttribute) {
		this.requestContextAttribute = requestContextAttribute;
	}
	public String getRequestContextAttribute() {
		return this.requestContextAttribute;
	}
	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}
	public String getBeanName() {
		return this.beanName;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public String getUrl() {
		return this.url;
	}
	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		for (Entry<String, ?> e : model.entrySet()) {
			request.setAttribute(e.getKey(),e.getValue());
		}
		request.getRequestDispatcher(getUrl()).forward(request, response);
	}
}

代码迁移与解耦

在最新的代码架构中,程序的核心任务保持不变,但代码的位置发生了变化,现在被移动到了View层。这一变化实现了前端渲染工作的解耦,使得DispatcherServlet不再负责渲染,从而允许我们扩展到多种前端技术,例如Excel和PDF等。

IoC容器的应用

对于InternalResourceViewResolverJstlView,我们可以利用IoC(控制反转)容器的机制,通过配置实现自动注入。这种方式提高了代码的灵活性和可维护性。

总结

通过将代码迁移到View层并利用IoC容器,我们不仅实现了功能的解耦,还提高了代码的扩展性和灵活性。

1
2
3
4
5
    <bean id="viewResolver" class="com.minis.web.servlet.view.InternalResourceViewResolver" >
	 <property type="String" name="viewClassName" value="com.minis.web.servlet.view.JstlView" />
	 <property type="String" name="prefix" value="/jsp/" />
	 <property type="String" name="suffix" value=".jsp" />
    </bean>

aaaaaaa 在Spring MVC框架中,DispatcherServlet的初始化过程中会根据配置加载实际的ViewResolver和View。这标志着整个处理流程的关键步骤之一。在本节课中,我们深入探讨了MVC模式下目标方法调用后的后续处理过程,包括数据格式的自动转换、视图的查找以及页面的渲染。我们可以观察到,Spring MVC并没有对具体的数据转换规则做出硬性规定,而是通过MessageConverter来完成这项任务;对于视图的定位也没有明确指定实现方式,而是依赖于ViewResolver组件;同样地,页面的具体渲染逻辑也是由实现了View接口的类来负责。这种设计使得开发者能够灵活地针对不同的场景选择合适的解决方案。

尽管JSP技术已经不再流行,但是这种将不同功能解耦并分配给专门组件的设计理念依然值得我们学习和借鉴。理解这些组件如何协同工作,有助于我们在开发过程中更好地利用Spring MVC提供的灵活性。

完整源代码可以访问:https://github.com/YaleGuo/minis

课后思考题

aaaaaaa

  • 当前系统仅支持Date、Number及String三种类型的数据返回。如果需要扩展支持更多数据类型,我们应该怎样做?

  • 目前仅限于使用JSP作为前端展示手段。那么,如何让系统兼容其他类型的前端技术或模板引擎呢? 欢迎你在留言区分享你的想法与见解,并邀请你将本课程内容转发给感兴趣的朋友。期待下次课程再见!