使用Spring MVC构建Web应用程序

3.1 Java Web应用程序架构#

3.1.2 Model 2或MVC架构#

典型的Model 2架构(即MVC):

model 2

Model 2架构明确划分了模型、视图与控制器之间的职责:

  • 模型:表示要用于生成视图的数据。
  • 视图:使用模型来渲染屏幕。
  • 控制器:控制流并获取浏览器提出的请求,填充模型以及重定向到视图,示例包括上图中的Servlet1和Servlet2。

3.1.3 Model 2前端控制器架构#

在基本版本的Model 2架构中,浏览器提出的请求直接由不同的servlet(或控制器)处理。

在新的Model 2前端控制器(Front Controller)架构中,所有请求注入一个控制器中,它称为前端控制器。(在前端控制器中可以统一处理一些授权认证等):

model 2 new

通常,前端控制器负责的一些工作如下:

  • 决定由哪个控制器执行请求。
  • 决定渲染哪个视图。
  • 提供资源来添加更多通用功能。
  • Spring MVC使用配有前端控制器的MVC模式。前端控制器称为DispatcherServlet

3.2 基本流#

创建Spring MVC控制器

@Controller
public class BasicController {
@RequestMapping(value = "/welcome")
@ResponseBody
public String welcome() {
return "Welcome to Spring MVC";
}
}

@ResponseBody 在这个特定的上下文中,welcome方法返回的文本会作为响应内容发送给浏览器。

@Controller
public class BasicViewController {
@RequestMapping(value = "/welcome-view")
public String welcome() {
return "welcome";
}
}

此方法上没有使用@RequestBody注解。因此,Spring MVC会尝试将返回的字符串welcome与视图进行匹配。

3.2.7 流6——在上一个流中添加验证功能#

Spring MVC全面集成了Bean Validation API。JSR 303和JSR 349为Bean Validation API(分别为版本1.0和1.1)定义了规范,Hibernate Validator充当引用实现。

下面是在Controller中增加Bean校验的示例代码:

Step 1. 添加依赖

首先将Hibernate Validator添加到项目pom.xml中:

pom.xml
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.2.Final</version>
</dependency>

Step 2. 使用

src/main/java/com/alanwei/spb/demo/controllers/BeanValidController.java
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Controller
@RequestMapping("/bean-valid")
public class BeanValidController {
@RequestMapping(value = "/test", method = RequestMethod.POST)
@ResponseBody
public String exec(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
String errors = result.getFieldErrors()
.stream()
.map(error -> String.format("%s: %s", error.getField(), error.getDefaultMessage()))
.collect(Collectors.joining("\n"));
return errors;
}
return "验证通过";
}
@Getter
@Setter
public static class User {
@Size(min = 6, max = 10, message = "长度必须位于6-10")
private String userName;
@NotNull(message = "密码不能为空")
private String password;
@NotNull(message = "两次密码不匹配")
private String password2;
@Min(value = 18, message = "最小18岁")
private int age;
@AssertTrue(message = "密码不匹配")
private boolean isValid() {
return Objects.equals(this.password, this.password2);
}
}
}

Step 3. 测试

curl --request POST 'http://localhost:8080/bean-valid/test' \
--header 'Content-Type: application/json' \
--data-raw '{
"userName": "AlanWei",
"password": "123456",
"password2": "123456",
"age": 18
}'

使用Bean Validation可以执行的其他验证功能如下。

  • @NotNull:不得为null。
  • @Size(min =5, max = 50):最多50个字符,最少5个字符。
  • @Past:应为过去日期。
  • @Future:应为将来日期。
  • @Pattern:应与提供的正则表达式相匹配。
  • @Max:字段的最大值。
  • @Min:字段的最小值。

使用@AssertTrue注解可以实现更加复杂的自定义验证.

info

Hibernate Validator 也可以在方法里手动执行校验, 参考Hibernate Validator 7.0.1.Final - Jakarta Bean Validation Reference Implementation: Reference Guide:

import org.junit.BeforeClass;
import org.junit.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import static org.junit.Assert.assertEquals;
public class HibernateValidatorTest {
private static Validator validator;
@BeforeClass
public static void setUpValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void validate() {
BeanValidController.User user = new BeanValidController.User();
user.setUserName("AlanWei");
user.setPassword("123456");
user.setPassword2("123456");
user.setAge(10);
Set<ConstraintViolation<BeanValidController.User>> violations = validator.validate(user);
assertEquals(1, violations.size()); // => 年龄校验失败
}
}

3.3 Spring MVC概述#

3.3.2 工作机制#

Spring MVC框架中的关键组件如下图所示:

MVC框架工作机制

以下面返回视图的代码为例,URL为http://localhost:8080/welcome-model-view:

@Controller
public class BasicModelViewController {
@RequestMapping(value = "/welcome-model-view")
public ModelAndView welcome(ModelMap model) {
model.put("name", "XYZ");
return new ModelAndView("welcome-model-view", model);
}
}
  1. 浏览器向特定URL提出请求。DispatcherServlet是处理所有请求的前端控制器,因此,它收到该请求。
  2. DispatcherServlet将分析URI(此例中为/welcome-model-view),并需要确定处理该URI所需的相应控制器。为帮助找到正确的控制器,它与处理程序(即Controller)映射进行交互。
  3. 处理程序映射返回特定处理程序方法(此例中为BasicModelViewController中的welcome方法)来处理请求。
  4. DispatcherServlet调用特定处理程序方法(public ModelAndView welcome(ModelMap model))。
  5. 处理程序方法返回模型和视图。本例中将返回ModelAndView对象。
  6. DispatcherServlet有逻辑视图名称(来自ModelAndView。本例中为welcome-model-view)。它因为需要了解如何确定物理视图名称,所以检查是否有任何可用的视图解析器,结果找到配置的视图解析器(org.springframework.web.servlet.view.InternalResourceViewResolver),并调用此视图解析器,提供逻辑视图名称(本例中为welcome-model-view)作为输入。
  7. 视图解析器执行逻辑,将逻辑视图名称映射为物理视图名称。本例中welcome-model-view被转换为/WEB-INF/views/welcome-model-view.jsp
  8. DispatcherServlet执行视图,还使模型对视图可用。
  9. 视图返回要送回给DispatcherServlet的内容。
  10. DispatcherServlet将响应返回给浏览器。

3.4 Spring MVC背后的重要概念#

3.4.1 RequestMapping#

RequestMapping 用于将URI映射为控制器或控制器方法, 可以在类或方法级别执行此操作:

@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/show-page" , method =
RequestMethod.GET)
public String showPage() {
/* 一些代码 */
}
}

使用了RequestMapping注解的方法所支持的入参类型:

参数类型/注解用途
java.util.Map / org.springframework. ui.Model / org.springframework.ui.ModelMap充当模型(MVC),将作为传送给视图的值的容器
命令或表单对象(自定义类, 比如上述使用的 User 类)用于将请求参数绑定到bean并支持验证
org.springframework.validation.Errors / org.springframework.validation.BindingResult命令或表单对象的验证结果(表单对象应为前一个方法参数)
@PreDestroy可以对任何Spring bean使用@PreDestroy注解,来提供一个在销毁前调用的方法, 只会在从容器中删除bean时调用此方法。此方法可用于释放任何由bean保留的资源
@RequestParam此注解用于访问特定HTTP请求参数
@RequestHeader此注解用于访问特定HTTP请求标头
@SessionAttribute此注解用于访问HTTP会话中的属性
@RequestAttribute此注解用于访问特定HTTP请求属性
@PathVariable此注解用于访问URI模板/owner/{ownerId}中的变量,我们将在讨论微服务时详细介绍此注解

使用了RequestMapping注解的方法所支持的返回类型:

返回类型结果
ModelAndView对象包括对模型和视图名称的引用
Model仅返回模型, 使用DefaultRequestToViewNameTranslator确定视图名称
Map用于公开模型的简单映射
View具有隐式定义模型的视图
String引用视图名称

使用RequestMapping注解的方法支持多种返回类型,不管是那种返回类型都要解决两个问题:

  • 什么是视图
  • 视图需要哪个模型

如果视图未显式定义为返回类型的一部分,则进行隐式定义:

  • 隐式充实模型: 如果模型是返回类型的一部分,那么它由命令对象(包括命令对象的验证结果)来充实。此外,还会在模型中添加使用@ModelAttribute注解的方法的调用结果。
  • 隐式确定视图: 如果返回类型中不包括视图名称,将使用DefaultRequestToViewNameTranslator确定该名称。默认情况下,DefaultRequestToViewNameTranslator会删除URI中的前导和尾部反斜杠以及文件扩展名;例如,display.html将变成display

3.4.2 视图解析#

Spring MVC提供了多种视图选项:

  • 集成JSP、Freemarker。
  • 多种视图解析策略,其中一些策略如下:
    • XmlViewResolver:视图解析基于外部XML配置。
    • ResourceBundleViewResolver:视图解析基于属性文件。
    • UrlBasedViewResolver:将逻辑视图名称直接映射为URL。
    • ContentNegotiatingViewResolver:根据Accept请求标头委任给其他视图解析器。
  • 支持用明确定义的优先次序链接视图解析器。
  • 使用内容协商(Content Negotiation)直接生成XML、JSON和Atom。

3.4.3 处理程序(Controller)映射和拦截器#

参考Spring MVC HandlerInterceptor 简介

Spring MVC 高级功能#

3.5.1 异常处理#

Controller Advice可用于实现所有控制器通用的异常处理:

@ControllerAdvice
public class ExceptionController {
private Log logger =
LogFactory.getLog(ExceptionController.class);
@ExceptionHandler(value = Exception.class)
public ModelAndView handleException
(HttpServletRequest request, Exception ex) {
logger.error("Request " + request.getRequestURL()
+ " Threw an Exception", ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", request.getRequestURL());
mav.setViewName("common/spring-mvc-error");
return mav;
}
}

上述代码返回的是个视图, 或者直接返回原始文本内容:

@ControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public String handleException(HttpServletRequest request, Exception ex) {
return request.getRequestURL() + " => " + ex.getMessage() + "\n"; // 这个字符串会直接输出到响应内容
}
}

通过实现使用@ExceptionHandler(value = Exception.class)注解可以指定处理特定异常.