《Spring 实战》笔记5:构建 Spring Web 应用程序
Spring MVC 基于模型-视图-控制器(Model-View-Controller,MVC)模式实现,它能够帮你构建像Spring框架那样灵活和松耦合的 Web 应用程序。
Spring MVC 起步
Spring 将请求在调度 Servlet、处理器映射(Handler Mappering)、控制器以及视图解析器(View resolver)之间移动,每一个 Spring MVC 中的组件都有特定的目的,并且也没那么复杂。
让我们看一下,请求是如何从客户端发起,经过 Spring MVC 中的组件,最终返回到客户端
跟踪 Spring MVC 的请求
每当用户在 Web 浏览器中点击链接或提交表单的时候,请求就开始工作了。请求是一个十分繁忙的家伙,从离开浏览器开始到获取响应返回,它会经历很多站,在每站都会留下一些信息,同时也会带上一些信息。下图展示了请求使用 Spring MVC 所经历的所有站点。
- 用户向服务器发送请求,请求被 Spring 前端控制 Servelt
DispatcherServlet
捕获; DispatcherServlet
对请求 URL 进行解析,得到请求资源标识符(URI)。然后根据该 URI,调用HandlerMapping
获得该Handler
配置的所有相关的对象(包括Handler
对象以及Handler
对象对应的拦截器),最后以HandlerExecutionChain
对象的形式返回;DispatcherServlet
根据获得的Handler
,选择一个合适的HandlerAdapter
。(附注:如果成功获得HandlerAdapter
后,此时将开始执行拦截器的preHandler(...)
方法)- 提取 Request 中的模型数据,填充
Handler
入参,开始执行Handler(Controller)
。 在填充Handler
的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:HttpMessageConveter
:将请求消息(如 JSON、XML 等数据)转换成一个对象,将对象转换为指定的响应信息数据转换
:对请求消息进行数据转换。如 String 转换成 Integer、Double 等数据根式化
:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等数据验证
:验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中
Handler
执行完成后,向DispatcherServlet
返回一个ModelAndView
对象;- 根据返回的
ModelAndView
,选择一个适合的ViewResolver
(必须是已经注册到 Spring 容器中的ViewResolver
)返回给DispatcherServlet
; ViewResolver
结合Model
和View
,来渲染视图- 将渲染结果返回给客户端。
搭建 Spring MVC
配置 DispatcherServlet
DispatcherServlet
是 Spring MVC 的核心。在这里请求会第一次接触到框架,它要负责将请求路由到其他的组件之中。
按照传统的方式,像 DispatcherServlet
这样的 Servlet 会配置在 web.xml
文件中
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.form</url-pattern>
</servlet-mapping>
Servlet 3 规范和 Spring 3.1 的功能增强,可以使用 Java 将 DispatcherServlet 配置在 Servlet 容器中
public class SpittrWebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() { // 将 DispatcherServlet 映射到 "/"
return new String[] { "/" };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
}
要理解程序是如何工作的,我们可能只需要知道扩展 AbstractAnnotationConfigDispatcherServletInitializer
的任意类都会自动地配置 DispatcherServlet
和 Spring 应用上下文,Spring 的应用上下文会位于应用程序的 Servlet 上下文之中。
AbstractAnnotationConfigDispatcherServletInitializer 剖析
在 Servlet 3.0 环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer
接口的类,如果能发现的话,就会用它来配置 Servlet 容器。
Spring 提供了这个接口的实现,名为 SpringServletContainerInitializer
,这个类反过来又会查找实现 WebApplicationInitializer
的类并将配置的任务交给它们来完成。Spring 3.2 引入了一个便利的 WebApplicationInitializer
基础实现,也就是 AbstractAnnotationConfigDispatcherServletInitializer
。因为我们的 SpittrWebAppInitializer
扩展了 AbstractAnnotationConfigDispatcherServletInitializer
(同时也就实现了 WebApplicationInitializer
),因此当部署到 Servlet 3.0 容器中的时候,容器会自动发现它,并用它来配置 Servlet 上下文。
SpittrWebAppInitializer
重写了三个方法。
- 第一个方法是
getServletMappings()
,它会将一个或多个路径映射到 DispatcherServlet 上。在本例中,它映射的是 "/" ,这表示它会是应用的默认 Servlet。它会处理进入应用的所有请求。
为了理解其他的两个方法,我们首先要理解 DispatcherServlet
和一个 Servlet 监听器(也就是 ContextLoaderListener
)的关系。
两个应用上下文之间的故事
当 DispatcherServlet
启动的时候,它会创建 Spring 应用上下文,并加载配置文件或配置类中所声明的 bean。在上面程序的 getServletConfigClasses()
方法中,我们要求 DispatcherServlet
加载应用上下文时,使用定义在 WebConfig
配置类(使用 Java 配置)中的 bean。
但是在 Spring Web 应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由 ContextLoaderListener
创建的。
我们希望 DispatcherServlet
加载包含 Web 组件的 bean,如控制器、视图解析器以及处理器映射,而 ContextLoaderListener
要加载应用中的其他 bean。这些 bean 通常是驱动应用后端的中间层和数据层组件。
实际上, AbstractAnnotationConfigDispatcherServletInitializer
会同时创建 DispatcherServlet
和 ContextLoaderListener
。getServletConfigClasses()
方法返回的带有 @Configuration
注解的类将会用来定义 DispatcherServlet
应用上下文中的 bean。getRootConfigClasses()
方法返回的带有 @Configuration
注解的类将会用来配置 ContextLoaderListener
创建的应用上下文中的 bean。
在本例中,根配置定义在 RootConfig
中, DispatcherServlet
的配置声明在 WebConfig
中。稍后我们将会看到这两个类的内容。
需要注意的是,通过 AbstractAnnotationConfigDispatcherServletInitializer
来配置 DispatcherServlet
是传统 web.xml
方式的替代方案。
启用 Spring MVC
以前,Spring 是使用 XML 进行配置的,你可以使用 <mvc:annotation-driven>
启用注解驱动的 Spring MVC。
基于 Java 进行配置,我们所能创建的最简单的 Spring MVC 配置就是一个带有 @EnableWebMvc
注解的类:
@Configuration
@EnableWebMvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 要求 DispatcherServlet 将对静态资源的请求转发到 Servlet 容器中默认的 Servlet 上
configurer.enable();
}
}
@Configuration
@ComponentScan(basePackages = {"spittr"},
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
})
public class RootConfig {
}
编写基本的控制器
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET) // 处理对 "/" 的 Get 请求
public String home() {
return "home"; // 视图名为 home
// DispatcherServlet 会要求视图解析器将这个逻辑名称解析为实际的视图。
// 由于配置 InternalResourceViewResolver 的方式,视图名 "home" 将会解析为 "WEB-INF/views/home.jsp" 路径的 JSP。
}
}
@Controller
是一个构造型(stereotype)的注解,它基于 @Component
注解。在这里,它的目的就是辅助实现组件扫描。因为 HomeController 带有 @Controller 注解,因此组件扫描器会自动找到 HomeController,并将其声明为 Spring 应用上下文中的一个 bean。
带有 @RequestMapping
注解的方法,它的 value
属性指定了这个方法所要处理的请求路径,method
属性细化了它所处理的 HTTP 方法。
测试控制器
public class HomeControllerTest {
@Test
public void testHomePage() throws Exception {
HomeController controller = new HomeController();
// 搭建 MockMvc
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(MockMvcRequestBuilders.get("/")) // 对 “/” 执行 GET 请求,
.andExpect(MockMvcResultMatchers.view().name("home"));// 预期得到 home 视图
}
}
定义类级别的请求处理
传递模型数据到视图中
@RequestMapping(method = RequestMethod.GET)
public String spittles(Model model) {
model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE, 20)); // 将 spittle 添加到视图
// 和上一行等效
// model.put("spittleList", spittleRepository.findSpittles(Long.MAX_VALUE, 20));
return "spittles"; // 返回视图名
}
Model
实际上就是一个 Map(也就是key-value对的集合),它会传递给视图,这样数据就能渲染到客户端了。当调用 addAttribute()
方法并且不指定 key
的时候,那么 key
会根据值的对象类型推断确定。在本例中,因为它是一个 List<Spittle>
,因此,键将会推断为 spittleList
。
@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles() {
return spittleRepository.findSpittles(Long.MAX_VALUE, 20));
}
它并没有返回视图名称,也没有显式地设定模型,这个方法返回的是 Spittle 列表。当处理器方法像这样返回对象或集合时,这个值会放到模型中,模型的 key 会根据其类型推断得出(在本例中,也就是 spittleList)。
而逻辑视图的名称将会根据请求路径推断得出。因为这个方法处理针对 "/spittles" 的 GET 请求,因此视图的名称将会是 spittles
(去掉开头的斜线)。
接受请求的输入
Spring MVC 允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:
- 查询参数(Query Parameter)。
- 表单参数(Form Parameter)。
- 路径变量(Path Variable)。
处理查询参数
// 注:原文中所写的这行是错误的,注解那块会提示 Attribute value must be constant
// private static final String MAX_LONG_AS_STRING = Long.toString(Long.MAX_VALUE);
private static final String MAX_LONG_AS_STRING = Long.MAX_VALUE + "";
@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
@RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max,
@RequestParam(value="count", defaultValue="20") int count) {
return spittleRepository.findSpittles(max, count);
}
通过路径参数接受输入
Spring MVC 允许我们在 @RequestMapping
路径中添加占位符。占位符的名称要用大括号(“{”和“}”)括起来。如果对 "/spittles/54321" 发送 GET 请求,那么将会把 "54321" 传递进来,作为 spittleId 的值。
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(@PathVariable("spittleId") long spittleId, Model model) {
model.addAttribute(spittleRepository.findOne(spittleId));
return "spittle";
}
需要注意的是,如果你想要重命名参数时,必须要同时修改占位符的名称,使其互相匹配。
处理表单
编写处理表单的控制器
校验表单
在 Spring MVC 中要使用 Java 校验 API 的话,并不需要什么额外的配置。只要保证在类路径下包含这个 Java API 的实现即可,比如 Hibernate Validator
。
Java 校验 API 所提供的校验注解
@AssertFalse
所注解的元素必须是 Boolean 类型,并且值为 false@AssertTrue
所注解的元素必须是 Boolean 类型,并且值为 true@DecimalMax
所注解的元素必须是数字,并且它的值要小于或等于给定的 BigDecimalString 值@DecimalMin
所注解的元素必须是数字,并且它的值要大于或等于给定的 BigDecimalString 值@Digits
所注解的元素必须是数字,并且它的值必须有指定的位数@Future
所注解的元素的值必须是一个将来的日期@Max
所注解的元素必须是数字,并且它的值要小于或等于给定的值@Min
所注解的元素必须是数字,并且它的值要大于或等于给定的值@NotNul
l 所注解元素的值必须不能为 null@Null
所注解元素的值必须为 null@Past
所注解的元素的值必须是一个已过去的日期@Pattern
所注解的元素的值必须匹配给定的正则表达式@Size
所注解的元素的值必须是 String 、集合或数组,并且它的长度要符合给定的范围
public class Spitter {
private Long id;
@NotNull
@Size(min=5, max=16)
private String username; // 非空,5 到 16 个字符
@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, Errors errors) {
if (errors.hasErrors()) {
return "registerForm";
}
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUsername();
}
Errors
参数要紧跟在带有 @Valid
注解的参数后面,@Valid
注解所标注的就是要检验的参数
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Spring 实战》笔记5:构建 Spring Web 应用程序
文章字数:3k
本文作者:Bin
发布时间:2019-07-13, 16:35:24
最后更新:2019-08-06, 20:24:32
原始链接:http://coolview.github.io/2019/07/13/Spring/%E3%80%8ASpring%20%E5%AE%9E%E6%88%98%E3%80%8B%E7%AC%94%E8%AE%B05%EF%BC%9A%E6%9E%84%E5%BB%BA%20Spring%20Web%20%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。