Spring Web MVC 深入研究

Spring Web MVC的特性

Spring Web MVC如何真正起作用

 

介绍

这是对Spring Web MVC的强大功能和内部工作的深入研究,它是Spring Framework的一部分。

GitHub上提供本文的源代码。

项目设置

在本文中,我们将使用最新和最好的Spring Framework 5.我们将重点放在Spring的经典Web堆栈上,该堆栈可以从框架的最初版本获得,并且仍然是构建Web应用程序的主要方式。与春天。 

对于初学者来说,要设置测试项目,您将使用Spring Boot及其一些初学者依赖项; 你还需要定义父:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.M5</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>

注意,为了使用Spring 5,您还需要使用Spring Boot 2.x. 在撰写本文时,这是一个里程碑版本,可在  Spring Milestone Repository中找到。让我们将此存储库添加到您的Maven项目中:

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

您可以在Maven Central上查看当前版本的Spring Boot 。

示例项目

要了解Spring Web MVC的工作原理,您将使用登录页面实现一个简单的应用程序。要显示登录页面,请创建一个@Controller -annotated类InternalController,其中包含上下文根的GET映射。

问候()方法是无参数。它返回一个String,由Spring MVC解释为视图名称(在我们的例子中是login.html模板):

import org.springframework.web.bind.annotation.GetMapping;

@GetMapping("/")
public String hello() {
    return "login";
}

要处理用户登录,请创建另一个使用登录数据处理POST请求的方法。然后,它会将用户重定向到成功或失败页面,具体取决于结果。

请注意,login()方法接收域对象作为参数并返回ModelAndView对象:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;

@PostMapping("/login")
public ModelAndView login(LoginData loginData) {
    if (LOGIN.equals(loginData.getLogin()) 
      && PASSWORD.equals(loginData.getPassword())) {
        return new ModelAndView("success", 
          Collections.singletonMap("login", loginData.getLogin()));
    } else {
        return new ModelAndView("failure", 
          Collections.singletonMap("login", loginData.getLogin()));
    }
}

ModelAndView是两个不同对象的持有者:

  • 模型 - 用于呈现页面的数据的键值映射
  • 视图 - 填充了模型数据的页面模板

这些是为了方便而连接的,因此控制器方法可以同时返回它们。

要呈现HTML页面,请使用Thymeleaf作为视图模板引擎,该引擎与Spring具有可靠,开箱即用的集成。

Servlet作为Java Web应用程序的基础

那么,当您在浏览器中键入http:// localhost:8080 /时,实际会发生什么,按Enter键,请求会到达Web服务器?您如何从此请求中获取在浏览器中查看Web表单?

鉴于该项目是一个简单的Spring Boot应用程序,您将能够通过Spring5Application运行它。

Spring Boot 默认使用Apache Tomcat。因此,运行应用程序时,您可能会在日志中看到以下信息:

2017-10-16 20:36:11.626  INFO 57414 --- [main] 
  o.s.b.w.embedded.tomcat.TomcatWebServer  : 
  Tomcat initialized with port(s): 8080 (http)

2017-10-16 20:36:11.634  INFO 57414 --- [main] 
  o.apache.catalina.core.StandardService   : 
  Starting service [Tomcat]

2017-10-16 20:36:11.635  INFO 57414 --- [main] 
  org.apache.catalina.core.StandardEngine  : 
  Starting Servlet Engine: Apache Tomcat/8.5.23

由于Tomcat是一个Servlet容器,因此发送到Tomcat Web服务器的每个HTTP请求自然都会由Java servlet处理。因此,Spring Web应用程序入口点毫不奇怪是一个servlet。

简而言之,servlet是任何Java Web应用程序的核心组件; 它是低级别的,并没有对特定编程模式(如MVC)施加太多限制。

HTTP servlet只能接收HTTP请求,以某种方式处理它,然后发回响应。

而且,从Servlet 3.0 API开始,您现在可以超越XML配置并开始利用Java配置(具有较小的限制)。

DispatcherServlet作为Spring MVC的核心

作为Web应用程序的开发人员,我们真正想要做的是抽象出以下乏味和样板任务,并专注于有用的业务逻辑:

  • 将HTTP请求映射到某种处理方法
  • 将HTTP请求数据和标头解析为数据传输对象(DTO)或域对象
  • 模型 - 视图 - 控制器交互
  • 生成DTO,域对象等的响应

Spring DispatcherServlet就是这样提供的。它是Spring Web MVC框架的核心; 此核心组件接收对您的应用程序的所有请求。

正如您将看到的,DispatcherServlet是非常可扩展的。例如,它允许您为许多任务插入不同的现有或新适配器:

  • 将请求映射到应该处理它的类或方法(HandlerMapping接口的实现)
  • 使用特定模式处理请求,如常规servlet,更复杂的MVC工作流,或只是POJO bean中的方法(HandlerAdapter接口的实现
  • 按名称解析视图,允许您使用不同的模板引擎,XML,XSLT或任何其他视图技术(ViewResolver接口的实现)
  • 通过使用默认的Apache Commons文件上传实现或编写自己的MultipartResolver来解析多部分请求
  • 使用任何LocaleResolver实现解析语言环境,包括cookie,会话,接受 HTTP标头或确定用户期望的语言环境的任何其他方式

 


处理HTTP请求

首先,让我们将简单HTTP请求的处理跟踪到控制器层中的方法并返回到浏览器/客户端。

的DispatcherServlet有很长的继承层次; 从上到下逐一理解这些个别方面是值得的。请求处理方法最让我们感兴趣。

https://lh3.googleusercontent.com/4ahYReme6gkGU8NIU--JzvxgCckXNYzCBa_Fi7Xk6DwqNctyNj0pOB_UPU4Euboy66vfURfovJK8VUgJMTz0ms2yQtFnqEhw9iJnWd_pCCpWN5tNXIYhUFtkCxakO-GTyuKlHoqs

在标准开发期间以及远程理解HTTP请求是理解MVC架构的关键部分。

GenericServlet类

GenericServlet是Servlet规范的一部分,不直接关注HTTP。它定义了接收传入请求并生成响应的service()方法。

请注意ServletRequestServletResponse方法参数如何与HTTP协议无关:

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

这是最终在对服务器的任何请求上调用的方法,包括简单的GET请求。

HttpServlet的

顾名思义,HttpServlet类是以HTTP为中心的Servlet实现,也是由规范定义的。

在更实际的术语中,HttpServlet是一个带有service()方法实现的抽象类,它通过HTTP方法类型拆分请求,看起来大致如下:

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_HEAD)) {
        // ...
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        // ...
    }

HttpServletBean

接下来,HttpServletBean是层次结构中第一个支持Spring的类。它使用从web.xmlWebApplicationInitializer接收的servlet init-param值注入bean的属性。

如果对您的应用程序发出请求,则会针对这些特定的HTTP请求调用doGet()doPost()等方法。

FrameworkServlet的

FrameworkServlet将Servlet功能与Web应用程序上下文集成,实现ApplicationContextAware接口。但它也能够自己创建Web应用程序上下文。

正如您已经看到的,HttpServletBean超类将init-params注入为bean属性。因此,如果在servlet 的contextClass init-param中提供了上下文类名,那么将创建此类的实例作为应用程序上下文。否则,将使用默认的XmlWebApplicationContext类。

由于XML配置现在不合时宜,Spring Boot 默认使用AnnotationConfigWebApplicationContext配置DispatcherServlet。但你可以很容易地改变它。

例如,如果需要使用基于Groovy的应用程序上下文配置Spring Web MVC应用程序,则可以在web.xml文件中使用以下DispatcherServlet配置:

 dispatcherServlet
    
        org.springframework.web.servlet.DispatcherServlet
    
    
        contextClass
        
        org.springframework.web.context.support.GroovyWebApplicationContext
        

可以使用WebApplicationInitializer类以更现代的基于Java的方式完成相同的配置。

DispatcherServlet:统一请求处理

HttpServlet.service()的实现,通过HTTP动词类型的请求路由,使得在低级别的servlet上下文非常有意义。但是,在Spring MVC抽象级别,方法类型只是可用于将请求映射到其处理程序的参数之一。

因此,FrameworkServlet类的另一个主要功能是将处理逻辑连接回单个processRequest()方法,该方法又调用doService()方法:

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

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

// …

DispatcherServlet:丰富请求

最后,DispatcherServlet实现了doService()方法。在这里,它向请求添加了一些有用的对象,它们可以在处理管道中派上用场:Web应用程序上下文,区域设置解析器,主题解析器,主题源等:

request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, 
  getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

此外,doService()方法准备输入和输出flash映射。Flash映射基本上是一种模式,用于将参数从一个请求传递到紧随其后的另一个请求。这在重定向期间可能非常有用(例如在重定向后向用户显示一次性信息消息):

FlashMap inputFlashMap = this.flashMapManager
  .retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, 
      Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());

然后,doService()方法调用负责请求分派的doDispatch()方法。

DispatcherServlet:调度请求

dispatch()方法的主要目的是为请求找到合适的处理程序并为其提供请求/响应参数。处理程序基本上是任何类型的Object,并不限于特定的接口。这也意味着Spring需要为这个知道如何与处理程序“交谈”的处理程序找到一个适配器。

为了找到与请求匹配的处理程序,Spring浏览了HandlerMapping接口的已注册实现。有许多不同的实现可以满足您的需求。

SimpleUrlHandlerMapping允许通过其URL将请求映射到某个处理bean。例如,可以通过使用类似于此的java.util.Properties实例注入其mappings属性来配置它:

/welcome.html=ticketController
/show.html=ticketController

可能最广泛使用的处理程序映射类是RequestMappingHandlerMapping,它将请求映射到@Controller类的@RequestMapping -annotated方法。这正是将调度程序与控制器的hello()login()方法连接起来的映射。

请注意,您的Spring感知方法相应地使用@GetMapping@PostMapping进行注释。反过来,这些注释用@RequestMapping元注释标记。

调度()方法还需要照顾其他一些特定的HTTP任务:

  • 在未修改资源的情况下,对GET请求进行短路处理
  • 将多部分解析器应用于相应的请求
  • 如果处理程序选择异步处理请求,则对请求进行短路处理

处理请求

现在,Spring确定了请求的处理程序和处理程序的适配器,现在是时候最终处理请求了。这是HandlerAdapter.handle()方法的签名。重要的是要注意处理程序可以选择如何处理请求:

  • 将数据自己写入响应对象并返回null

返回由DispatcherServlet呈现的ModelAndView对象

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

提供了几种类型的处理程序。以下是SimpleControllerHandlerAdapter处理Spring MVC控制器实例的方法(不要将它与@Controller注释的POJO 混淆)。

请注意控制器处理程序如何返回ModelAndView对象,并且不会自动呈现视图:

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}

第二个是SimpleServletHandlerAdapter,它将常规Servlet作为请求处理程序进行调整。

一个Servlet的不知道任何有关的ModelAndView,只是自己处理请求,渲染结果到响应对象。所以这个适配器只返回null而不是ModelAndView

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    ((Servlet) handler).service(request, response);
    return null;
}

在您的情况下,控制器是带有多个@RequestMapping注释的POJO ,因此任何处理程序基本上都是包含在HandlerMethod实例中的此类的方法。为了适应这种处理程序类型,Spring使用RequestMappingHandlerAdapter类。


 


处理Handler方法的参数和返回值

请注意,控制器方法通常不接受HttpServletRequestHttpServletResponse参数,而是接收和返回许多不同类型的数据,例如域对象,路径参数等。

另请注意,您不需要从控制器方法返回ModelAndView实例。您可以返回视图名称,或者将转换为JSON响应的ResponseEntity或POJO等。

RequestMappingHandlerAdapter确保该方法的参数从解决HttpServletRequest的。此外,它从方法的返回值创建ModelAndView对象。

RequestMappingHandlerAdapter中有一段重要的代码可确保所有这些转换魔法发生:

ServletInvocableHandlerMethod invocableMethod 
  = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(
      this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(
      this.returnValueHandlers);
}

所述argumentResolvers对象是不同的复合HandlerMethodArgumentResolver实例。

有超过30种不同的参数解析器实现。它们允许从请求中提取任何类型的信息并将其作为方法参数提供。这包括URL路径变量,请求正文参数,请求标头,cookie,会话数据等。

所述returnValueHandlers对象是一个复合HandlerMethodReturnValueHandler对象。还有许多不同的值处理程序可以处理方法的结果,以创建适配器所期望的ModelAndView对象。

例如,当您从hello()方法返回一个字符串时,ViewNameMethodReturnValueHandler将处理该值。但是当你从login()方法返回一个准备好的ModelAndView时,Spring使用了ModelAndViewMethodReturnValueHandler

渲染视图

到目前为止,Spring已经处理了HTTP请求并收到了一个ModelAndView对象,因此它必须呈现用户将在浏览器中看到的HTML页面。它基于模型和封装在ModelAndView对象中的选定视图来实现。

另请注意,您可以呈现JSON对象,XML或可通过HTTP协议传输的任何其他数据格式。我们将在接下来的REST重点部分中详细介绍。

让我们回到DispatcherServlet。的渲染()方法首先将使用所提供的响应区域的LocaleResolver实例。假设您的现代浏览器正确设置了Accept标头,并且默认情况下使用AcceptHeaderLocaleResolver

在渲染过程中,如果控制器依赖于默认视图,则ModelAndView对象可能已包含对所选视图的引用,或仅包含视图名称,或者根本不包含任何内容。

由于hello()login()方法都将所需视图指定为String名称,因此必须使用此名称进行查找。所以,这是viewResolvers列表发挥作用的地方:

for (ViewResolver viewResolver : this.viewResolvers) {
    View view = viewResolver.resolveViewName(viewName, locale);
    if (view != null) {
        return view;
    }
}

这是清单ViewResolver的情况下,包括我们ThymeleafViewResolver由提供thymeleaf-spring5集成库。此解析器知道在何处搜索视图,并提供相应的视图实例。

在调用视图的render()方法之后,Spring最终通过将HTML页面发送到用户的浏览器来完成请求处理:

REST支持

除了典型的MVC场景之外,我们还可以使用该框架来创建REST Web服务。

简单地说,您可以接受资源作为输入,将POJO指定为方法参数,并使用@RequestBody对其进行注释。您还可以使用@ResponseBody对方法本身进行批注,以指定其结果必须直接转换为HTTP响应:

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

@ResponseBody
@PostMapping("/message")
public MyOutputResource sendMessage(
  @RequestBody MyInputResource inputResource) {
    
    return new MyOutputResource("Received: "
      + inputResource.getRequestMessage());
}

由于Spring MVC的可扩展性,这也是可能的。

为了将内部DTO编组为REST表示,该框架使用了HttpMessageConverter基础结构。例如,其中一个实现是MappingJackson2HttpMessageConverter,它能够使用Jackson库将模型对象转换为JSON和从JSON转换模型对象。

为了进一步简化REST API的创建,Spring引入了   @RestController注释。默认情况下,假设@ResponseBody语义很方便,并避免在每个REST控制器上显式设置:

import org.springframework.web.bind.annotation.RestController;

@RestController
public class RestfulWebServiceController {

    @GetMapping("/message")
    public MyOutputResource getMessage() {
        return new MyOutputResource("Hello!");
    }
}

结论

在本文中,您已经详细了解了Spring MVC框架中的请求处理。您已经看到框架的不同扩展如何协同工作以提供所有的魔力,并使您无需处理HTTP协议的棘手部分。

 本文翻译自:https://stackify.com/spring-mvc/ 

转载请备注译者 开发猫

出处https://blog.csdn.net/qq_37939251/article/details/83446350

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值