跟随SpringMVC的请求
!!!等有时间了自己画一个符合描述的图,图来源于网络
- DispatcherServlet(前端控制器)会通过查询处理器映射将请求交给指定的SpringMVC控制器
- 处理器映射通过请求所携带的URL信息进行决策,确定请求的下一站
- 确定了控制器之后,DispatcherServlet会将请求交给指定的控制器,请求卸下负载等待控制器的处理,控制器会将具体的业务逻辑交给M层去实现
- 在控制器处理完成之后,会产生一些用于响应的信息,这些信息被称为模型,不能只将原始信息数据返回给用户,所以还会发送一个视图(视图名称),将模型视图打包发给DispatcherServlet,在这里使用视图名可以保证控制器不与具体的视图相耦合
- 因为从控制器传来的只是一个视图名,DispatcherServlet会使用视图解析器匹配为一个特定的视图
- DispatcherServlet交付模型数据,实现视图
- 视图使用模型视图输出,输出通过响应对象传递给客户端
1. 搭建SpringMVC
用javaConfig初始化DispatcherServlet,并加载Spring的应用上下文,去扩展抽象类AbstractAnnotionConfigDispatcherServletInitialiaer,实现抽象类的三个方法,前两个方法用来加载上下文配置文件,第三个方法配置DispatcherServlet的映射路径
package config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {//扩展该抽象类将自动的配置DispatcherServlet和Spring上下文
protected Class<?>[] getRootConfigClasses() {
//指定由ContextLoaderListen加载的上下文的配置类,这些bean通常是驱动应用后端的中间层和数据层组件(除了springWeb组件的bean)
return new Class<?>[] {RootConfig.class};
}
protected Class<?>[] getServletConfigClasses() {
//指定由DispatcherServlet加载的上下文的配置类,DispatcherServlet上下文加载包含web组件的bean,包括控制器、视图解析器、处理器映射
return new Class<?>[]{WebConfig.class};
}
protected String[] getServletMappings() {
//配置DispatcherServlet的映射路径
return new String[]{"/"};
}
}
2. 启用SpringMVC
- 通过配置WebConfig来启动SpringMVC
- 通过注解@EnableWebMvc启动SpringMVC的注解驱动
- 启用组件扫描
- 创建视图解析器bean
- 配置静态资源的处理???还没有
package 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.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc//注解启动SpringMVC的注解驱动
@ComponentScan("spitter.web")//启动组件扫描,怎样去控制仅仅扫描web组件????????
public class WebConfig {
@Bean
public ViewResolver viewResolver() {//配置jsp视图解析器
InternalResourceViewResolver resolver=new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");//设置视图名称前缀
resolver.setSuffix(".jsp");//设置视图名称后缀
resolver.setExposeContextBeansAsAttributes(true);//
return resolver;
}
}
- 配置RootConfig
- s
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.context.annotation.ComponentScan.Filter;
@Configuration
@ComponentScan(basePackages= {"spitter.web"},
excludeFilters= {
@Filter(type=FilterType.ANNOTATION,value=EnableWebMvc.class)
})//启动组件扫描,新加的内容的意思??????
public class RootConfig {
}
3. 编写基本的控制器
- 配置
- 使用@Controller声明组件为控制器
- 使用@RequestMapping声明所要处理的请求(通过范围,请求方式等确定)
package spitter_web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;//method的GET取值
@Controller
public class HomeController {
@RequestMapping(value="/",method=RequestMethod.GET)//配置要控制的请求,通过定义value和method
public String home() {
return "home";
}
}
4. 进行测试
package chapter_05;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import spitter_web.HomeController;
public class HomeControllerTest {
@Test
public void testHomePage() throws Exception {
HomeController controller=new HomeController();//实例化控制器
MockMvc mockMvc = standaloneSetup(controller).build();//创建MockMvc实例
mockMvc.perform(get("/")).andExpect(view().name("home"));//发起对"/"的get请求,并断言结果视图的名称为home
}
}
5. 传递模型数据到视图中(通过Model)
- 定义数据访问的Repository,为了实现解耦以及避免陷入数据库的实现细节上,这里利用接口实现
package spittr_data;
import java.util.List;
import entity.Spittle;
public interface SpittleRepository {
public List<Spittle> findSpittles();
}
- 实现实体类Spittle
package entity;
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");
}
}
- 书写控制器
package spitter_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 SpittleRepository repository;
@Autowired//注入数据仓库类
public SpittleController(SpittleRepository repository) {
this.repository=repository;
}
@RequestMapping(method=RequestMethod.GET)
public String Spittles(Model model) {
model.addAttribute("spittleList",repository.findSpittles(Long.MAX_VALUE,20));//将查询到的数据加入模型中
return "spittlesR";
}
}
- 书写测试类
package chapter_05;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
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.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.web.servlet.view.InternalResourceView;
import entity.Spittle;
import spitter_web.SpittleController;
import spittr_data.SpittleRepository;
public class SpittleControllerTest {
@Test
public void houldShowRecentSpittles() throws Exception {
List<Spittle> expectedSpittles = createSpittleList(20);
SpittleRepository mockRepository = mock(SpittleRepository.class);
when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
.thenReturn(expectedSpittles);
SpittleController controller = new SpittleController(mockRepository);
MockMvc mockMvc = standaloneSetup(controller)
/* .setSingleView(new InternalResourceView("/WEB-INF/views/spittlesR.jsp"))//当请求名称与视图返回名称一样时,在这里我将视图名改了
*/ .build();
mockMvc.perform(get("/spittles"))
.andExpect(view().name("spittlesR"))
.andExpect(model().attributeExists("spittleList"))
.andDo(MockMvcResultHandlers.print());
/* .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;
}
}
6. 接受请求的输入
- SpringMVC允许以多种方式将客户端的数据传到控制器的处理器方法中
- 查询参数
- 表单参数
- 路径变量
- 处理查询参数
- 通过路径名+参数值将参数传递到控制器
- 在控制器的方法中通过注解@RequestParam来定义从前端传来的参数,还可以设置默认值
package spitter_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 org.springframework.web.bind.annotation.RequestParam;
import spittr_data.SpittleRepository;
@Controller
@RequestMapping("/spittles")
public class SpittleController {
private SpittleRepository repository;
@Autowired//注入数据仓库类
public SpittleController(SpittleRepository repository) {
this.repository=repository;
}
@RequestMapping(method=RequestMethod.GET)
public String Spittles(@RequestParam(value="max",defaultValue=(Long.MAX_VALUE+"")) Long max,
@RequestParam(value="count",defaultValue="20") int count,Model model) {
model.addAttribute("spittleList",repository.findSpittles(max,count));//将查询到的数据加入模型中
return "spittlesR";
}
}
- 通过路径参数决定输入
- 从面向资源的角度来看,要识别的资源应该通过URL路径进行标识,而不是通过查询参数(路径+请求参数)
- 例如/spittles/show?spittle_id=12345应写成/spittles/show/12345
- 之前实现的控制器方法都被@RequestMapping映射到静态的地址上,在这里如果要实现设计好的路径参数需要编写@RequestMapping的变量部分
//路径参数实现数据传递
//之前实现的控制器方法都被@RequestMapping映射到静态的地址上,在这里如果要实现设计好的路径参数需要编写@RequestMapping的变量部分
@RequestMapping(value="/{spittleId}",method=RequestMethod.GET)//通过占位符{}匹配任意值,设置变量spittleId
public String spille(@PathVariable("spittleId") Long spittleId,Model model) {//配置注解@PathVariable使用@RequestMapping的变量
model.addAttribute(repository.findById(spittleId));//key值默认为函数的返回这类型,在这里是spittle
return "spittle";
}
代码扩展在SpittleController类中,该类同查询参数
- 处理表单
- 当请求中传递较少数据时,查询参数和路径参数可以胜任,但我们还要处理表单等产生数据比较多的时候要使用处理表单功能
- 使用表单体现在两个方面
- 展现表单
- 处理用户通过表单提交的数据
3.1 展现表单
去配置控制器方法,返回视图名,通过视图解析器去查找相应的jsp文件
package spitter_web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/spittle")
public class SpitterController {
//展现表单
@RequestMapping(value="register",method=RequestMethod.GET)
public String showRegisterForm() {
return "registerForm";
}
}
3.2 处理表单
- 将实体类对象作为控制器方法的参数接收表单数据
- 注册完成之后,为了防止重复提交表单数据(浏览器刷新页面),可以重定向到一个页面(在return中添加redirect)
处理注册提交的表单,并在处理之后重定向到显示每个注册用户注册信息的页面
package spitter_web;
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 entity.Spitter;
import spittr_data.SpitterRepository;
@Controller
@RequestMapping("/spittle")
public class SpitterController {
private SpitterRepository repository;
@Autowired//通过构造器自动注入依赖
public SpitterController(SpitterRepository repository) {
this.repository=repository;
}
//展现表单
@RequestMapping(value="register",method=RequestMethod.GET)
public String showRegisterForm() {
return "registerForm";
}
//处理表单
@RequestMapping(value="register",method=RequestMethod.POST)
public String handlerRegister(Spitter spitter) {//通过实体类对象接收表单数据
repository.save(spitter);
return "redirect:/spitter/"+spitter.getUsername();
//在将注册信息存储完成之后,为了防止因为舒心浏览器而导致的表单重复提交问题,这里要重定向到每个注册用户的注册信息页面(profile.jsp)
}
//显示不同注册用户的注册信息
@RequestMapping(value="{/userName}",method=RequestMethod.GET)
public String showRegisterInfo(@PathVariable String username,Model model) {
Spitter spitter = repository.findByUsername(username);
model.addAttribute(spitter);
return "profile";
}
}
6. 校验表单
- 通过java校验API(java validation API),来对表单数据进行校验,从而限制这行属性值
- 该方式校验表单并不能阻止表单你提交
- 通过注解实现
- @AssertFalse:所注解的元素必须是Bollean类型,并且值为false
- @AssertTrue:所注解的元素必须是Bollean类型,并且值为true
- @DecimalMax:所注解的元素必须是数字,并且值小于或等于给定的BigDecimalSting的值
- @DecimalMin:所注解的元素必须是数字,并且值大于或等于给定的BigDecimalSting的值
- @Digits:所注解的元素必须是数字类型,并且它的值有指定的位数
- @Future:所注解的元素必须是一个将来的日期
- @Max:所注解的元素必须是数字,并且它的值要小于或等于给定的值
- @Min:所注解的元素必须是数字,并且它的值要大于或等于给定的值
- @NotNull:所注解的元素不能为空
- @Null:所注解的元素为空
- @Past:所注解的元素必须是一个已经过去的日期
- @Pattern:所注解的元素必须匹配给定的正则表达式
- @Size:所注解的元素必须是String、集合或数组,并且它的长度要符合给定范围
- java校验API的实现可能还会提供额外的校验注解,同时我们也可以定义自己的限制条件
- 设置步骤
- 给表单数据的实体类属性通过注解设置限定
- 在控制器的方法中指定要校验的参数(@Valid标注要校验的参数,Errors对象检查是否出错)
- 给属性值加入限定
package entity;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Spitter {
private Long id;
@NotNull//限制属性非空
@Size(min=5, max=16)//限制字符串长度大于等于5,小于等于16
private String username;
@NotNull//限制属性非空
@Size(min=5, max=25)//限制字符串长度大于等于5,小于等于25
private String password;
@NotNull//限制属性非空
@Size(min=2, max=30)//限制字符串长度大于等于2,小于等于30
private String firstName;
@NotNull//限制属性非空
@Size(min=2, max=30)//限制字符串长度大于等于2,小于等于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);//this在这里的作用??????????
}
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;
}
- 在控制器的方法中指定要校验的参数
//处理表单,校验表单
@RequestMapping(value="register",method=RequestMethod.POST)
public String handlerRegister(@Valid Spitter spitter,Errors errors) {
//通过实体类对象参数接收表单数据,对参数加注解,指定对它进行校验,后跟Errors对象,用来判断校验结果
if(errors.hasErrors()) {
return "registerForm";//判断表单数据是否有错,有错则让浏览器转到注册页面
}
repository.save(spitter);
return "redirect:/spitter/"+spitter.getUsername();
//在将注册信息存储完成之后,为了防止因为舒心浏览器而导致的表单重复提交问题,这里要重定向到每个注册用户的注册信息页面(profile.jsp)
}