一、SpringMVC起步
请求处理流程
(1)请求携带url、参数等信息到达前端控制器DispatcherServlet(单例);
(2)DispatcherServlet会查询一个或多个处理器映射(handler mapping)来确定应该将请求发送给哪个控制器。处理器映射会根据请求所携带的URL信息来进行决策。
(3)DispatcherServlet将请求发送给选中的控制器,然后请求会卸下负载(用户提交信息)并耐心等待控制器处理这些信息。
(4)控制器将处理请求产生的模型信息数据打包,并标示出用于渲染数据的视图,然后将请求连同模型和视图名发送回DispatcherServlet;(视图名是逻辑名称,可以用来查找真正视图)
(5)DispatcherServlet将会使用视图解析器(view resolver)来将逻辑视图名匹配为一个特定的视图实现(不一定是jsp)。
(6)视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端;
二、代码编写
1、配置DispatcherServlet
(1)DispatcherServlet是Spring MVC的核心。在这里请求会第一次接触到框架,它要负责将请求路由到其他的组件之中。
(2)当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。
(3)DispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射,而ContextLoaderListener要加载应用中的其他bean。这些bean通常是驱动应用后端的中间层和数据层组件。
(4)AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServlet和ContextLoaderListener。getServletConfigClasses()方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的bean。getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中bean。
方式一:xml
SpringMVC_01
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
classpath:config/applicationContext.xml
springmvc
org.springframework.web.servlet.DispatcherServlet
1
springmvc
*.do
方式二:java
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import spittr.web.WebConfig;
public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 部署时自动发现这个类,并用它来配置Servlet上下文 P139
//AbstractAnnotationConfigDispatcherServletInitializer是传统web.xml的替代方案(servlet3.0之后支持)
@Override
protected String[] getServletMappings() {
// 会将一个或多个路径映射到DispatcherServlet,此处是"/"表示它会是默认的servlet,处理进入应用的所有请求
return new String[] { "/" };
}
@Override
protected Class>[] getRootConfigClasses() {
return new Class>[] { RootConfig.class };
}
@Override // 指定配置类
protected Class>[] getServletConfigClasses() {
// DispatcherServlet加载上下文时,会使用定义在WebConfig中bean
return new Class>[] { WebConfig.class };
}
}
2、启用Spring MVC
方式一:xml方式
方式二:java方式
WebConfig:
@Configuration
@EnableWebMvc
public class WebConfig {
}
/*
但还有其他问题要解决:
1、没有配置视图解析器。Spring默认会使用BeanNameView-Resolver,这个视图解析器会查找ID与视图名称匹配的bean,并且查找的bean要实现View接口,以这样的方式来解析视图。
2、没有启用组件扫描。Spring只能找到显式声明在配置类中的控制器。
3、DispatcherServlet会映射为应用的默认Servlet,所以它会处理所有的请求,包括对静态资源的请求,如图片和样式表(可能并不是想要的效果)。
*/
改进:
@Configuration
@EnableWebMvc // 启用spring mvc
@ComponentScan("spittr.web") // 启用组件扫描
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean // InternalResourceViewResolver会查找jsp,查找的时候会添加视图名前后添加此前缀后缀
public ViewResolver viewResolver() { // 配置jsp视图解析器
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();
// 设置DispatcherServlet对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此类请求??
}
}
问题:默认的Servlet是啥?本身处理的话将如何处理呢?
RootConfig:
@Configuration
@ComponentScan(basePackages = {"spittr"},
excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {
}
3、编写控制器
在Spring MVC中,控制器只是方法上添加了@RequestMapping注解的类,这个注解声明了它们所要处理的请求。
(1)定义@RequestMapping
a、@RequestMapping应用在方法上
@Controller // 声明为一个控制器
public class HomeController {
@RequestMapping(value = "/",method = GET) // 处理对“/”的GET请求
public String home(){
return "home"; // 视图名为 home
// home将会被Spring MVC解读为要渲染的视图名称,DispatcherServlet会要求视图解析器将这个逻辑名称解析为实际的视图
// 由前面配置,home将被解析为/WEB-INF/views/home.jsp路径的jsp
}
}
b、@RequestMapping应用在类级别上
当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理器方法上。处理器方法上的@RequestMapping注解会对类级别上的@RequestMapping的声明进行补充。
@Controller
@RequestMapping("/") // 将控制器映射到"/"
public class HomeController {
@RequestMapping(method = GET) // 处理GET请求
public String home(){
return "home"; // 视图名为 home
}
}
@RequestMapping({"/","/homepage"}) 可以接收String数组;
(2)传递模型数据到控制器
@Controller
@RequestMapping("/spittles")
public class SpittleController {
private SpittleRepository spittleRepository;
@Autowired // 创建SpittleController bean时自动注入SpittleRepository
public SpittleController(SpittleRepository spittleRepository){
this.spittleRepository = spittleRepository;
}
@RequestMapping(method=RequestMethod.GET)
public String spittles(Model model) { // 使用Model参数,则可将获取的信息填充到模型中
// 将spittle添加到模型中
model.addAttribute(spittleRepository
.findSpittles(Long.MAX_VALUE,20));
//当addAttribute不指定key时,key会根据值的对象类型推断,
//此处是一个List,key被推断为spittleList
return "spittles"; // 返回视图名
}
}
@RequestMapping(method=RequestMethod.GET)
public String spittles(Model model) {
model.addAttribute("spittleList", spittleRepository
.findSpittles(Long.MAX_VALUE,20));// 指定key,功能同上
return "spittles";
}
@RequestMapping(method=RequestMethod.GET)
public String spittles(Map model) {
model.put("spittleList", spittleRepository
.findSpittles(Long.MAX_VALUE,20));// 使用map
return "spittles";
}
// 没有返回视图名称,没有显式指定模型
//当处理器方法像这样返回对象或集合时,这个值会放到模型中,模型的key会根据其类型推断得出
//逻辑视图的名称将会根据请求路径推断得出。因为这个方法处理针对“/spittles”的GET请求,因此视图的名称将会是spittles(去掉开头的斜线)
@RequestMapping(method=RequestMethod.GET)
public List spittles(){
return spittleRepository.findSpittles(Long.MAX_VALUE, 20);
}
当视图是JSP的时候,模型数据会作为请求属性放到请求之中。
4、接口请求的输入
Spring MVC允许以多种方式将客户端中的数据传送到控制器的处理方法中,包括:
(1)处理查询参数
/*
defaultValue:表示如果当前参数请求中不存在,则使用此默认参数。查询参数是String的,因此此处也需要是String型;当绑定到方法的max时,它会转换成Long类型
*/
@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);
}
(2)通过路径参数接受输入
假设我们的应用程序需要根据给定的ID来展现某一个Spittle记录。其中一种方法就是编写处理器方法,通过使用@RequestParam注解,让它接受ID作为查询参数,/spittles/show?spittle_id=12345。另一种方式,让需要识别的资源通过URL路径进行标示,形如/spittles/12345。前者描述的是带有参数的一个操作,后者能够识别出要查询的资源,在这里使用后者更加合理。
// 占位符的名称要用大括号(“{”和“}”)括起来。路径中的其他部分要与所处理的请求完全匹配,但是占位符可以是任意的值
//@PathVariable("spittleId")注解,表明在请求路径中,不管占位符部分的值是什么都会传递到处理器方法的spittleId参数中
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(@PathVariable("spittleId") long spittleId, Model model) {
model.addAttribute(spittleRepository.findOne(spittleId));
return "spittle";
}
// 方法的参数名称与占位符名称相同,可以去掉@PathVariable的value属性。它会假设占位符的名称和方法参数名相同
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(@PathVariable long spittleId, Model model) {
model.addAttribute(spittleRepository.findOne(spittleId));
return "spittle";
}
(3)处理表单
少量的数据,可以使用查询参数和路径变量;但传递更多的数据,表单更适合;
@Controller
@RequestMapping("/spitter")
public class SpitterController {
private SpitterRepository spitterRepository;
@Autowired // 构造器注入进来
public SpitterController(SpitterRepository spitterRepository){
this.spitterRepository = spitterRepository;
}
// 它接受一个Spitter对象作为参数。这个对象有firstName、lastName、username和password属性,
// 这些属性将会使用请求中同名的参数进行填充。
@RequestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter) {
spitterRepository.save(spitter);
return "redirect:/spitter/"+spitter.getUsername(); // 重定向
}
}
(4)表单校验:校验入参合法性
方式一:在controller中写代码校验入参,比较初级
方式二:使用Spring对Java校验API
Java校验API定义了多个注解,这些注解可以放到属性上,限制这些属性的值,所有注解都位于javax.validation.constraints包中。
注解 | 描述 |
---|---|
@AssertFalse | 所注解的元素必须是Boolean类型,并且值为false |
@AssertTrue | 所注解的元素必须是Boolean类型,并且值为true |
@DecimalMax | 所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString值 |
@DecimalMin | 所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString值 |
@Digits | 所注解的元素必须是数字,并且它的值必须有指定的位数 |
@Future | 所注解的元素的值必须是一个将来的日期 |
@Max | 所注解的元素必须是数字,并且它的值要小于或等于给定的值 |
@Min | 所注解的元素必须是数字,并且它的值要大于或等于给定的值 |
@NotNull | 所注解的元素的值必须不能为null |
@Null | 所注解的元素的值必须为null |
@Past | 所注解的元素的值必须是一个已过去的日期 |
@Pattern | 所直接的元素的值必须匹配给定的正则表达式 |
@Size | 所注解的元素的值必须是String、集合或数组,并且它的长度要符合给定的范围 |
public class Spitter {
private Long id;
@NotNull
@Size(min=5, max=16) // 非空,5-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;
}
@RequestMapping(value="/register", method=POST)
public String processRegistration(
@Valid Spitter spitter, // 校验 Spitter输入
Errors errors) {
// 如果校验出现错误,可以通过Errors对象进行访问。Errors参数要紧跟在@Valid注解的参数后面,@Valid注解所标注的就是要校验的参数
if (errors.hasErrors()) {
return "registerForm"; // 如果校验出现错误,则重新返回表单
}
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUsername();
}