栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringMVC-原理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringMVC-原理

1. MVC 1.1 什么是MVC

MVC分别是:模型Model(javabean)、视图View(jsp)、控制器Controller(servlet)。

模型(Model):负责数据逻辑的处理和实现数据操作。

视图(View):负责格式化数据并把它们呈现给用户,包括数据展示、用户交互、数据验证、页面设计等。

控制器(Controller):负责接收转发请求,对请求进行处理后,指定试图并将响应结果发送给客户端。

MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。

1.2 JSP+JavaBean

在以往,我们用到的是JSP+JavaBean设计模式,适合一些业务流程比较简单的Web程序。

  • JSP(视图):用于处理用户请求
  • JavaBean(模型):用于封装和处理数据

工作流程: 浏览器端发出request请求,JSP从请求中获取需要的数据,交由JavaBean进行业务处理,JavaBean通过与数据库的交互获取相关返回值。JavaBean将结果以response形式返还给JSP,最终解析在浏览器。

JSP+JavaBean弊端:

  1. JSP与JavaBean间严重耦合,维护困难
  2. JSP责任太多,结构较为混乱,无法实现业务流程复杂的应用
1.3 Servlet+JSP+JavaBean

而现在,我们用到的是Servlet+JSP+JavaBean设计模式,也就是MVC模式。

  • Servlet(Controller):处理用户请求
  • JSP(View):数据显示
  • JavaBean(Model):数据封装

该模式将控制层单独划分出来负责业务流程的控制,接收request请求,实例化所需要的JavaBean,并将处理后的数据以response返回给JSP惊醒页面数据展示。

1.4 MVC优缺点

优点:

  1. 多视图可共享同一个模型,提高代码的重用性
  2. 业务划分清晰,MVC模块相互独立,松耦合框架
  3. 易于维护,易于扩展大型复杂的web应用

缺点:

  1. 提高了系统的负责度
  2. 试图对模型数据的低效率访问
2. Spring MVC 2.1 什么是Spring MVC

Spring MVC是Spring framework的一部分,是基于Java实现MVC的轻量级Web框架,本质上是Servlet。

Spring MVC是Servlet+JSP+JavaBean的实现,与Spring无缝对接,是如今主流的Web开发框架。

Spring MVC框架曹勇松耦合可拔插的组件结构,具有高度可配置性,相对其其他的框架更具扩展性和灵活性。

2.2 Spring MVC 特点
  1. 角色划分清晰,Model、View、Controller
  2. 配置灵活,可把类当作Bean通过xml配置
  3. 与Spring无缝对接
2.3 DispatcherServlet

DispatcherServlet(前置控制器):Spring MVC框架是围绕DispatcherServlet设计的,这种Servlet用来处理所有的HTTP请求和响应,支持配置处理器映射、试图渲染、本地化与文件上传等功能。

  • 通过 HandlerMapping(处理器映射器)将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器)。
  • 通过 HandlerAdapter(适配器) 支持多种类型的处理器(HandlerExecutionChain中的处理器)。
  • 通过 ViewResolver(视图解析器) 解析逻辑视图名到具体视图实现。
2.4 核心组件

DispatcherServlet通过与Spring MVC核心组件的相互协作,完成了请求和响应的处理。暂且就叫核心组件吧,但是在官网上定义为 Special Bean Types,因为在程序中我们需要以bean的方式配置它们。

 处理流程相当繁琐,不如先搞懂每一各组件的功能,工作流程放后面再说。

2.4.1 HandlerMapping 2.4.1.1 HandlerMapping功能

 功能:根据请求匹配到对应的 Handler,然后将找到的 Handler 和所有匹配的 HandlerInterceptor(拦截器)绑定到创建的 HandlerExecutionChain 对象上并返回。

 返回值:Handler

2.4.1.2 HandlerMapping接口

该接口内容如下,其中主要包含getHandler抽象方法,该方法最终返回了一个HandlerExecutionChain实例。

2.4.1.3 HandlerMapping实现类

该接口的实现类如下(选中的是下文中我们要用到的):

可以看到,大致上分为两大类 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping,且都继承自 AbstractHandlerMapping 抽象类。

⚠️ 注意:不同的映射处理器(HandlerMapping) 映射出来的 handler 对象是不一样的,AbstractUrlHandlerMapping 映射器映射出来的是 handler 是 Controller 对象,AbstractHandlerMethodMapping 映射器映射出来的 handler 是 HandlerMethod 对象。

2.4.2 HandlerExecutionChain 2.4.2.1 HandlerExecutionChain功能

HandlerExecutionChain与HanderMapping关系非常紧密,HandlerExecutionChain只能通过HanderMapping接口中的唯一方法来获得。

 功能:处理程序执行链,由处理程序对象和任何处理程序拦截器组成。根据url查找控制器,下文中url被查找控制器为:hello

组成:实例封装了一个handler处理对象和一些interceptors

当用户请求到到DispaterServlet中后,配置的HandlerMapping会根据用户请求(也就是handler)会将它与所有的interceptors封装为HandlerExecutionChain对象。

2.4.2.2 HandlerExecutionChain类

在该类中是这样定义HandlerExecutionChain的:

Handler execution chain, consisting of handler object and any handler interceptors. Returned by HandlerMapping’s HandlerMapping.getHandler method.

该类的内容如下:

2.4.2.3 HandlerExecutionChain实例

HandlerExecutionChain实例的获取写在AbstractHandlerMapping类中,由下图的getHandlerExecutionChain方法获取该实例。

getHandlerExecutionChain方法如下:

2.4.3 HandlerAdapter

在上文中,提到HandlerMapping不同实现类映射的handler不同,各种处理器中的处理方法各不相同,Spring为了解决适应多种处理器,定义了处理器适配器的概念,也就是我们所说的HandlerAdapter。

在Spring MVC中可以支持多种处理器(处理器也就是处理用户请求的程序)。

Spring实现的处理器类型有Servlet、Controller、HttpRequestHandler以及注解类型的处理器。

2.4.3.1 HandlerAdapter功能

 功能:帮助DispatcherServlet调用映射到请求的处理程序,而不管处理程序实际是如何调用的(规则要求)。 通过HandlerAdapter对处理器进行执行。

 返回值:返回一个ModelAndView对象(包含模型数据、逻辑视图名);

2.4.3.2 HandlerAdapter接口

HandlerAdapter接口的内容如下:

public interface HandlerAdapter {

    boolean supports(Object handler);

    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    @Deprecated
    long getLastModified(HttpServletRequest request, Object handler);

}

 handle方法:使用给定的处理程序来处理此请求,返回ModelAndView。

2.4.3.3 HandlerAdapter实现类

HandlerAdapter接口的实现类如下:

  • SimpleServletHandlerAdapter 适配Servlet处理器
  • HttpRerquestHandlerAdapter 适配HttpRequestHandler处理器
  • RequestMappingHandlerAdapter 适配注解处理器
  • SimpleControllerHandlerAdapter 适配Controller处理器(非默认使用)

在下文中,我们用到的是第四种,来具体看下SimpleControllerHandlerAdapter 实现类里面的内容:

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {

    return ((Controller) handler).handleRequest(request, response);
}

handle方法:该实现类实现了接口的方法,同样返回ModelAndView

⚠️ 注意:该方法的返回值实际上是Controller执行的handler方法!!

2.4.4 Controller

Controller接口被显式设计为操作HttpServletRequest和HttpServletResponse对象。

 就像HttpServlet一样。

2.4.4.1 Controller功能

 功能:具体实现handler方法,

 返回值:返回一个ModelAndView对象(包含模型数据、逻辑视图名);

2.4.4.2 Controller接口

Controller接口基本没有上面内容(也没找到实现类),只有一个handleRequest方法:

public interface Controller {
    @Nullable
    ModelAndView handleRequest(HttpaServletRequest request, HttpServletResponse response) throws Exception;

}

 handleRequest方法:处理请求并返回一个ModelAndView对象,DispatcherServlet将呈现该对象。

⚠️ 注意:返回值为null不是错误,它表明该对象本身完成了请求处理,因此没有ModelAndView来呈现。

2.4.4.3 Controller实现类

Controller接口的实现类如下(最下面选中的是下文中自己写的实现类):

2.4.4.4 ModelAndView对象

ModelAndView对象中有两个主要的方法:

public class ModelAndView {
    
    public ModelAndView addObject(String attributeName, @Nullable Object attributevalue)
    {
        getModelMap().addAttribute(attributeName, attributevalue);
        return this;
    }

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

 addObject可以看作一个session,就像之前定义session.setAttribute那样,name、value就对应session的key-value。

2.4.5 ViewResolver 2.4.5.1 ViewResolver功能

 功能:解析DispatcherServlet传递的逻辑视图名,解析为具体的View,并将解析结果传回给DispatcherServlet。

2.4.5.2 ViewResolver接口

ViewResolver接口中只有一个返回视图名的resolveViewName抽象方法。

public interface ViewResolver {
    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

 resolveViewName方法:按名称解析给定的视图。

  • viewName:要解析的视图的名称
  • locale:用于解析视图的区域设置。
2.4.5.3 ViewResolver实现类

ViewResolver接口的实现类如下(选中的是下文中我们要用到的):

  • ResourceBundleViewResolver:采用bundle根路径所指定的ResourceBundle中的bean定义作为配置。
  • XmlViewResolver:该类接受一个XML格式的配置文件。
  • URLbasedViewResolver:它直接使用URL来解析到逻辑视图名,除此之外不需要其他任何显式的映射声明。
  • FreeMarkerViewResolver :最终会解析逻辑视图配置,返回 freemarker 模板。不需要指定 viewClass 属性。
  • ContentNegotiatingViewResolver:它会根据所请求的文件名或请求的Accept头来解析一个视图。

InternalResourceViewResolver 为“内部资源视图解析器”,是日常开发中最常用的视图解析器类型。它是 URLbasedViewResolver 的子类,拥有 URLbasedViewResolver 的一切特性。

InternalResourceViewResolver 能自动将返回的视图名称解析为 InternalResourceView 类型的对象。InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。也就是说,使用 InternalResourceViewResolver 视图解析时,无需再单独指定 viewClass 属性。

2.4.5.4 InternalResourceViewResolver

InternalResourceViewResolver 是实现类的主要内容如下:

public class InternalResourceViewResolver extends UrlbasedViewResolver {

    //将默认视图类设置为requiredViewClass:默认情况下是InternalResourceView,如果JSTL API存在,则默认是JstlView。
    public InternalResourceViewResolver() {
        Class viewClass = requiredViewClass();
        if (InternalResourceView.class == viewClass && jstlPresent) {
            viewClass = JstlView.class;
        }
        setViewClass(viewClass);
    }

    
    public InternalResourceViewResolver(String prefix, String suffix) {
        this();
        setPrefix(prefix);
        setSuffix(suffix);
    }

    @Override
    protected AbstractUrlbasedView buildView(String viewName) throws Exception {
        InternalResourceView view = (InternalResourceView) super.buildView(viewName);
        if (this.alwaysInclude != null) {
            view.setAlwaysInclude(this.alwaysInclude);
        }
        view.setPreventDispatchLoop(true);
        return view;
    }

}

在其父类URLbasedViewResolver 中,buildView方法如下:

protected AbstractUrlbasedView buildView(String viewName) throws Exception{..}

AbstractUrlbasedView:基于url的视图的抽象基类。以“URL”bean属性的形式提供一致的方式来保存View包装的URL。

2.4.6 View

View是最后一个处理环节了!!

2.4.6.1 View功能

 功能:View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术,实现类支持不同的View类型(jsp、freemarker、pdf…)

2.4.6.1 View接口

view接口的抽象内容如下:

void render(@Nullable Map model, HttpServletRequest request, HttpServletResponse response)
    throws Exception;

可以看到,通过Map数据结构支持给定模型呈现视图。

2.4.6.1 View实现类

该接口的实现类如下:

3. DispatcherServlet

通过对Spring MVC中核心组件的认识,我们应该看到了DispatcherServlet在这之中扮演的角色。

下面根据源码,将DispatcherServlet的交互过程再梳理一遍。

1️⃣ Step1:request -> HandlerMapping.getHandler() -> HandlerExecutionChain对象(Handler)

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

2️⃣ Step2:Handler -> HandlerAdapter -> ModelAndView

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
                                               @Nullable Object handler, Exception ex) throws Exception {

    // Success and error responses may use different content types
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
    if (exMv != null) {
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        // We might still need view name translation for a plain error model...
        if (!exMv.hasView()) {
            String defaultViewName = getDefaultViewName(request);
            if (defaultViewName != null) {
                exMv.setViewName(defaultViewName);
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Using resolved error view: " + exMv, ex);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Using resolved error view: " + exMv);
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }

    throw ex;
}

3️⃣ Step3:ModelAndView -> View -> Response

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale =
        (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // We need to resolve the view name.
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                                       "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                                       "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}
4. Spring MVC 执行原理

通过上文描述,应该对Spring MVC有了简单的认识,下面梳理下整体的工作流程。

1️⃣ Step1:DispatcherServlet接受浏览器请求、拦截请求;

2️⃣ Step2:DispatcherServlet调用HandlerMapping,HandlerMapping根据请求URL查找Handler;

3️⃣ Step3:HandlerExecutionChain处理程序执行链,根据URL查找控制器;

4️⃣ Step4:HandlerExecutionChain将查找到的具体的Handler解析后返回Handler;

5️⃣ Step5:DispatcherServlet调用HandlerAdapter处理器适配器,按照特定的规则去执行Handler。

6️⃣ Step6:HandlerAdapter经过适配调用具体的处理器Controller;

7️⃣ Step7:Handler由Controller执行,执行后的具体信息(ModelAndView)返回给HandlerAdapter;

8️⃣ Step8:HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet;

9️⃣ Step9: DispatcherServlet将ModelAndView传给ViewReslover视图解析器;

 Step10:视图解析器将解析的逻辑视图名(具体的View)返回给DispatcherServlet;

1️⃣1️⃣ Step11:DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图;

1️⃣2️⃣ Step12:将View作为Request返回给历览器。

5. Spring MVC 模型

看完理论部分,不如来实际写一个Model。

官方文档:spring-web

JDK:1.8

Maven:3.8.2

IDEA:2021.2

Tomcat:9.0.46

5.1 前期准备

新建一个Maven项目,删去src目录,再项目下新建一个空的Model。

导入核心依赖:

  • spring-webmvc :org.springframework
  • servlet-api :javax.servlet
  • jsp-api :javax.servlet
  • jstl :javax.servlet
5.2 配置Web/Tomcat

在Model名称右键Add frameworl Support...,勾选Web Application 。

配置Tomcat参考:IDEA配置Tomcat

5.3 配置web.xml 5.3.1 配置Servlet

我们知道DispatcherServlet本质是一个Servlet,所以需要像往常一样在web.xml文件中配置Servlet映射。


    SpringMVC
    org.springframework.web.servlet.DispatcherServlet
    
    
        contextConfigLocation
        classpath:SpringMVC-servlet.xml
    
    
    1


    SpringMVC
    /

在这里面,出现了不认识的内容,下面展开看看。

5.3.2 配置Spring MVC 配置文件

    contextConfigLocation
    classpath:SpringMVC-servlet.xml

这里是配置一个contextConfigLocation,在标签内如果没有提供值,默认会去找WEB-INF/*-servlet.xml。

如果是 标签内的,如果没有提供值,默认会去找/WEB-INF/applicationContext.xml

5.3.3 配置启动级别

元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。

  • 如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
  • 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
1
5.3.4 匹配JSP

/ 匹配所有的请求;(不包括.jsp)

/* 匹配所有的请求;(包括.jsp)

/
5.4 编写Spring MVC 配置文件




5.4.1 处理映射器

5.4.2 处理器适配器

5.4.3 视图解析器
  • prefix:前缀
  • suffix:后缀

    
    

5.5 Controller

前面提到,相当于一个Servlet的组件是Controller。我们的操作业务写在这里面。

public class HelloController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //ModelAndView 实例化模型和视图
        ModelAndView modelAndView = new ModelAndView();
        // 封装对象,放在ModelAndView中
        modelAndView.addObject("username", "welcome");
        // 跳转页面,全路径为:/jsp/test.jsp
        modelAndView.setViewName("test");
        return modelAndView;
    }
}
5.6 注册Controller

有了Control后,我们需要将它交给SpringIOC容器,所以我们需要将其注册为Bean。

在SpringMVC-servlet.xml中注册Bean。


5.7 JSP页面

写要跳转的jsp页面,显示ModelandView存放的数据,以及我们的正常页面。

注意之前定义的前缀,JSP路径为web/jsp/test.jsp

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

 username:这里利用EL表达式取到的username,相当于之前的session.setAttribute

5.8 测试

启动Tomcat,在修改地址栏为http://localhost:8080/test

6. 写在最后

上面Spring MVC Model的代码量其实并不多,但在实际应用中我们也并不会写这么代码,后期通过简单的注解就能实现相同的功能。重要的是理解Spring MVC 的执行过程,这也是面试点。

Spring MVC 中的各个组件容易搞混淆,下面将Model中到的实现类以及父类接口再梳理一遍。

接口实现类说明
NullDispatcherServlet前置控制器
HandlerMappingBeanNameUrlHandlerMapping处理映射器
HandlerAdapterSimpleControllerHandlerAdapter处理适配器
ViewResolverInternalResourceViewResolver视图解析器

DispatcherServlet:org.springframework.web.servlet.DispatcherServlet

BeanNameUrlHandlerMapping:org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping

SimpleControllerHandlerAdapter:org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter

InternalResourceViewResolver:org.springframework.web.servlet.view.InternalResourceViewResolver

 


❤️ END ❤️
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/324778.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号