手写 MiniSpring 之 MVC

Spring Framework 的设计与迭代

每当仰望山巅,我就感到如坠深渊,于是我便开始攀登

复用:初始的 MVC

MiniSpring 使用 Tomcat 作为服务器,初始的 MVC 与 IoC 类似,可以复用 IoC 的代码
beans.xml:

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="/url" class="com.alioth4j.XxxClass" value="methodName"/>
</beans>

与 Servlet 相关的 web.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID">
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>com.alioth4j.web.DispatcherServlet</servlet-class>
        <init-param>    
            <param-name>servletConfigLocation</param-name>
            <param-value>/WEB-INF/servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

ClassPathXmlResource 复用 IoC 容器中的同名组件
MappingValue 类似 BeanDefinitionXmlConfigReader 类似 XmlBeanDefinitionReader
DispatcherServlet 类似初始 IoC 中的 ClassPathXmlApplicationContext

其中 DispatcherServlet 需要 extends javax.servlet.http.HttpServlet 并重写 init 方法

增强 MVC:组件扫描和注解支持

servlet.xml:

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<components>
    <component-scan base-package="com.alioth4j.xxx" />
</components>

@RequestMapping:

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

    String value() default "";

}

工具类 XmlScanComponentUtil:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class XmlScanComponentUtil {

    public static List<String> getNodeValue(URL xmlPath) {
        List<String> packages = new ArrayList<>();
        // dom4j
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(xmlPath);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        Element root = document.getRootElement();
        Iterator it = root.elementIterator();
        while (it.hasNext()) {
            Element element = (Element) it.next();
            packages.add(element.attributeValue("base-package"));
        }
        return packages;
    }

}

DispatcherServlet 中增加集合:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class DispatcherServlet extends HttpServlet {

    private List<String> packageNames = new ArrayList<>();
    private List<String> controllerNames = new ArrayList<>();
    private Map<String, Class<?>> controllerClasses = new HashMap<>();
    private Map<String, Object> controllerObjs = new HashMap<>();
    private List<String> urlMappingNames = new ArrayList<>();
    private Map<String, Object> mappingObjs = new HashMap<>();
    private Map<String, Method> mappingMethods = new HashMap<>();

    // ...

}

DispatcherServlet#init 中增加使用工具类获取要扫描的包名集合的操作,最后调用 refresh 方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class DispatcherServlet extends HttpServlet {

    // ...

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        // ...

        this.packageNames = XmlScanComponentHelper.getNodeValue(xmlPath);

        // ...

        refresh();
    }

    // ...

}

DispatcherServlet#refresh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class DispatcherServlet extends HttpServlet {

    // ...

    protected void refresh() {
        initController(); // 初始化 controller     
        initMapping(); // 初始化 url 映射
    }

}

DispatcherServlet#initController 装填 Controller 相关集合:

 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
public class DispatcherServlet extends HttpServlet {

    // ...

    protected void initController() {
        this.controllerNames = scanPackages(this.packageNames);
        for (String controllerName : this.controllerNames) {
            Class<?> clz = null;
            try {
                clz = Class.forName(controllerName);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            this.controllerClasses.put(controllerName, clz);
            Object obj = null;
            try {
                obj = clz.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            this.controllerObjs.put(controllerName, obj);
        }
    }

}

DispatcherServlet#initMapping 检查所有 Controller 的所有方法是否带有 @RequestMapping 注解,有的话加入到相关集合中:

 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
public class DispatcherServlet extends HttpServlet {

    // ...

    protected void initMapping() {
        for (String controllerName : this.controllerNames) {
            Class<?> clazz = this.controllerClasses.get(controllerName);
            Object obj = this.controllerObjs.get(controllerName);
            Method[] methods = clazz.getDeclaredMethods();
            if (methods != null) {
                for (Method method : methods) {
                    boolean isRequestMapping = method.isAnnotationPresent(RequestMapping.class);
                    if (isRequestMapping){
                        String methodName = method.getName();
                        String urlmapping = method.getAnnotation(RequestMapping.class).value();
                        this.urlMappingNames.add(urlmapping);
                        this.mappingObjs.put(urlmapping, obj);
                        this.mappingMethods.put(urlmapping, method);
                    }
                }
            }
        }
    }

}

使用递归根据包名获取所有 Controller 名称:

 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
public class DispatcherServlet extends HttpServlet {

    // ...

    private List<String> scanPackages(List<String> packageNames) {
        List<String> tempControllerNames = new ArrayList<>();
        for (String packageName : packageNames){
            tempControllerNames.addAll(scanPackage(packageName));
        }
        return tempControllerNames;
    }

    private List<String> scanPackage(String packageName) {
        List<String> tempControllerNames = new ArrayList<>();
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                scanPackage(packageName + "." + file.getName());
            } else {
                // -6 是去掉最后的 ".class"
                String controllerName = packageName + "." + file.getName().substring(0, file.getName() - 6);
                tempControllerNames.add(controllerName);
            }
        }
        return tempControllerNames;
    }

}

重写 doGet 方法,用于处理 Http Get 请求,从 HttpServletRequest 中取得请求路径,到集合中取得对应的对象,进行反射调用,最后把结果写回 HttpServletResponse:

 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
public class DispatcherServlet extends HttpServlet {

    // ...

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String sPath = request.getServletPath();
        if (!this.urlMappingNames.contains(sPath)) {
            return;
        }
        Object obj = null;
        Object objResult = null;
        try {
            Method method = this.mappingMethods.get(sPath);
            obj = this.mappingObjs.get(sPath);
            objResult = method.invoke(obj);
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        response.getWriter().append(objResult.toString());
    }

}

Servlet 规范:服务器的启动时序

  1. 读取 web.xml 中的配置
  2. 启动 Listener
  3. 启动 DispatcherServlet

Spring 通过这个时序实现 MVC 的功能

第一步,web.xml:

 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
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/nx/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>com.alioth4j.web.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>com.alioth4j.web.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>servletConfigLocation</param-name>
            <param-value>/WEB-INF/servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

第二步与第三步,先看 WebApplicationContext 家族
WebApplicationContext 接口:

1
2
3
4
5
6
7
8
public interface WebApplicationContext extends ApplicationContext {

    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

    ServletContext getServletContext();
    void setServletContext(ServletContext servletContext);

}

Listener 和 DispatcherServlet 中是两级 WebApplicationContext,因此用两个实现类 XmlWebApplicationContext 和 AnnotationConfigWebApplicationContext 分别与之对应

这两个 WebApplicationContext 的实现类 extends ClassPathXmlApplicationContext implements WebApplicationContext,由此将 IoC 和 MVC 关联起来

XmlWebApplicationContext:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class XmlWebApplicationContext extends ClassPathXmlApplicationContext implements WebApplicationContext {

    private ServletContext servletContext;

    public XmlWebApplicationContext(String fileName) {
        super(fileName);
    }


    @Override
    public ServletContext getServletContext() {
        return this.servletContext;
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

}

AnnotationConfigWebApplicationContext:

 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
75
76
77
public class AnnotationConfigWebApplicationContext extends ClassPathXmlApplicationContext implements WebApplicationContext {

    private WebApplicationContext parentApplicationContext;
    private ServletContext servletContext;
    DefaultListableBeanFactory beanFactory;
    private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>();

    public AnnotationConfigWebApplicationContext(String fileName) {
        this(fileName, null);
    }

    public AnnotationConfigWebApplicationContext(String fileName, WebApplicationContext parentApplicationContext) {
        this.parentApplicationContext = parentApplicationContext;
        this.servletContext = parentApplicationContext.getServletContext();
        URL xmlPath = null;
        try {
            xmlPath = this.getServletContext().getResource(fileName);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        List<String> packageNames = XmlScanComponentHelper.getNodeValue(xmlPath);
        List<String> controllerNames = scanPackages(packageNames);
        DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
        this.beanFactory = bf;
        this.beanFactory.setParent(this.parentApplicationContext.getBeanFactory());
        loadBeanDefinitions(controllerNames);
        refresh();
    }

    private void loadBeanDefinitions(List<String> controllerNames) {
        for (String controllerName : controllerNames) {
            String beanID = controllerName;
            String beanClassName = controllerName;
            BeanDefinition beanDefinition = new BeanDefinition(beanID, beanClassName);
            this.beanFactory.registerBeanDefinition(beanID, beanDefinition);
        }
    }

    private List<String> scanPackages(List<String> packageNames) {
        List<String> tempControllerNames = new ArrayList<>();
        for (String packageName : packageNames){
            tempControllerNames.addAll(scanPackage(packageName));
        }
        return tempControllerNames;
    }

    private List<String> scanPackage(String packageName) {
        List<String> tempControllerNames = new ArrayList<>();
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                scanPackage(packageName + "." + file.getName());
            } else {
                String controllerName = packageName + "." + file.getName().replace(".class", "");
                tempControllerNames.add(controllerName);
            }
        }
        return tempControllerNames;
    }

    @Override
    public ServletContext getServletContext() {
        return this.servletContext;
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Override
    public ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException {
        return this.beanFactory;
    }

}

因此第二步中的 Listener:

 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
public class ContextLoaderListener implements ServletContextListener {

    private static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

    private WebApplicationContext context;

    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        this.context = context;
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

    private void initWebApplicationContext(ServletContext servletContext) {
        String sContextLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
        WebApplicationContext wac = new XmlWebApplicationContext(sContextLocation);
        wac.setServletContext(servletContext);
        this.context = wac;
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
    }

}

第三步中的 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
public class DispatcherServlet extends HttpServlet {

    public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";

    private String servletConfigLocation;

    private WebApplicationContext parentApplicationContext;
    private WebApplicationContext webApplicationContext;

    // ...

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        this.parentApplicationContext = (WebApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        servletConfigLocation = config.getInitParameter("servletConfigLocation");
        URL xmlPath = null;
        try {
            xmlPath = this.getServletContext().getResource(servletConfigLocation);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        this.packageNames = XmlScanComponentUtil.getNodeValue(xmlPath);
        this.webApplicationContext = new AnnotationConfigWebApplicationContext(servletConfigLocation, this.parentApplicationContext);
        refresh();
    }

    // ...

}

职责分离:抽取出 url 映射与反射调用代码

MappingRegistry 中存储集合:

1
2
3
4
5
6
7
8
9
public class MappingRegistry {

    private List<String> urlMappingNames = new ArrayList<>();
    private Map<String, Object> mappingObjs = new HashMap<>();
    private Map<String, Method> mappingMethods = new HashMap<>();

    // getter and setter omitted

}

HandlerMapping 用于找到对应的反射相关对象:

1
2
3
4
5
public interface HandlerMapping {

    HandlerMethod getHandler(HttpServletRequest request) throws Exception;

}

实现类 RequestMappingHandlerMapping:

 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
public class RequestMappingHandlerMapping implements HandlerMapping {

    WebApplicationContext wac;

    private final MappingRegistry mappingRegistry = new MappingRegistry();
    
    public RequestMappingHandlerMapping(WebApplicationContext wac) {
        this.wac = wac;
        initMapping();
    }
    
    protected void initMapping() {
        Class<?> clz = null;
        Object obj = null;
        String[] controllerNames = this.wac.getBeanDefinitionNames();
        for (String controllerName : controllerNames) {
            try {
                clz = Class.forName(controllerName);
            } catch (ClassNotFoundException e1) {
                e1.printStackTrace();
            }
            try {
                obj = this.wac.getBean(controllerName);
            } catch (BeansException e) {
                e.printStackTrace();
            }
            Method[] methods = clz.getDeclaredMethods();
            if (methods != null) {
                for (Method method : methods) {
                    boolean isRequestMapping = method.isAnnotationPresent(RequestMapping.class);
                    if (isRequestMapping) {
                        String methodName = method.getName();
                        String urlmapping = method.getAnnotation(RequestMapping.class).value();
                        this.mappingRegistry.getUrlMappingNames().add(urlmapping);
                        this.mappingRegistry.getMappingObjs().put(urlmapping, obj);
                        this.mappingRegistry.getMappingMethods().put(urlmapping, method);
                    }
                }
            }
        }
    }

    @Override
    public HandlerMethod getHandler(HttpServletRequest request) throws Exception {
        String sPath = request.getServletPath();
    
        if (!this.mappingRegistry.getUrlMappingNames().contains(sPath)) {
            return null;
        }

        Method method = this.mappingRegistry.getMappingMethods().get(sPath);
        Object obj = this.mappingRegistry.getMappingObjs().get(sPath);
        HandlerMethod handlerMethod = new HandlerMethod(method, obj);
        
        return handlerMethod;
    }

}

反射相关对象封装在 HandlerMethod 中:

 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
public class HandlerMethod {

    private Object bean;
    private Class<?> beanType;
    private Method method;
    private MethodParameter[] parameters;
    private Class<?> returnType;
    private String description;
    private String className;
    private String methodName;
    
    public HandlerMethod(Method method, Object obj) {
        this.setMethod(method);
        this.setBean(obj);
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Object getBean() {
        return bean;
    }

    public void setBean(Object bean) {
        this.bean = bean;
    }

}

HandlerAdapter 用于反射调用:

1
2
3
4
5
public interface HandlerAdapter {

    void handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

}

具体实现类:

 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
public class RequestMappingHandlerAdapter implements HandlerAdapter {

    WebApplicationContext wac;

    public RequestMappingHandlerAdapter(WebApplicationContext wac) {
        this.wac = wac;
    }

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        handleInternal(request, response, (HandlerMethod) handler);
    }

    private void handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler) {
        Method method = handler.getMethod();
        Object obj = handler.getBean();
        Object objResult = null;
        try {
            objResult = method.invoke(obj);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        try {
            response.getWriter().append(objResult.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

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
public class DispatcherServlet extends HttpServlet {

    private HandlerMapping handlerMapping;
    private HandlerAdapter handlerAdapter;

    // ...

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) {
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webApplicationContext);
        try {
            doDispatch(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerMethod handlerMethod = null;
        handlerMethod = this.handlerMapping.getHandler(processedRequest);
        if (handlerMethod == null) {
            return;
        }
        HandlerAdapter ha = this.handlerAdapter;
        ha.handle(processedRequest, response, handlerMethod);
    }

}

增强 MVC:传入参数自动绑定

增强处为 RequestMappingHandlerAdapter 中反射调用之前,new 出 WebDataBinderFactory 对象,从 HandlerMethod 对象中获取方法参数,对每一个参数执行下列逻辑:

  • WebDataBinderFactory#createBinder 得到对应参数的 WebDataBinder
  • WebDataBinder#bind 方法首先将 request 中的参数转换成一个 PropertyValues 对象,之后调用 BeanWrapperImpl#setPropertyValues 方法
  • BeanWrapperImpl#setPropertyValues 中对每一个 propertyValues.getPropertyValues() 调用 setPropertyValue 方法
  • BeanWrapperImpl#setPropertyValue 去获取 PropertyEditor,优先获取自定义 PropertyEditor ,不存在再去获取默认的,接着转换参数类型,并借助内部类 BeanPropertyHandler 通过反射读写参数的值

PropertyEditorRegistrySupport 中分别存储默认和自定义 PropertyEditor,构造器中初始化默认 PropertyEditor,用户可以通过 registerCustomEditor 方法注册自定义 PropertyEditor

增强 MVC:处理返回结果

返回结果分为两种,视图和 @ResponseBody
增强处为 RequestMappingHandlerAdapter 中反射调用之后,判断 @ResponseBody 是否存在,进入对应逻辑

@ResponseBody

反射的返回值进行序列化,写入 response 中即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class RequestMappingHandlerAdapter {

    // ...

    @Override
    protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // ...

        if (invocableMethod.isAnnotationPresent(ResponseBody.class)) {
            // 序列化

        } else {
            // ...
        }
        // ...

    }

}

视图

Model 本质上是一个 Map
ModelAndView 中的 view 可能为 View 对象,也可能为逻辑视图名(String)
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
public class ModelAndView {

    private Map<String, Object> model = new HashMap<>();
    private Object view;
    
    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);
    }

    // ...

}

没有 @ResponseBody 就认为返回的是视图,如果是 ModelAndView 直接返回,是 String 认为是逻辑视图名,构造出 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
public class RequestMappingHandlerAdapter {

    // ...

    @Override
    protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // ...
                    
        ModelAndView mav = null;
        if (invocableMethod.isAnnotationPresent(ResponseBody.class)) {
            // 序列化

        } else {
            if (returnObj instanceof ModelAndView) {
                mav = (ModelAndView) returnObj;
            } else if (returnObj instanceof String) {
                String viewName = (String) returnObj;
                mav = new ModelAndView();
                mav.setViewName(viewName);
            }
        }
        return mav;
    }

}

返回到 DispatcherServlet 中,调用 render 方法对视图进行渲染:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class DispatcherServlet extends HttpServlet {

    // ...

    protected void render(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
        Map<String, Object> modelMap = mv.getModel();
        for (Map.Entry<String, Object> e : modelMap.entrySet()) {
            request.setAttribute(e.getKey(), e.getValue());
        }
        String path = "/" + mv.getViewName() + ".jsp";
        request.getRequestDispatcher(path).forward(request, response);
    }

}

至此 MVC 完成

Licensed under CC BY-NC-SA 4.0
Who comes from mountains, rivers, lakes and seas, yet is confined to days, nights, kitchen and love?