使用Spring MVC构建Web应用程序
3.1 Java Web应用程序架构
3.1.2 Model 2或MVC架构
典型的Model 2架构(即MVC):
Model 2架构明确划分了模型、视图与控制器之间的职责:
- 模型:表示要用于生成视图的数据。
- 视图:使用模型来渲染屏幕。
- 控制器:控制流并获取浏览器提出的请求,填充模型以及重定向到视图,示例包括上图中的Servlet1和Servlet2。
3.1.3 Model 2前端控制器架构
在基本版本的Model 2架构中,浏览器提出的请求直接由不同的servlet(或控制器)处理。
在新的Model 2前端控制器(Front Controller)架构中,所有请求注入一个控制器中,它称为前端控制器。(在前端控制器中可以统一处理一些授权认证等):
通常,前端控制器负责的一些工作如下:
- 决定由哪个控制器执行请求。
- 决定渲染哪个视图。
- 提供资源来添加更多通用功能。
- 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中:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.2.Final</version>
</dependency>
Step 2. 使用
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
注解可以实现更加复杂的自定义验证.
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框架中的关键组件如下图所示:
以下面返回视图的代码为例,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);
}
}
- 浏览器向特定URL提出请求。
DispatcherServlet
是处理所有请求的前端控制器,因此,它收到该请求。 - DispatcherServlet将分析URI(此例中为/welcome-model-view),并需要确定处理该URI所需的相应控制器。为帮助找到正确的控制器,它与处理程序(即Controller)映射进行交互。
- 处理程序映射返回特定处理程序方法(此例中为
BasicModelViewController
中的welcome
方法)来处理请求。 - DispatcherServlet调用特定处理程序方法(
public ModelAndView welcome(ModelMap model)
)。 - 处理程序方法返回模型和视图。本例中将返回
ModelAndView
对象。 - DispatcherServlet有逻辑视图名称(来自
ModelAndView
。本例中为welcome-model-view
)。它因为需要了解如何确定物理视图名称,所以检查是否有任何可用的视图解析器,结果找到配置的视图解析器(org.springframework.web.servlet.view.InternalResourceViewResolver
),并调用此视图解析器,提供逻辑视图名称(本例中为welcome-model-view
)作为输入。 - 视图解析器执行逻辑,将逻辑视图名称映射为物理视图名称。本例中
welcome-model-view
被转换为/WEB-INF/views/welcome-model-view.jsp
。 - DispatcherServlet执行视图,还使模型对视图可用。
- 视图返回要送回给DispatcherServlet的内容。
- 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)
注解可以指定处理特定异常.