代码结构

域对象 spittle.java

package _5BuildingSpringwebapplications.domain;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(message, time, null, null);	}	public Spittle(String message, Date time, Double longitude, Double latitude) {		this.id = null;		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");	}}

dao层SpittleRepositoryImpl.java,示例中没有没有内容,但是在源码中有实际内容,即相关的sql查询,但是本章还没有涉及到系统的后端,这样的话,只能通过mockito校验controller层,不能在浏览器中操作连接.

package _5BuildingSpringwebapplications.repository;import java.util.List;import org.springframework.stereotype.Repository;import _5BuildingSpringwebapplications.domain.Spittle;@Repositorypublic class SpittleRepositoryImpl implements SpittleRepository{	@Override	public List
 findSpittles(long max, int count) { return null; } @Override public Spittle findOne(long spittleId) { // TODO Auto-generated method stub return null; }}

controller层,SpittleController.java,这里没有明确的区别controller层和service层

package _5BuildingSpringwebapplications.web;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import _5BuildingSpringwebapplications.domain.Spittle;import _5BuildingSpringwebapplications.repository.SpittleRepository;@Controller@RequestMapping({ "/spittles" })public class SpittleController {	private static final String MAX_LONG_AS_STRING = "9223372036854775807";		private SpittleRepository spittleRepository;		@Autowired	public SpittleController(SpittleRepository spittleRepository) {		// TODO Auto-generated constructor stub		this.spittleRepository = spittleRepository;	}//	@RequestMapping(method = RequestMethod.GET)//	public String spittles(Model model) {//		model.addAttribute("spittleList",spittleRepository.findSpittles(Long.MAX_VALUE, 20));/* 默认的属性名是spittleList *///		return "spittles";/*这个默认的View名就是spittles*///	}		@RequestMapping(method = RequestMethod.GET)	public List
 spittles(@RequestParam(value = "max",defaultValue = MAX_LONG_AS_STRING) long max, @RequestParam(value = "count", defaultValue = "20") int count) { return spittleRepository.findSpittles(max, count); } @RequestMapping(value = "/show", method = RequestMethod.GET) public String showSpittle(@RequestParam("spittle_id") long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; } //explicit about the model key // @RequestMapping(method = RequestMethod.GET) // public String spittles(Model model) { // model.addAttribute("spittleList", // spittleRepository.findSpittles(Long.MAX_VALUE, 20)); // return "spittles"; // } //work with a non-Spring type// @RequestMapping(method = RequestMethod.GET)// public String spittles(Map model) {// model.put("spittleList", spittleRepository.findSpittles(Long.MAX_VALUE, 20));// return "spittles";// }  //total different from the others,the model key is inferred from its type (spittleList, as in the other examples// @RequestMapping(method=RequestMethod.GET)// public List
 spittles() {// return spittleRepository.findSpittles(Long.MAX_VALUE, 20));// } @RequestMapping(value = "/{spittleId}", method = RequestMethod.GET) public String spittle(@PathVariable("spittleId") long spittleId, Model model) { model.addAttribute(spittleRepository.findOne(spittleId)); return "spittle"; } }

对contrller层的校验,主要校验了是否能够返回正确的view名等.源码中mockito的相关方法只写了名字,eclipse不能自动识别,因为方法都是静态的,如果加上类名,即能正确识别.mokito很方便,实际上在用junit运行下面的代码时,不要求service层对应的方法有实际的内容.

package _5BuildingSpringwebapplications.web.test;import static org.hamcrest.Matchers.hasItems;import java.util.ArrayList;import java.util.Date;import java.util.List;import org.hamcrest.Matchers;import org.junit.Test;import org.mockito.Mockito;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;import org.springframework.web.servlet.view.InternalResourceView;import _5BuildingSpringwebapplications.domain.Spittle;import _5BuildingSpringwebapplications.repository.SpittleRepository;import _5BuildingSpringwebapplications.web.SpittleController;public class SpittleControllerTest {	@Test	public void shouldShowRecentSpittles() throws Exception {		List
 expectedSpittles = createSpittleList(20); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); Mockito.when(mockRepository.findSpittles(Long.MAX_VALUE, 20)).thenReturn(expectedSpittles); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller) .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp")).build(); mockMvc.perform(MockMvcRequestBuilders.get("/spittles")) .andExpect(MockMvcResultMatchers.view().name("spittles")) .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList")).andExpect(MockMvcResultMatchers .model().attribute("spittleList", hasItems(expectedSpittles.toArray()))); } private List
 createSpittleList(int count) { List
 spittles = new ArrayList
(); for (int i = 0; i < count; i++) { spittles.add(new Spittle("Spittle " + i, new Date())); } return spittles; } @Test public void shouldShowPagedSpittles() throws Exception { List
 expectedSpittles = createSpittleList(50); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); Mockito.when(mockRepository.findSpittles(238900, 50)).thenReturn(expectedSpittles); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller) .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp")).build(); mockMvc.perform(MockMvcRequestBuilders.get("/spittles?max=238900&count=50")) .andExpect(MockMvcResultMatchers.view().name("spittles")) .andExpect(MockMvcResultMatchers.model().attributeExists("spittleList")).andExpect(MockMvcResultMatchers .model().attribute("spittleList", Matchers.hasItems(expectedSpittles.toArray()))); } @Test public void testSpittle() throws Exception { Spittle expectedSpittle = new Spittle("hello", new Date()); SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class); Mockito.when(mockRepository.findOne(12345)).thenReturn(expectedSpittle); SpittleController controller = new SpittleController(mockRepository); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); mockMvc.perform(MockMvcRequestBuilders.get("/spittles/12345")) .andExpect(MockMvcResultMatchers.view().name("spittle")) .andExpect(MockMvcResultMatchers.model().attributeExists("spittle")) .andExpect(MockMvcResultMatchers.model().attribute("spittle", expectedSpittle)); }}

junit运行结果

本示例没有贴出SpittleRepository.java即SpittleRepositoryImpl实现的接口.其实很简单,自己实现即可.

域对象spitter.java

package _5BuildingSpringwebapplications.domain;import javax.validation.constraints.NotNull;import javax.validation.constraints.Size;import org.apache.commons.lang3.builder.EqualsBuilder;import org.apache.commons.lang3.builder.HashCodeBuilder;import org.hibernate.validator.constraints.Email;public class Spitter {	private Long id;	@NotNull	@Size(min = 5, max = 16)	private String username;	@NotNull	@Size(min = 5, max = 25)	private String password;	@NotNull	@Size(min = 2, max = 30)	private String firstName;	@NotNull	@Size(min = 2, max = 30)	private String lastName;	@NotNull	@Email	private String email;	public Spitter() {	}	public Spitter(String username, String password, String firstName, String lastName, String email) {		this(null, username, password, firstName, lastName, email);	}	public Spitter(Long id, String username, String password, String firstName, String lastName, String email) {		this.id = id;		this.username = username;		this.password = password;		this.firstName = firstName;		this.lastName = lastName;		this.email = email;	}	public String getUsername() {		return username;	}	public void setUsername(String username) {		this.username = username;	}	public String getPassword() {		return password;	}	public void setPassword(String password) {		this.password = password;	}	public Long getId() {		return id;	}	public void setId(Long id) {		this.id = id;	}	public String getFirstName() {		return firstName;	}	public void setFirstName(String firstName) {		this.firstName = firstName;	}	public String getLastName() {		return lastName;	}	public void setLastName(String lastName) {		this.lastName = lastName;	}	public String getEmail() {		return email;	}	public void setEmail(String email) {		this.email = email;	}	@Override	public boolean equals(Object that) {		return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email");	}	@Override	public int hashCode() {		return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email");	}}

spitter对象的service层SpitterRepositoryImpl.java,与spittle的dao层一样,这里的方法都没有具体的内容,即没有与数据库的交互,这不影响对本章的理解.

package _5BuildingSpringwebapplications.repository;import org.springframework.stereotype.Repository;import _5BuildingSpringwebapplications.domain.Spitter;@Repositorypublic class SpitterRepositoryImpl implements SpitterRepository{	@Override	public void save(Spitter spitter) {		// TODO Auto-generated method stub			}	@Override	public Spitter findByUsername(String username) {		// TODO Auto-generated method stub		return null;	}	}

spitter的controller层SpitterController.java

package _5BuildingSpringwebapplications.web;import javax.validation.Valid;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.validation.Errors;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import _5BuildingSpringwebapplications.domain.Spitter;import _5BuildingSpringwebapplications.repository.SpitterRepository;@Controller@RequestMapping("/spitter")public class SpitterController {	private SpitterRepository spitterRepository;	public SpitterController() {		// TODO Auto-generated constructor stub	}	@Autowired	public SpitterController(SpitterRepository spitterRepository) {		this.spitterRepository = spitterRepository;	}	@RequestMapping(value = "/register", method = RequestMethod.GET)	public String showRegistrationForm() {		return "registerForm";	}	@RequestMapping(value = "/register", method = RequestMethod.POST)	public String proce***egistration(@Valid Spitter spitter, Errors errors) {		if (errors.hasErrors()) {			return "registerForm";		}		spitterRepository.save(spitter);		return "redirect:/spitter/" + spitter.getUsername();	}	@RequestMapping(value = "/{username}", method = RequestMethod.GET)	public String showSpitterProfile(@PathVariable String username, Model model) {		Spitter spitter = spitterRepository.findByUsername(username);		model.addAttribute(spitter);		return "profile";	}}

代码中没有提到model层和view层.controller直接调的dao,本章的源码中也没有model层.可能因为涉及到的请求都比较简单,不需要太复杂的逻辑运行,直接由dao层返回,由view显示即可.

本章的内容除了介绍springmvc的一些知识点外,如何对controller进行单元测试也是一大亮点.