3.SpringBoot学习(三)——Spring Boot WebMVC及其工作原理
1.简介
1.1 概述
The Spring portfolio provides two parallel stacks. One is based on a Servlet API with Spring MVC and Spring Data constructs. The other is a fully reactive stack that takes advantage of Spring WebFlux and Spring Data’s reactive repositories. In both cases, Spring Security has you covered with native support for both stacks. https://spring.io/reactive
Spring产品组合提供了两个并行技术栈。一种基于带有 Spring MVC 和 Spring Data 结构的 Servlet API。另一个是完全响应式技术栈,该栈利用了 Spring WebFlux 和 Spring Data 的响应式存储库。在这两种情况下,Spring Security 都为两个堆栈提供了本机支持。
1.2 特点
- 清晰的角色划分:控制器(controller)、验证器(validator)、命令对象(command obect)、表单对象(form object)、模型对象(model object)、Servlet分发器(DispatcherServlet)、处理器映射(handler mapping)、试图解析器(view resoler)等等。每一个角色都可以由一个专门的对象来实现。
- 强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器validator)的引用。
- 可适配、非侵入:可以根据不同的应用场景,选择合适的控制器子类(simple型、command型、from型、wizard型、multi-action型或者自定义),而不是一个单一控制器(比如Action/ActionForm)继承。
- 可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
- 可定制的绑定(binding)和验证(validation):比如将类型不匹配作为应用级的验证错误,这可以保证错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。
- 可定制的handler mapping和view resolution:Spring提供从最简单的URL映射,到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
- 灵活的model转换:在Springweb框架中,使用基于Map的键/值对来达到轻易的与各种视图技术集成。
- 可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
- 简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(theme)之类的许多功能。他提供在标记方面的最大灵活性。
- JSP表单标签库:在Spring2.0中引入的表单标签库,使用在JSP编写表单更加容易。
- Spring Bean的生命周期可以被限制在当前的HTTp Request或者HTTp Session。准确的说,这并非Spring MVC框架本身特性,而应归属于Spring MVC使用的WebApplicationContext容器。
1.3 对比 WebFlux
2.环境
- JDK 1.8.0_201
- Spring Boot 2.2.0.RELEASE
- 构建工具(apache maven 3.6.3)
- 开发工具(IntelliJ IDEA )
3.代码
3.1 代码结构
3.2 maven 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.3 java代码
User.java
public class User {
/**
* id
*/
private Integer id;
/**
* 姓名
*/
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public User() {
}
}
UserRepository.java
@Repository
public class UserRepository {
// 生成id
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
// 模拟内存数据库
private static final Map<Integer, User> USER_MAP = new HashMap<>();
public List<User> selectAll() {
return new ArrayList<>(USER_MAP.values());
}
public User getUserById(Integer id) {
return USER_MAP.get(id);
}
public User addUser(User user) {
if (Objects.isNull(user.getId())) {
user.setId(ID_GENERATOR.incrementAndGet());
}
USER_MAP.put(user.getId(), user);
return user;
}
public User update(User user) {
USER_MAP.put(user.getId(), user);
return user;
}
public User delete(Integer id) {
return USER_MAP.remove(id);
}
public boolean exist(User user) {
List<String> nameList = USER_MAP.values().stream().map(User::getName).collect(Collectors.toList());
return nameList.contains(user.getName());
}
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public List<User> selectAll() {
return userRepository.selectAll();
}
@Override
public User getUserById(Integer id) {
return userRepository.getUserById(id);
}
@Override
public User addUser(User user) {
return userRepository.addUser(user);
}
@Override
public User update(User user) {
return userRepository.update(user);
}
@Override
public User delete(Integer id) {
return userRepository.delete(id);
}
@Override
public boolean exist(User user) {
return userRepository.exist(user);
}
}
UserController.java
@RestController
@RequestMapping(value = "/user")
public class UserController {
private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
public List<User> list() {
return userService.selectAll();
}
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
public User get(@PathVariable Integer id) {
return userService.getUserById(id);
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<Void> create(@RequestBody User user, UriComponentsBuilder builder) {
if ("duplicated".equals(user.getName())) {
LOGGER.warn("the user already exist");
return new ResponseEntity<>(HttpStatus.ALREADY_REPORTED);
}
userService.addUser(user);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(builder.path("/user/get/{id}").buildAndExpand(user.getId()).toUri());
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
@RequestMapping(value = "/update", method = RequestMethod.PUT)
public User update(@RequestBody User user) {
return userService.update(user);
}
@RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
public User delete(@PathVariable Integer id) {
return userService.delete(id);
}
}
3.4 git 地址
spring-boot/spring-boot-03-webmvc
4.结果
启动 SpringBoot03WebApplication.main 方法,在 spring-boot-03-webmvc.http 访问下列地址,观察输出信息是否符合预期。
### GET /user/list
GET http://localhost:8080/user/list
由于数据保存在内存中,最开始没有数据,所以返回为空。可以调用 /add 添加数据后再查询
### GET /user/get/{id}
GET http://localhost:8080/user/get/1
通过 id 查询同样为空,可以调用 /add 添加数据后再查询
### POST /user/add
POST http://localhost:8080/user/add
Content-Type: application/json
{
"name": "zhangsan"
}
这里响应码为 201,同时响应头中 location 设定为一个新的地址
### PUT /user/update
PUT http://localhost:8080/user/update
Content-Type: application/json
Accept: application/json
{
"id": 1,
"name": "lisi"
}
### DELETE /user/delete/{id}
DELETE http://localhost:8080/user/delete/1
5.源码分析
5.1 Spring WebMvc 运行流程
- 用户发送请求至前端控制器 DispatcherServlet。
- DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
- 处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet,一般使用的是 url 映射器。
- DispatcherServlet 调用 HandlerAdapter 处理器适配器。
- HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller 执行完成返回 ModelAndView。
- HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet。
- DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
- ViewReslover 解析后返回具体 View。
- DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet 返回响应给用户。
5.2 Spring WebMvc 的原理
DispatcherServlet 其实也是一个 HttpServlet,它的类图如下
在传统的 HttpServlet 中,它的生命周期包含 init、service、destroy,在 service 中一般有 doGet、doPost 分别来处理 get、post 请求。DispatcherServlet 即是在 HttpServlet 上面进行的扩展。
SpringMvc 的初始化时序图:
protected void initStrategies(ApplicationContext context) {
// 初始化上传组件,用于文件上传等
initMultipartResolver(context);
// 初始化本地化组件,用于国际化
initLocaleResolver(context);
// 初始化主题组件
initThemeResolver(context);
// 初始化处理器映射器
initHandlerMappings(context);
// 初始化处理器适配器
initHandlerAdapters(context);
// 初始化异常处理器
initHandlerExceptionResolvers(context);
// 初始化请求-视图名称翻译器
initRequestToViewNameTranslator(context);
// 初始化视图处理器
initViewResolvers(context);
// 初始化 FlashMapManager
initFlashMapManager(context);
}
SpringMvc 的运行时序图:
5.3 DispatcherServlet 如何初始化?
在一般的 Spring WebMvc 项目中,通常会在 web.xml 中配置好 DispatcherServlet,如下所示
<servlet>
<!-- 配置DispatcherServlet -->
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定spring mvc配置文件位置 不指定使用默认情况 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-context.xml</param-value>
</init-param>
<!-- 设置启动顺序 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- ServLet 匹配映射 -->
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
那么,在 Spring Boot 项目中,DispatcherServlet 又是如何生效的呢?
其实,在 spring-boot-autoconfigure/META-INF/spring.factories 中有这样一个配置
这个 DispatcherServletAutoConfiguration 即是 DispatcherServlet 的自动装配类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
return dispatcherServlet;
}
}
// ...
}