为了账号安全,请及时绑定邮箱和手机立即绑定

【Spring实战——构建Spring Web应用程序】1.7 编写基本的控制器

  • 在Spring MVC中,控制器只是方法上添加了@RequestMapping注解 的类,这个注解声明了它们所要处理的请求。

  • 假设控制器类要处理对“/”的请求, 并渲染应用的首页。程序清单5.3所示的HomeController可能是最 简单的Spring MVC控制器类了。
    程序清单5.3 HomeController:超级简单的控制器

    * package com.spring.mvc.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import static org.springframework.web.bind.annotation.RequestMethod.GET;
    
    /**
    
     * 声明一个控制器
     * @author huyingqi
       */
       @Controller
       public class HomeController {
       @RequestMapping(value = "/",method = GET )
       public String home(){
           //视图名为home
           return "home";  
       }
    
    }
    
  • 在上述内容中,我们可以注意到HomeController带有@Controller注解。

    • 这个注解用于声明控制器,但它对Spring MVC本身的影响并不大。
  • HomeController是一个构造型注解,基于@Component注解。

    • 它的目的是辅助实现组件扫描。
    • 由于HomeController带有@Controller注解,组件扫描器会自动找到它,并将其声明为Spring应用上下文中的一个bean。
  • 实际上,你也可以让HomeController带有@Component注解,它的效果是一样的。但在表意性上可能会有所差别,无法确定HomeController是什么类型的组件。

  • HomeController中唯一的方法是home()方法,它带有@RequestMapping注解。

    • value属性
      • 指定了该方法要处理的请求路径,
    • method属性
      • 细化了它所处理的HTTP方法。
    • 在这个例子中,当收到对"/"的HTTP GET请求时,将调用home()方法。
  • 在home()方法中,并没有做太多的事情,它只是返回一个String类型的"home"。这个字符串将被Spring MVC解释为要渲染的视图名称。DispatcherServlet会要求视图解析器将这个逻辑名称解析为实际的视图。
    鉴于我们配置InternalResourceViewResolver的方式,视图 名“home”将会解析为“/WEB-INF/views/home.jsp”路径的JSP 。现在, 我们会让Spittr应用的首页相当简单,如下所示。
    程序清单5.4 Spittr应用的首页,定义为一个简单的JSP
    [图片]
    这个JSP并没有太多需要注意的地方。它只是欢迎应用的用户,并提 供了两个链接:一个是查看Spittle列表,另一个是在应用中进行 注册。图5.2展现了此时的首页是什么样子的。
    在本章完成之前,我们将会实现处理这些请求的控制器方法。但现在,让我们对这个控制器发起一些请求,看一下它是否能够正常工作。测试控制器最直接的办法可能就是构建并部署应用,然后通过浏 览器对其进行访问,但是自动化测试可能会给你更快的反馈和更一致 的独立结果。所以,让我们编写一个针对HomeController的测 试。
    [图片]
    图5 .2 当前的Spittr首页
    5.2.1 测试控制器
    让我们再审视一下HomeController。如果你眼神不太好的话,你 甚至可能注意不到这些注解,所看到的仅仅是一个简单的POJO 。我 们都知道测试POJO是很容易的。因此,我们可以编写一个简单的类 来测试HomeController,如下所示:
    程序清单5.5 HomeControllerTest:测试HomeController

    package com.spring.mvc;
    
    import com.spring.mvc.controller.HomeController;
    import org.junit.Assert;
    import org.junit.Test;
    
    public class HomeControllerTest {
        @Test
        public void testHomePage() throws Exception {
            HomeController homeController = new HomeController();
            Assert.assertEquals("home",homeController.home());
        }
    }
    
    - 
    

    在程序清单5.5中的测试非常简单,它只测试了home()方法的行为。测试直接调用home()方法,并断言返回的字符串包含"home"值。这个测试并没有从Spring MVC控制器的角度进行测试,它没有断言当接收到针对"/“的GET请求时会调用home()方法。由于返回的值正好是"home”,所以也没有真正判断"home"是否是视图的名称。

  • 然而,从Spring 3.2开始,我们可以使用控制器的方式来测试Spring MVC中的控制器,而不仅仅作为POJO进行测试。Spring现在提供了一种模拟Spring MVC并执行HTTP请求的机制。这样,在测试控制器时,就不需要启动Web服务器和Web浏览器了。

  • 为了演示如何测试Spring MVC控制器,我们重写了HomeControllerTest,并使用了Spring MVC中的新测试特性。程序清单5.6展示了新的HomeControllerTest。
    程序清单5.6 改进HomeControllerTest
    file

  • 新版本的测试相比之前的版本只多了几行代码,但它更完整地测试了HomeController。这次测试不是直接调用home()方法并测试返回值,而是发起了对"/“的GET请求,并断言结果视图的名称为"home”。首先,它使用HomeControllerMockMvcBuilders.standaloneSetup()创建一个MockMvc实例,并调用build()方法进行构建。然后,使用MockMvc实例执行针对"/"的GET请求,并设置期望得到的视图名称。
    5.2.2 定义类级别的请求处理

  • 现在,已经为HomeController编写了测试,那么我们可以做一些重构,并通过测试来保证不会对功能造成什么破坏。

  • 我们可以做的一件事就是拆分@RequestMapping,并将其路径映射部分放到类级别 上。

  • 程序清单5.7展示了这个过程。
    程序清单5.7 拆分HomeController中的@RequestMapping

    - package com.spring.mvc.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import static org.springframework.web.bind.annotation.RequestMethod.GET;
    
    /**
    
     * 声明一个控制器
     * @author huyingqi
       */
       @Controller.       ——>将控制器映射到“/@RequestMapping("/)
       public class HomeController {
       @RequestMapping(method = GET )   //处理GET请求
       public String home(){
           //视图名为home
           return "home";    ——> 视图名为home
       }
    
    }
    
  • 在这个新版本的HomeController中,路径被转移到类级别的@RequestMapping上,而HTTP方法仍然映射在方法级别上。

    • 当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理器方法上。
    • 处理器方法上的@RequestMapping注解会补充类级别上的@RequestMapping的声明。
  • 对于HomeController来说,只有一个控制器方法。

    • 在与类级别的@RequestMapping合并之后,该方法的@RequestMapping注解明确了它将处理对"/"路径的GET请求。
  • 换句话说,我们实际上没有改变任何功能,只是将一些代码移动了位置,但HomeController所做的事情与之前相同。由于我们现在有了测试,可以确保在这个过程中没有破坏原有的功能。

  • 当我们在修改@RequestMapping时,还可以对HomeController 做另外一个变更。

    • @RequestMapping的value属性能够接受一 个String类型的数组。

    • 到目前为止,我们给它设置的都是一个 String类型的“/” 。

    • 但是,我们还可以将它映射到对“/homepage”的 请求,只需将类级别的@RequestMapping改为如下所示:

      - - @Controller.       ——>将控制器映射到“/@RequestMapping("/","/homepage")
          public class HomeController {
      
      }
      

      现在,HomeController的home ()方法能够映射到对“/”和“/homepage”的GET请求。
      5.2.3 传递模型数据到视图中

    1. HomeController是一个简单的控制器样例,但大多数控制器并不是这么简单。
    1. 在Spittr应用中,需要有一个页面来展示最近提交的Spittle列表,因此需要一个新的方法来处理这个页面。
    1. 首先,需要定义一个数据访问的Repository。为了解耦和避免陷入数据库访问的细节,将Repository定义为一个接口,并在稍后实现它(在第10章中)。
    1. 目前,只需要一个能够获取Spittle列表的Repository,下面是一个足够实现功能的SpittleRepository的示例。

      public. interface Spilttlerepository{
       List<Spittle>   findSpittleRepository(long max,int count);
      }
      
  • findSpittles ()方法接受两个参数。

    • 其中max参数代表所返回的 Spittle中,Spittle ID属性的最大值,
    • 而count参数表明要返回 多少个Spittle对象。
      为了获得最新的20个Spittle对象,我们可 以这样调用findSpittles ():
      List findSpittleRepository(long max,int count);
      现在,我们让Spittle类尽可能的简单,如下面的程序清单5.8所 示。它的属性包括消息内容、时间戳以及Spittle发布时对应的经纬 度。
      程序清单5.8 Spittle类:包含消息内容、时间戳和位置信息
      [图片]
  1. Spittle是一个基本的POJO数据对象,没有复杂的内容。
  2. 在Spittle类中,我们使用了Apache Common Lang包来实现equals()和hashCode()方法。
  3. 这些方法除了常规的作用外,当我们为控制器的处理器方法编写测试时,它们也是有用的。
    既然我们说到了测试,那么我们继续讨论这个话题并为新的控制器方 法编写测试。如下的程序清单使用Spring的MockMvc来断言新的处理 器方法中你所期望的行为。
    程序清单5.9 测试SpittleController处理针对“/spittles”的GET请求
    [图片]
    1. 首先,测试会创建一个SpittleRepository接口的mock实现。
    1. 这个mock实现会在其findSpittles()方法中返回20个Spittle对象。
    1. 接下来,将这个mock实现注入到一个新的SpittleController实例中。
    1. 然后,创建一个MockMvc并使用这个控制器进行测试。

根据您提供的内容,我将尝试梳理一下:

  1. 首先,与HomeController不同,这个测试在MockMvc构造器上调用了setSingleView()方法。
  • 优点
    • 这样可以避免mock框架解析控制器中的视图名。在很多场景中,这样做并不是必需的。
  • 缺点
    • 但是对于这个控制器方法来说,视图名与请求路径非常相似。按照默认的视图解析规则,MockMvc会发生失败,因为无法区分视图路径和控制器的路径。
  • 在这个测试中,设置InternalResourceView的实际路径与InternalResourceViewResolver的配置一致是无关紧要的。
  1. 这个测试对"/spittles"发起GET请求,并断言视图的名称为"spittles",并且模型中包含名为"spittleList"的属性,该属性包含预期的内容。
  2. 当然,如果此时运行测试,它将会失败。它不是在运行时失败,而是在编译时就会失败。这是因为我们还没有编写SpittleController。现在,我们需要创建SpittleController。
    总结概要,我梳理了以下内容:与HomeController不同,这个测试在MockMvc构造器上调用了setSingleView()方法,避免了视图名的解析问题。测试对"/spittles"发起GET请求,并断言视图的名称为"spittles",并且模型中包含名为"spittleList"的属性,该属性包含预期的内容。然而,由于还没有编写SpittleController,测试将在编译时失败。请注意,以上内容是用中文输出的。如果您有任何其他问题或需要进一步的解释,请随时告诉我。

让它满足程序清单5.9的预期。如下的SpittleController实现将 会满足以上测试的要求。
程序清单5.10 SpittleController:在模型中放入最新的spittle 列表
[图片]

  1. 首先,SpittleController类有一个构造器,使用@Autowired注解来注入SpittleRepository。这个SpittleRepository随后在spittles()方法中使用,用于获取最新的spittle列表。
  2. 在spittles()方法中,我们给定了一个Model作为参数。这样,spittles()方法可以将Repository中获取到的Spittle列表填充到模型中。Model实际上是一个Map(键值对的集合),它会传递给视图,从而使数据能够渲染到客户端。
  3. 当调用addAttribute()方法并且不指定key时,key会根据值的对象类型进行推断。在这个例子中,因为值是一个List,所以键会被推断为spittleList。
  4. spittles()方法的最后一步是返回spittles作为视图的名称,这个视图将用于渲染模型中的数据。
    如果你希望显式声明模型的key的话,那也尽可以进行指定。例如, 下面这个版本的spittles ()方法与程序清单5. 10中的方法作用是一样的:
    [图片]
    如果你希望使用非Spring类型的话,那么可以用java.util.Map来 代替Model。下面这个版本的spittles ()方法与之前的版本在功能 上是一样的:
    [图片]
    既然我们现在提到了各种可替代的方案,那下面还有另外一种方式来 编写spittles ()方法:
    [图片]
  5. 首先,这个版本与其他版本有一些差别。它没有返回视图名称,也没有显式地设置模型,而是直接返回了Spittle列表。
  6. 当处理器方法像这样返回对象或集合时,这个值会放到模型中,模型的key会根据其类型推断得出(在本例中,是spittleList)。
  7. 逻辑视图的名称将根据请求路径推断得出。因为这个方法处理针对“/spittles”的GET请求,所以视图的名称将是spittles(去掉开头的斜线)。
  8. 无论选择哪种方式编写spittles()方法,结果都是相同的。模型中会存储一个Spittle列表,key为spittleList,然后将这个列表发送到名为spittles的视图中。
  9. 根据配置的InternalResourceViewResolver,视图的JSP文件将是“/WEB-INF/views/spittles.jsp”。
  10. 现在,数据已经放到模型中,那么在JSP中如何访问它呢?实际上,当视图是JSP时,模型数据会作为请求属性放到请求(request)中。因此,在spittles.jsp文件中可以使用JSTL(JavaServer Pages Standard Tag Library)的<c:forEach>标签来渲染spittle列表。

[图片]
图5.3为显示效果,能够让你对它在Web浏览器中是什么样子有个可视 化的印象。
尽管SpittleController很简单,但是它依然比 HomeController更进一步了。不过,SpittleController和 HomeController都没有处理任何形式的输入。现在,让我们扩 展SpittleController,让它从客户端接受一些输入。
图5 .3 控制器中的Spittle模型数据将会作为请求参数,并在Web页面上渲染为列 表的形式

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消