SpringInAction笔记(五)—— 构建Spring Web应用程序(上)

       Spring MVC框架基于模型-视图-控制器(Model-View-Controller, MVC)模式实现,能够帮你构建像Spring框架那样灵活和松耦合的Web应用程序。
1、Spring MVC起步
1.1、跟踪Spring MVC的请求
       请求从离开浏览器开始到获取响应返回,它会经历好多站,在每站都会留下一些信息同时也会带上其他信息。图展示了请求使用Spring MVC所经历的所有站点。


 

(1)请求离开浏览器时,会带有用户所请求内容的信息,至少会包含请求的URL。但是还可能带有其他的信息,例如用户提交的表单信息。Spring MVC所有的请求都会通过DispatcherServlet这个前端控制器(front controller)Servlet。前端控制器是常用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。   

(2)DispatcherServlet的任务是将请求发送给Spring MVC控制器(controller)。控制器是一个用于处理请求的Spring组件。在典型的应用程序中可能会有多个控制器,DispatcherServlet需要知道应该将请求发送给哪个控制器。所以DispatcherServlet以会查询一个或多个处理器映射(handler mapping) 来确定请求的下一站在哪里。处理器映射会根据请求所携带的URL信息来进行决策。

(3)选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息(实际上,设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个服务对象进行处理)。

(4)控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送给一个视图(view),通常会是JSP。控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet 。这样控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑名称,这个名字将会用来查找产生结果的真正视图。

(5)DispatcherServlet将会使用视图解析器(view resolver) 来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能不是JSP。

(6)既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是JSP) ,在这里它交付模型数据。请求的任务就完成了。

(7)视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端(不会像听上去那样硬编码)

 

1.2、搭建Spring MVC

创建一个Web工程,名称为spittr。注意,需要删除掉工程/WebRoot目录下的index.jsp文件,并把spring 4.3.13的jar包拷贝到libs文件夹下,jstl-1.2.jar也需要添加进去。




1.2.1 配置DispatcherServlet
从Servlet 3开始,像DispatcherServlet这样的Servlet无需配置在web.xml文件中,可以使用Java将DispatcherServlet配置在Servlet容器中。

package spittr.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  
	/**
	 * 将一个或多个路径映射到DispatcherServlet上
	 */
	@Override
	protected String[] getServletMappings() {  
		return new String[] { "/" };
	}
	
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class<?>[] { RootConfig.class };
	}
 
	/**
	 * 指定配置类
	 * 当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置类或配置文件中所声明的bean
	 */
	@Override 
	protected Class<?>[] getServletConfigClasses() {   
		return new Class<?>[] { WebConfig.class }; 
	}
 
}

        扩展AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动地配置Dispatcher-Servlet和Spring应用上下文,Spring的应用上下文会位于应用程序的Servlet上下文之中

        SpitterWebInitializer扩展三个方法:       

        第一个方法是getServletMappings(),它会将一个或多个路径映射到DispatcherServlet上。在本例中,它映射的是“/”,这表示它会是应用的默认Servlet。它会处理进入应用的所有请求。

       第二个方法是getServletConfigClasses()。当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。getServletConfigClasses()方法中,我们要求DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类(使用Java配置)中的bean。       

       第三个方法getRootConfigClasses()。在Spring Web应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由ContextLoaderListener创建的。我们希望DispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射,而ContextLoaderListener要加载应用中的其他bean。这些bean通常是驱动应用后端的中间层和数据层组件。      

    实际上:AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServlet和ContextLoaderListener。GetServleConfigClasses()方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的bean。getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean。  

    在本例中,根配置定义在RootConfig中,DispatcherServlet的配置声明在WebConfig中。(AbstractAnnotationConfigDispatcherServletInitializer它只能部署到支持Servlet 3.0的服务器中才能正常工作)。

 

1.2.2 启动Spring MVC      

       创建的最简单的Spring MVC配置就是一个带有@EnableWebMvc注解的类。但仅仅配置了@EnableWebMvc注解,还有不少问题要解决:      

      没有配置视图解析器。如果这样的话,Spring默认会使用BeanNameViewResolver,这个视图解析器会查找ID与视图名称匹配的bean,并且查找的bean要实现View接口,它以这样的方式来解析视图。      

      没有启用组件扫描。这样的结果就是,Spring只能找到显式声明在配置类中的控制器。      

      这样配置的话,DispatcherServlet会映射为应用的默认Servlet,所以它会处理所有的请求,包括对静态资源的请求,如图片和样式表(在大多数情况下,这可能并不是你想要的效果)。

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("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

	/**
	 * 配置视图解析器,名为home的视图将被解析为/WEB-INF/views/home.jsp
	 * @return
	 */
	@Bean
	public ViewResolver viewResolver() {
		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
	    resolver.setPrefix("/WEB-INF/views/");
	    resolver.setSuffix(".jsp");
	    resolver.setExposeContextBeansAsAttributes(true);
	    return resolver;  
	}

	/**
	 * 配置静态资源的处理
	 * 此配置要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,
	 * 而非使用DispatcherServlet本身来处理此类请求
	 */
	@Override
	public void configureDefaultServletHandling(
			DefaultServletHandlerConfigurer configurer) {
		// TODO Auto-generated method stub
		//super.configureDefaultServletHandling(configurer);
		configurer.enable();
	}	
	
}

      WebConfig使用@Component-Scan注解,因此将会扫描spitter.web包来查找组件。控制器带有@Controller注解,这会使其成为组件扫描时的候选bean。不用再显示配置
        添加了一个ViewResolver bean:InternalResourceViewResolver。它会查找JSP文件,在查找的时候,它会在视图名称上加一个特定的前缀和后缀(例如,名为home的视图将会解析为/WEB-INF/views/home.jsp)。
     WebConfig还扩展了WebMvcConfigurerAdapter并重写了其configureDefaultServletHandling()方法。通过调用DefaultServlet-HandlerConfigurer的enable()方法,我们要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此类请求。

简单配置RootConfig

        RootConfig使用了@ComponentScan注解,有很多机会可以使用非Web的组件充实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={"spittr"},
    excludeFilters={@Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)})
public class RootConfig {
}

 

 

2、编写基本的控制器

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

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() {
		System.out.println("处理get请求");
		return "home";
	}
}

HomeController带有@Controller注解,很显然这个注解是用来声明控制器。
        HomeController是一个构造型(stereotype)的注解,它基于@Component注解。在这里,它的目的就是辅助实现组件扫描。因为HomeController带有@Controller注解,因此组件扫描器会自动找到HomeController,并将其声明为Spring应用上下文中的一个bean。其实,你也可以让HomeController带有@Component注解,它所实现的效果是一样的,但是在表意性上可能会差一些,无法确定HomeController是什么组件类型。
        HomeController唯一的一个方法,也就是home()方法,带有@RequestMapping注解。它的value属性指定了这个方法所要处理的请求路径,method属性细化了它所处理的HTTP方法。它返回了一个String类型的“home”。这个String将会被Spring MVC解读为要渲染的视图名称。DispatcherServlet会要求视图解析器将这个逻辑名称解析为实际的视图。

       鉴于配置InternalResourceViewResolver的方式,视图名“home”将会被解析为“/WEB-INF/views/home.jsp”路径的jsp。

 

home.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" 
          type="text/css" 
          href="http://blog.163.com/yetong1985@126/blog/<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <h1>Welcome to Spitter</h1>

    <a href="http://blog.163.com/yetong1985@126/blog/<c:url value="/spittles" />" >Spittles</a> | 
    <a href="http://blog.163.com/yetong1985@126/blog/<c:url value="/spitter/register" />" >Register</a>
  </body>
</html>

 

发布到tomcat,输入地址:http://localhost:8080/spittr/ ,得到:

注意:如果报http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar错,请下载jstl-1.2.jar

 

2..1 测试控制器                           

       从Spring 3.2开始,可以按照控制器的方式来测试Spring MVC中的控制器了,而不仅仅是作为POJO进行测试。Spring现在包含了一种mock Spring MVC并针对控制器执行HTTP请求的机制。这样的话,在测试控制器的时候,就没有必要再启动Web服务器和Web浏览器了。

package spittr.test;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import spittr.web.HomeController;

public class HomeControllerTest {
  
	@Test
	public void testHomePage() throws Exception { 
		HomeController controller = new HomeController();  
		//搭建MOckMvc,通过参数指定一组控制器,这样就不需要从上下文获取了
		MockMvc mockMvc = standaloneSetup(controller).build();  
		/*
		 * MockMvcRequestBuilders.get("/user/1")构造一个请求
		 * mockMvc.perform执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理
		 * andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确
		 */
		mockMvc.perform(MockMvcRequestBuilders.get("/"))
           .andExpect(view().name("home")); 
	}

}

    发起了对“/”的GET请求,并断言结果视图的名称为home。它首先传递一个HomeController实例到MockMvcBuilders.standaloneSetup()并调用build()来构建MockMvc实例。然后它使用MockMvc实例来执行针对“/”的GET请求并设置期望得到的视图名称。

 

2.2 定义类级别的请求处理

      拆分@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() {
		System.out.println("处理get请求");
		return "home";
	}
}

       在这个新版本的HomeController中,路径现在被转移到类级别的@RequestMapping上,而HTTP方法依然映射在方法级别上。当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理器方法上。处理器方法上的@RequestMapping注解会对类级别上的@RequestMapping的声明进行补充(类上和方法上的映射组合就可以完成全部的映射)。

        @RequestMapping的value属性能够接受一个String类型的数组。到目前为止,我们给它设置的都是一个String类型的“/”,但是,我们还可以将它映射到对“/homepage”的请求:

package spittr.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller //声明一个控制器
@RequestMapping({"/","/homepage"})
public class HomeController {

	@RequestMapping(method=RequestMethod.GET) //处理对"/"的GET请求
	public String home() {
		System.out.println("处理get请求");
		return "home";
	}
}

 

2.3 传递模型数据到视图中

 

       在Spittr应用中,我们需要有一个页面展现最近提交的Spittle列表。因此,我们需要一个新的方法处理这个页面。

       首先,Spittle类尽可能简单,包含消息内容、时间戳以及Spittle发布时对应的经纬度信息:

清单1 Spittle.java

package spittr;

import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Spittle {
	
	private final Long id;
	//消息内容
	private final String message; 
	private final Date time;
	//纬度
	private Double latitude; 
	//经度
	private Double longitude;
  
	public Spittle(String message, Date time) {
		this(null, message, time, null, null); 
	}
  
	public Spittle(Long id, String message, Date time, Double longitude, Double latitude) {  
		this.id = id;   
		this.message = message;   
		this.time = time;   
		this.longitude = longitude;   
		this.latitude = latitude;
	}

	public long getId() {    
		return id;
	}

	public String getMessage() {  
		return message;
	}
  
	public Date getTime() {   
		return time;
	}
  
	public Double getLongitude() {  
		return longitude;
	} 
  
	public Double getLatitude() {   
		return latitude;
	}
  
	@Override 
	public boolean equals(Object that) {  
		return EqualsBuilder.reflectionEquals(this, that, "id", "time");
	}
   
	@Override  
	public int hashCode() {   
		return HashCodeBuilder.reflectionHashCode(this, "id", "time"); 
	}
  
}

 

然后需要定义一个数据访问的Repository。为了实现解耦以及避免陷入数据库访问的细节之中,我们将Repository定义为一个接口,并在后面实现它。此时,我们只需要一个能够获取Spittle列表的Repository。

 

清单2 SpittleRepository.java:

package spittr.data;

import java.util.List;

import spittr.Spittle;

public interface SpittleRepository {
	
	/**
	 * 
	 * @param max   返回的Spittlr对象中,Spittle ID属性的最大值
	 * @param count 返回多少个Spittle对象
	 * @return 获取最新的Spittle列表
	 */
	List<Spittle> findSpittles(long max, int count);
}

 

使用H2数据库

下载地址,下载Platform-Independent Zip版本,然后解压,把bin目录下的h2-1.4.195.jar文件拷贝到web工程WEB-INF/lib目录下。

 

在config包下新建一个数据源配置类DataConfig.java.java

package spittr.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
public class DataConfig {

	@Bean
	public DataSource dataSource() {
	    return new EmbeddedDatabaseBuilder()
	            .setType(EmbeddedDatabaseType.H2)
	            //初始化数据,chema.sql文件中包含用于创建数据表的关系
	            .addScript("classpath:spittr/data/schema.sql")
	            .setScriptEncoding("UTF-8")
	            .build();  
	}
	  
	@Bean
	public JdbcOperations jdbcTemplate(DataSource dataSource) {
	    return new JdbcTemplate(dataSource);  
	}
	
}

 

 

创建数据表及初始化数据:/spittr/src/spittr/data/schema.sql

DROP TABLE IF EXISTS Spittle;
create table Spittle (
	id identity,
	message varchar(140) not null,
	created_at timestamp not null,
	latitude double,
	longitude double
);

INSERT INTO Spittle (id, message, created_at, latitude, longitude) VALUES (1111, 'Spittles go fourth', '2018-02-24', 116.579618, 39.647447);

DROP TABLE IF EXISTS Spitter;
create table Spitter (
	id identity,
	username varchar(20) unique not null,
	password varchar(20) not null,
	first_name varchar(30) not null,
	last_name varchar(30) not null,
	email varchar(30) not null
);

 

修改RootConfig,引入数据源配置:

package spittr.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;


@Configuration
@Import(DataConfig.class) //导入spring jdbc配置
@ComponentScan(basePackages={"spittr"}, 
    excludeFilters={
		@ComponentScan.Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)
    })
public class RootConfig {
}

 

装配数据库JdbcSpittleRepository.java

package spittr.data;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import spittr.Spittle;

@Repository
public class JdbcSpittleRepository implements SpittleRepository {
	
	private JdbcOperations jdbc;
	
	@Autowired
	public JdbcSpittleRepository(JdbcOperations jdbc) {
	    this.jdbc = jdbc;  
	}

	@Override
	public List<Spittle> findSpittles(long max, int count) {
		// TODO Auto-generated method stub
	    return jdbc.query(
	            "select id, message, created_at, latitude, longitude" +
	            " from Spittle" +
	            " where id < ?" +
	            " order by created_at desc limit ?",
	            new SpittleRowMapper(), new Object[] { max, count });
	}

	@Override
	public Spittle findOne(long id) {
		// TODO Auto-generated method stub
	    return jdbc.queryForObject(
	    		"select id, message, created_at, latitude, longitude" +
	            " from Spittle" +
	            " where id = ?",
	            new SpittleRowMapper(), id);
	}
	
	
	private static class SpittleRowMapper implements RowMapper<Spittle> {
		
		public Spittle mapRow(ResultSet rs, int rowNum) throws SQLException {
			
			return new Spittle (
					rs.getLong("id"),
					rs.getString("message"), 
					rs.getDate("created_at"), 
					rs.getDouble("longitude"), 
					rs.getDouble("latitude"));		    
		}		  
	}

}

 

SpittleController类

package spittr.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import spittr.data.SpittleRepository;

@Controller
@RequestMapping("/spittles")
public class SpittleController {
	
	private static final String MAX_LONG_AS_STRING = "9223372036854775807";
	private SpittleRepository spittleRepository;

	@Autowired 
	public SpittleController(SpittleRepository spittleRepository) {  
		this.spittleRepository = spittleRepository;
	}
  
    @RequestMapping(method=RequestMethod.GET)
    public String spittles(Model model){
    	//将spittle添加到模型中
        model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE, 20));
        return "spittles";
    }

}

 

在spittles()方法中给定了一个Model作为参数。这样,spittles()方法就能将Repository中获取到的Spittle列表填充到模型中。Model实际上就是一个Map(也就是key-value对的集合),它会传递给视图,这样数据就能渲染到客户端了。当调用addAttribute()方法并且不指定key的时候,那么key会根据值的对象类型推断确定。在本例中,因为它是一个List<Spittle>,因此,键将会推断为spittleList。      

 

        spittles()方法所做的最后一件事是返回spittles作为视图的名字,这个视图会渲染模型。

       也可以显示声明模型的key,指定key的值:

    @RequestMapping(method=RequestMethod.GET)
    public String spittles(Model model){
    	//将spittle添加到模型中
        model.addAttribute("spittleList", spittleRepository.findSpittles(Long.MAX_VALUE, 20));
        return "spittles";
    }

 

希望使用非Spring类型的话,那么可以用java.util.Map来代替Model:

    @RequestMapping(method=RequestMethod.GET)
    public String spittles(Map model){
        model.put("spittleList", spittleReopsitory.findSpittles(Long.MAX_VALUE, 20));
        return "spittles";
    }

 

还有另外一种奇怪的替代方案:

    @RequestMapping(method=RequestMethod.GET)
    public List<Spittle> spittles(Map model){
        return spittleReopsitory.findSpittles(Long.MAX_VALUE, 20));
    }

它并没有返回视图名称,也没有显式地设定模型,这个方法返回的是Spittle列表。当处理器方法像这样返回对象或集合时,这个值会放到模型中,模型的key会根据其类型推断得出(在本例中,也就是spittleList)。而逻辑视图的名称将会根据请求路径推断得出。因为这个方法处理针对“/spittles”的GET请求,因此视图的名称将会是spittles(去掉开头的斜线)。

 

spittles.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" href="http://blog.163.com/yetong1985@126/blog/<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <div class="listTitle">
      <h1>Recent Spittles</h1>
      <ul class="spittleList">
        <c:forEach items="${spittleList}" var="spittle" >
          <li id="spittle_<c:out value="spittle.id"/>">
            <div class="spittleMessage"><c:out value="${spittle.message}" /></div>
            <div>
              <span class="spittleTime"><c:out value="${spittle.time}" /></span>
              <span class="spittleLocation">(<c:out value="${spittle.latitude}" />, <c:out value="${spittle.longitude}" />)</span>
            </div>
          </li>
        </c:forEach>
      </ul>
      <c:if test="${fn:length(spittleList) gt 20}">
        <hr />
        <s:url value="/spittles?count=${nextCount}" var="more_url" />
        <a href="http://blog.163.com/yetong1985@126/blog/${more_url}">Show more</a>
      </c:if>
    </div>
  </body>
</html>

输入http://localhost:8080/spittr/spittles,访问效果如图:

 

 测试SpittleController

 

使用Spring的MockMvc来断言新的处理器方法中所期望的行为。

package spittr.test;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.view.InternalResourceView;

import spittr.Spittle;
import spittr.data.SpittleRepository;
import spittr.web.SpittleController;

public class SpittleControllerTest {
 

    @Test
    public void shouldShowRecentSpittles() throws Exception {
        List<Spittle> expectedSpittles = createSpittleList(20);
        //创建SpittleRepository接口的mock对象
        SpittleRepository mockRepository = mock(SpittleRepository.class);
        //设定mock对象findSpittles方法调用时的返回值
        when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
            .thenReturn(expectedSpittles);
    
        SpittleController controller = new SpittleController(mockRepository);
        //注册一个@Controller实例,并设置单个视图,即视图解析时总是解析到这一个
        MockMvc mockMvc = standaloneSetup(controller)
            .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
            .build();
    
        
        mockMvc.perform(get("/spittles"))       //对"/spittles"发起get请求
           .andExpect(view().name("spittles"))
           .andExpect(model().attributeExists("spittleList"))
           .andExpect(model().attribute("spittleList", 
                      hasItems(expectedSpittles.toArray())));
    }
    
    private List<Spittle> createSpittleList(int count) {     
          List<Spittle> spittles = new ArrayList<Spittle>();    
          for (int i=0; i < count; i++) {    
              spittles.add(new Spittle("Spittle " + i, new Date()));     
          }    
          return spittles;
    }
    
}

 

       上面的测试通过创建一个SpittleRepository接口的mock实现,该实现会通过findSpittles()方法返回一个包含20个Spittle对象的集合。然后将这个bean注入到SpittleController实例中,并设置MockMvc使用该实例。

       不同于HomeControllerTest,该测试使用了setSingleView(),发起一个/spittles的GET请求,并断言视图是否为spittles以及model是否含有一个spittleList的属性值,在spittleList中包含预期的内容。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值