zoukankan      html  css  js  c++  java
  • 《spring实战》学习笔记-第五章:构建spring web应用程序

    5.1 Spring MVC起步

    5.1.1 跟踪Spring MVC的请求
      每当用户在Web浏览器中点击链接或提交表单的时候,请求就开始工作了。对请求的工作描述就像是快递投送员。与邮局投递员或FedEx投送员一样,请求会将信息从一个地方带到另 一个地方。

      在请求离开浏览器时 ①,会带有用户所请求内容的信息,例如用户提交的表单信息。
      请求旅程的第一站是Spring的前端控制器DispatcherServlet,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。
    DispatcherServlet的任务是将请求发送给Spring MVC控制器(controller)。DispatcherServlet会查询一个或多个处理器映射(handler mapping)② 来确定请求应该将请求发送给哪个控制器。处理器映射会根据请求所携带的URL信息来进行决策。
      一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器 。到了控制器③,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。(实际上, 设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个服务 对象进行处理。)
      控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显 示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信 息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送给一个视图 (view),通常会是JSP。
      控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet④ 。
      这样,控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个 逻辑名称,这个名字将会用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器(view resolver) ⑤来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能 不是JSP。 既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成 了。它的最后一站是视图的实现(可能是JSP)⑥ ,在这里它交付模型数据。请求的任务就完 成了。视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端(不会像听上 去那样硬编码)⑦ 。
      可以看到,请求要经过很多的步骤,最终才能形成返回给客户端的响应。大多数的步骤都是 在Spring框架内部完成的,也就是图5.1所示的组件中。尽管本章的主要内容都关注于如何编 写控制器,但在此之前我们首先看一下如何搭建Spring MVC的基础组件。

    5.1.2 搭建Spring MVC
      基于图5.1,看上去我们需要配置很多的组成部分。幸好,借助于最近几个Spring新版本的功能增强,开始使用Spring MVC变得非常简单了。现在,我们要使用最简单的方式来配置 Spring MVC:所要实现的功能仅限于运行我们所创建的控制器。在第7章中,我们会看一些其他的配置选项。
    配置DispatcherServlet
      DispatcherServlet是Spring MVC的核心。在这里请求会第一次接触到框架,它要负责将请求路由到其他的组件之中。 按照传统的方式,像DispatcherServlet这样的Servlet会配置在web.xml文件中,这个文件会放到应用的WAR包里面。当然,这是配置DispatcherServlet的方法之一。

      但是, 借助于Servlet 3规范和Spring 3.1的功能增强,这种方式已经不是唯一的方案了,这也不是我们本章所使用的配置方法。 我们会使用Java将DispatcherServlet配置在Servlet容器中,而不会再使用web.xml文件。如下的程序清单展示了所需的Java类。
    程序清单5.1 配置DispatcherServlet

    package spittr.config;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected String[] getServletMappings() {
            return new String[] { "/" };//将DispatcherServlet映射到"/"
        }
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class<?>[] { RootConfig.class };
        }
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class<?>[] { WebConfig.class };//指定配置类
        }
    }

      在程序清单5.1中,SpittrWebAppInitializer重写了三个方法。
      第一个方法是getServletMappings(),它会将一个或多个路径映射到DispatcherServlet上。在本例中,它映射的是“/”,这表示它会是应用的默认Servlet。它会处理进入应用的所有请求。
      为了理解其他的两个方法,我们首先要理解DispatcherServlet和一个Servlet监听器(也就是ContextLoaderListener)的关系。
      当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。在程序清单5.1的getServletConfigClasses()方法中,我们要求DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类(使用Java配置)中的bean。
      但是在Spring Web应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由ContextLoaderListener创建的。
      我们希望DispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射,而ContextLoaderListener要加载应用中的其他bean。这些bean通常是驱动应后端的中间层和数据层组件。
      实际上,AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServlet和ContextLoaderListener。GetServletConfigClasses()方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的bean。getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean。
    启用Spring MVC
      正如有多种方式可以配置DispatcherServlet,激活Spring MVC组件也有不止一种方法。一般的,都会通过XML配置文件的方式来配置Spring,例如可以通过<mvc:annotation-driven>来激活基于注解的Spring MVC。
      我们所能创建的最简单的Spring MVC配置就是一个带有@EnableWebMvc注解的类:

    package spittr.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
     
    @Configuration
    @EnableWebMvc
    public class WebConfig {
    }

      这可以运行起来,它的确能够启用Spring MVC,但还有不少问题要解决:
      ● 没有配置视图解析器。如果这样的话,Spring默认会使用BeanNameView-Resolver,这个视图解析器会查找ID与视图名称匹配的bean,并且查找的bean要实现View接口,它以这样的方式来解析视图。
      ● 没有启用组件扫描。这样的结果就是,Spring只能找到显式声明在配置类中的控制器。
      ● 这样配置的话,DispatcherServlet会映射为应用的默认Servlet,所以它会处理所有的请求,包括对静态资源的请求,如图片和样式表(在大多数情况下,这可能并不是你想要的效果)。
      因此,我们需要在WebConfig这个最小的Spring MVC配置上再加一些内容,从而让它变得真正有用。如下程序清单中的WebConfig解决了上面所述的问题。
    程序清单5.2 最小但可用的Spring MVC配置

    package spittr.config;
     
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.view.InternalResourceViewResolver;
     
    @Configuration
    @EnableWebMvc   //启动spring mvc
    @ComponentScan("spitter.web") // 启动组件扫描
    public class WebConfig extends WebMvcConfigurerAdapter {
     
        // 配置JSP视图解析器
        @Bean
        public ViewResolver viewResolver() {
            InternalResourceViewResolver resolver = new InternalResourceViewResolver();
            resolver.setPrefix("/WEB_INF/views/");
            resolver.setSuffix(".jsp");
            resolver.setExposeContextBeansAsAttributes(true);
            return resolver;
        }
     
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();//配置静态资源的处理
        }
    }

      在程序清单5.2中第一件需要注意的事情是WebConfig现在添加了@ComponentScan注解,因此将会扫描spitter.web包来查找组件。稍后你就会看到,我们所编写的控制器将会带有@Controller注解,这会使其成为组件扫描时的候选bean。因此,我们不需要在配置类中显式声明任何的控制器。
      接下来,我们添加了一个ViewResolver bean。更具体来讲,是InternalResourceViewResolver。我们将会在第6章更为详细地讨论视图解析器。我们只需要知道它会查找JSP文件,在查找的时候,它会在视图名称上加一个特定的前缀和后缀(例如,名为home的视图将会解析为WEB-INFviews/home.jsp)。
      最后,新的WebConfig类还扩展了WebMvcConfigurerAdapter并重写了其configureDefaultServletHandling()方法。通过调用DefaultServletHandlerConfigurer的enable()方法,我们要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此类请求
      WebConfig已经就绪,那RootConfig呢?因为本章聚焦于Web开发,而Web相关的配置通过DispatcherServlet创建的应用上下文都已经配置好了,因此现在的RootConfig相对很简单:

    package spittr.config;
     
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.ComponentScan.Filter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
     
    @Configuration
    @ComponentScan(basePackages = { "spitter" }, excludeFilters = {
            @Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) })
    public class RootConfig {
    }

      唯一需要注意的是RootConfig使用了@ComponentScan注解。这样的话,在本书中,我们就有很多机会用非Web的组件来充实完善RootConfig。
    5.1.3 Spittr应用介绍
      这一章要用的例子应用,从Twitter获取了一些灵感,因此最开始叫Spitter;然后又借鉴了最近比较流行的网站Flickr,因此我们也把e去掉,最终形成Spittr这个名字。这也有利于区分领域名称(类似于twitter,这里用spring实现,因此叫spitter)和应用名称。
      Spittr类似于Twitter,用户可以通过它添加一些推文。Spittr有两个重要的概念:spitter(应用的用户)和spittle(用户发布简单状态)。本章将会构建该应用的web层、创建用于展示spittle的控制器以及用户注册流程。

    5.2 编写基本的控制器

      在Spring MVC中,控制器只是方法上添加了@RequestMapping注解的类,这个注解声明了它们所要处理的请求。
      开始的时候,我们尽可能简单,假设控制器类要处理对“/”的请求,并渲染应用的首页。程序清单5.3所示的HomeController可能是最简单的Spring MVC控制器类了。

    package spittr.web;
     
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
     
    @Controller // 声明一个控制器
    public class HomeController {
     
        @RequestMapping(value = "/", method = RequestMethod.GET) // 处理GET请求
        public String home() {
            return "home";
        }
     
    }

      你可能注意到的第一件事情就是HomeController带有@Controller注解。很显然这个注解是用来声明控制器的,但实际上这个注解对Spring MVC本身的影响并不大。            HomeController是一个构造型(stereotype)的注解,它基于@Component注解。在这里,它的目的就是辅助实现组件扫描。因为HomeController带有@Controller注解,因此组件扫描器会自动找到HomeController,并将其声明为Spring应用上下文中的一个bean。
      其实,你也可以让HomeController带有@Component注解,它所实现的效果是一样的,但是在表意性上可能会差一些,无法确定HomeController是什么组件类型。
      HomeController唯一的一个方法,也就是home()方法,带有@RequestMapping注解。它的value属性指定了这个方法所要处理的请求路径,method属性细化了它所处理的HTTP方法。在本例中,当收到对“/”的HTTP GET请求时,就会调用home()方法。
      你可以看到,home()方法其实并没有做太多的事情:它返回了一个String类型的“home”。这个String将会被Spring MVC解读为要渲染的视图名称。DispatcherServlet会要求视图解析器将这个逻辑名称解析为实际的视图。
      鉴于我们配置InternalResourceViewResolver的方式,视图名“home”将会解析为“WEB-INFviews/home.jsp”路径的JSP。现在,我们会让Spittr应用的首页相当简单,如下所示。
    程序清单5.4 Spittr应用的首页,定义为一个简单的JSP

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
     
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
     
    <html>
    <head>
    <meta charset="utf-8">
    <title>Spittr</title>
    <link rel="stylesheet" type="text/css"
        href="<c:url value="/resources/style.css" />">
    </head>
    <body>
        <h1>Welcome to Spittr</h1>
        <a href="<c:url value="/spittles" />">Spittles</a> |
        <a href="<c:url value="/spitter/register" />">Register</a>
    </body>
    </html>

      这个JSP并没有太多需要注意的地方。它只是欢迎应用的用户,并提供了两个链接:一个是查看Spittle列表,另一个是在应用中进行注册。下图展现了此时的首页是什么样子的。

    5.2.1 测试控制器
      下面程序阐述了如何测试Spring MVC的控制器。
    程序清单5.5 改进HomeControllerTest

    package spittr.web;
     
    import org.junit.Test;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
     
    public class HomeControllerTest {
     
        @Test
        public void testHomePage() throws Exception {
            HomeController controller = new HomeController();
            // 设置MockMvc
            MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
            mockMvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.view().name("home"));
        }
     
    }

      它首先传递一个HomeController实例到MockMvcBuilders.standaloneSetup()并调用build()来构建MockMvc实例。然后它使用MockMvc实例来执行针对“/”的GET请求并设置期望得到的视图名称。
    5.2.2 定义类级别的请求处理
      我们可以做的一件事就是拆分@RequestMapping,并将其路径映射部分放到类级别上。程序清单5.7展示了这个过程。
    程序清单5.7 拆分HomeController中的@RequestMapping

    package spittr.web;
     
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
     
    @Controller // 声明一个控制器
    @RequestMapping("/") // 控制器匹配路径
    public class HomeController {
     
        @RequestMapping(method = RequestMethod.GET) // 处理GET请求
        public String home() {
            return "home";// 视图名称
        }
     
    }

      在这个新版本的HomeController中,路径现在被转移到类级别的@RequestMapping上,而HTTP方法依然映射在方法级别上。当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理器方法上。处理器方法上的@RequestMapping注解会对类级别上的@RequestMapping的声明进行补充。
      就HomeController而言,这里只有一个控制器方法。与类级别的@RequestMapping合并之后,这个方法的@RequestMapping表明home()将会处理对“/”路径的GET请求。
    换言之,我们其实没有改变任何功能,只是将一些代码换了个地方,但是HomeController所做的事情和以前是一样的。因为我们现在有了测试,所以可以确保在这个过程中,没有对原有的功能造成破坏。
      当我们在修改@RequestMapping时,还可以对HomeController做另外一个变更。@RequestMapping的value属性能够接受一个String类型的数组。到目前为止,我们给它设置的都是一个String类型的“/”。但是,我们还可以将它映射到对“/homepage”的请求,只需将类级别的@RequestMapping改为如下所示:

    @RequestMapping("/","homepage") // 控制器匹配路径
    public class HomeController {
    //......
    }

      现在,HomeController的home()方法能够映射到对“”和“homepage”的GET请求。

      本文代码链接:https://github.com/Gugibv/spring/tree/master/spittr

    -
  • 相关阅读:
    二进制位运算
    Leetcode 373. Find K Pairs with Smallest Sums
    priority_queue的用法
    Leetcode 110. Balanced Binary Tree
    Leetcode 104. Maximum Depth of Binary Tree
    Leetcode 111. Minimum Depth of Binary Tree
    Leetcode 64. Minimum Path Sum
    Leetcode 63. Unique Paths II
    经典的递归练习
    案例:java中的基本排序
  • 原文地址:https://www.cnblogs.com/gugibv/p/7856830.html
Copyright © 2011-2022 走看看