springmvc执行原理和流程

示例项目见第四部分

1 原理

 1.1 配置文件web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!--配置dispatcher.xml作为mvc的配置文件-->
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

对于Servlet容器Tomcat,有两个配置文件比较重要:分别是server.xml和web.xml,server.xml主要是Tomcat的配置文件,不是Servlet规范中的东西。web.xml是Servlet规范中的东西,其每个标签的定义都需要符合Servlet规范。我们来了解一下web.xml中的配置。

在另一个博文https://www.cnblogs.com/zhenjingcool/p/16585255.html中,我们了解到springmvc有两个上下文,分别是根上下文和web上下文。其中根上下文是通过如下代码配置的

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

https://www.cnblogs.com/zhenjingcool/p/16585255.html中我们知道,Tomcat通过ContextLoaderListener实现根上下文的实例化。实例化过程中,根上下文的配置和bean都配置在 <context-param> 中,也就是applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.szj"/>
    <mvc:annotation-driven />
    <bean></bean>
</beans>

在这个配置文件中,如果需要配置bean,我们使用<bean>标签进行配置。

<mvc:annotation-driven />是告知Spring,我们启用注解驱动。然后Spring会自动为我们注册RequestMappingHandlerMapping和AnnotationMethodHandlerAdapter等几个Bean到工厂中,此时我们可以使用@RequestMapping、@Valid注解来处理请求,也可以使用@ResponseBody来处理返回结果。

<context:component-scan base-package="com.szj"/>。配置组件扫描器。即Spring容器初始化时,扫描base-package包或者子包下面的Java文件,如果扫描到有@controller、@Service、@Repository、@Component等注解的java类,就会将这些bean注册到工厂中。

当然,还有一些其他的配置,可参考:https://blog.csdn.net/originations/article/details/88843698

下面的配置是前端控制器DispatcherServlet

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!--配置dispatcher.xml作为mvc的配置文件-->
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

前端控制器实例化过程中,会创建第二个上下文,这个上下文主要管理如下bean:处理器映射器、处理器适配器、视图解析器等。

其余部分待补充...

2 流程

 

3 源码分析

3.1 启动

对于springmvc,我们会打包成war包部署到tomcat。这里的启动指的是Tomcat的启动。

我们以第四部分示例项目为例说明,在web.xml文件中我们配置了前端控制器DispatcherServlet,并设置了跟随Tomcat一起启动。

我们通过这个博文https://www.cnblogs.com/zhenjingcool/p/15878453.html我们了解到,在Tomcat启动后,容器加载DispatcherServlet时,首先调用init方法。

GenericServlet的init方法

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

该方法是由Tomcat容器调用的,传入的参数config是容器生成的。

注意:调试的时候会发现这个方法会调用多次。原因是:我们idea启动时,除了当前web应用被部署外,还会默认部署manager应用。而这两个应用中都有多个随Tomcat一起启动的servlet,每个servlet都会调用一次init方法。具体细节参考https://www.cnblogs.com/zhenjingcool/p/16542157.html的1.5小结 (idea部署tomcat的方式分析)

然后执行this.init()调用HttpServletBean的init()方法

    public final void init() throws ServletException {

        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();
    }

然后调用initServletBean();该方法在FrameworkServlet中实现

    protected final void initServletBean() throws ServletException {
        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }
    }

我们看一下 this.webApplicationContext = initWebApplicationContext(); 

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext wac = this.webApplicationContext;

        onRefresh(wac);

        return wac;
    }

这里调用了onRefresh()方法,该方法在DispatcherServlet中实现。

    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

又调用了initStrategies方法

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我们重点看一下 initHandlerMappings(context); 和 initHandlerAdapters(context); ,分别是初始化处理器映射器和处理器适配器。

initHandlerMappings方法初始化处理器映射器,如果web.xml中配置了处理器映射器,将在这里处理。或者如果applicationContext.xml中配置了 <mvc:annotation-driven /> 将会在根上下文中默认添加RequestMappingHandleMapping和BeanNameUrlHandlerMapping这两个bean,DispatcherServlet实例化的时候会获取父容器中的实现了HandleMapping的实例作为自身的处理器映射器。否则,将使用默认的。

    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        // 在ApplicationContext查找所有的HandlerMapping.,如果我们在web.xml中配置了处理器映射器,这里会获取到
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }

        // 如果web.xml中没有配置处理器映射器,这里添加默认的
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        }
    }

在该博文末尾提供的实例项目中,我们在web.xml中没有配置处理器映射器,在applicationContext.xml中也没有配置<mvc:annocation-driven/>,所以会执行

this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);

我们看一下这个方法

    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<>(classNames.length);
            for (String className : classNames) {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            return strategies;
        }
        else {
            return new LinkedList<>();
        }
    }

其中 private static final Properties defaultStrategies; 是一个Properties对象的实例,在该实例中为我们提供了如下2个默认处理器映射器

org.springframework.web.servlet.HandlerMapping -> 
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

然后会遍历这两个默认处理器映射器,执行如下方法,我们看一下

Object strategy = createDefaultStrategy(context, clazz);
    protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
        return context.getAutowireCapableBeanFactory().createBean(clazz);
    }

这里会实例化 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping 

我们先看一下实例化 BeanNameUrlHandlerMapping 的过程。

在实例化之前会先执行BeanPostProcessor,其中就包括ApplicationContextAwareProcessor,调用其postProcessBeforeInitialization方法,最终会导致 BeanNameUrlHandlerMapping 其父类的如下方法被调用

    protected void detectHandlers() throws BeansException {
        ApplicationContext applicationContext = obtainApplicationContext();
        String[] beanNames = applicationContext.getBeanNamesForType(Object.class));

        // Take any bean name that we can determine URLs for.
        for (String beanName : beanNames) {
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
        }
    }

这里获取到applicationContext所有的bean定义,然后遍历这些bean,判断beanname是否以"/"开头,然后添加到列表urls里面返回,这也是为啥web.xml中配置的controller对应的名称必须以"/"开头的原因,如下:

    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

如果beanname以"/"开头,则添加到this.handlerMap中

this.handlerMap.put(urlPath, resolvedHandler);

最终,我们得到的这个handlerMapper里面有一个元素

 

 

实例化 RequestMappingHandlerMapping  的过程类似,这里不再赘述。

至此处理器映射器初始化完成,处理器映射器里面包含了所有url和controller的对应关系。

我们再回到 DispatcherServlet 的initStrategies方法

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我们接着看处理器适配器的初始化过程 initHandlerAdapters(context); 

    private void initHandlerAdapters(ApplicationContext context) {
        this.handlerAdapters = null;

        // 在ApplicationContext中找到所有的处理器适配器.如果我们在web.xml中配置了处理器适配器,这里会获取到
        Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }

        // 如果web.xml中没有配置处理器适配器,这里创建默认的
        if (this.handlerAdapters == null) {
            this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        }
    }

在实例项目中,我们没有在web.xml中配置处理器适配器,所以会创建默认的处理器适配器 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); 

和处理器映射器的逻辑相同,springmvc为我们准备了3个默认处理器适配器

org.springframework.web.servlet.HandlerAdapter ->
 org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

最终DispatcherServlet中得到一个this.handlerAdapters列表,包含以上3个处理器适配器。

 

我们再回到DispatcherServlet的initStrategies方法

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

同样的分析过程,我们可以分析视图解析器的初始化过程 initViewResolvers(context); ,这里不再赘述,只说明一点,因为我们在实例项目中的web.xml中配置了视图解析器,所以不会创建默认的视图解析器,会使用我们配置的视图解析器。

 

 

 

3.2 处理请求

以示例代码中的get /hello为例

我们在web.xml中配置了DispatcherServlet并且配置了所有url都通过这个Servlet处理。根据Servlet运行原理https://www.cnblogs.com/zhenjingcool/p/15878453.html我们知道,Tomcat容器接收到请求之后,首先调用的是Servlet的service方法。

HttpServlet

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        service(request, response);
    }
}

然后,调用重载方法service(request,response)

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            doGet(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        }//其他method省略
    }

根据请求类型,分别调用FrameworkServlet的doGet和doPost方法

    protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }
    protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

然后,调用processRequest方法(省略了不重要的代码)

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doService(request, response);
    }

然后到达DispatcherServlet的doService方法

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        doDispatch(request, response);
    }

然后调用doDispatch方法,这个方法才是我们要看的重点

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;

        try {
            ModelAndView mv = null;

            // 获取请求对应的处理器(对应的Controller)
            mappedHandler = getHandler(processedRequest);

            // 获取对应的处理器适配器
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
       // 调用拦截器
       mappedHandler.applyPreHandle(processedRequest, response)
       // 调用处理器 
       mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
       // 调用拦截器 mappedHandler.applyPostHandle(processedRequest, response, mv); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }
catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } }

首先,获取请求对应的处理器

HandlerExecutionChain mappedHandler = getHandler(processedRequest);

这是一个HandlerExecutionChain对象,里面包含了要调用的Controller和拦截器Interceptor。

然后获取处理器适配器

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

在3.1我们知道,我们始化了3个处理器适配器,这里遍历这3个处理器适配器,分别调用supports方法,判断是否支持这个handler。

其中SimpleControllerHandlerAdapter的supports方法如下:

    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }

我们的/hello对应的MyController满足这个条件,因此,返回的处理器适配器就是SimpleControllerHandlerAdapter。

再回到doDispatch方法

我们已经获取到处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 下一步,调用处理器

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

调用处理器MyController,返回一个ModelAndView对象

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //ModelAndView 模型和视图
        ModelAndView mv=new ModelAndView();
        //封装对象,放在ModelAndView中
        mv.addObject("msg", "Hello!SpringMVC!~~");
        //封装要跳转的视图,放在ModelAndView中。
        mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp
        return mv;
    }
}

再回到doDispatch方法,调用processDispatchResult方法

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {
    if (mv != null) {
        render(mv, request, response);
    }
}

如果mv非空,则调用render方法

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String viewName = mv.getViewName();
        View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

        view.render(mv.getModelInternal(), request, response);
    }

该方法把Controller获取到的ModelAndView解析到Model(也就是要显示的数据),并且把Model塞入View中(jsp中),最终封装在response中返回。

此处有三个点要注意

1 ModelAndView,包含了view名称,model数据

2 将model取出来(要显示的数据),填充view(这里是jsp)

3 对于现在前后端分离的应用,往往mv=null,不存在render这一步。

4 如果model或者view没有返回,可能此时这一个请求处理已经完成(比如带有@ResponseBody注解的方法,会在Controller中就直接响应response,而不会返回ModelAndView)

 

最终,返回HttpServlet的service方法

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;

        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }

执行完该方法,res持有了我们要交给Tomcat容器的响应。把响应交给Tomcat容器,Tomcat容器再交给web服务器,web服务器最后交给网卡,网卡将响应封装成报文发送给客户端网卡,客户端网卡接收数据链路发来的物理帧,经过几层协议的转换,最终得到http报文。我们发送请求时一般使用的是浏览器,这里响应肯定也回到浏览器,浏览器解析http报文渲染页面。(当然如果不是浏览器发起的请求,比如curl发起,获取到http报文后没有渲染页面这一步了,直接展示http报文原文),tcp/ip报文传输过程可以参考https://www.cnblogs.com/zhenjingcool/p/15776825.html

 

 

4 示例项目

4.1 项目结构

 

 

 4.2 web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!--配置dispatcher.xml作为mvc的配置文件-->
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

4.3 dispatcher-servlet.xml

XXX-servlet.xml是对XXX这个servlet的配置文件。这里是对web.xml中配置的dispatcher的配置文件。

这里配置了两个bean,分别是视图解析器和我们自定义的MyController

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--添加处理器映射-->
<!--    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>-->
    <!--添加处理器适配-->
<!--    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>-->

    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/><!--设置JSP文件的目录位置-->
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean id="/hello" class="com.szj.controller.MyController" />
</beans>

 

4.4 applicationContext.xml

这个文件默认是空的,对于容器的一些配置可以在这里配置。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

4.5 index.jsp

默认首页

<html>
<body>
<h2>Tomcat Server!</h2>
</body>
</html>

4.6 hello.jsp

对应MyController的视图文件。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${msg}
</body>
</html>

4.7 MyController

自定义处理器,实现了Controller接口,该接口有一个handlerRequest方法。

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //ModelAndView 模型和视图
        ModelAndView mv=new ModelAndView();
        //封装对象,放在ModelAndView中
        mv.addObject("msg", "Hello!SpringMVC!~~");
        //封装要跳转的视图,放在ModelAndView中。
        mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp
        return mv;
    }
}

 

posted @ 2022-02-12 14:03  zhenjingcool  阅读(279)  评论(0编辑  收藏  举报