4.1.4 Spring WebMVC案例分析
本节将通过一个案例来演示如何通过Spring WebMVC构建RESTful风格的Web API。首先,我们在Maven的pom文件中添加如代码清单4-36所示的依赖包。
代码清单4-36 Spring WebMVC依赖包定义代码
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
在本案例里,我们采用MongoDB来实现数据存储,所以这里还引入了一个spring-boot-starter-data-mongodb依赖包。
现在,让我们来定义业务领域对象。在本案例中,我们设计一个User对象,该对象可以包含该用户的好友信息以及该用户所阅读的文章信息。User对象的字段定义如代码清单4-37所示,这里的@Document、@Id以及@Field注解都来自MongoDB。
代码清单4-37 User类定义代码
@Document("users") public class User { @Id private String id; @Field("name") private String name; @Field("age") private Integer age; @Field("createAt") private Date createdAt; @Field("nationality") private String nationality; @Field("friendsIds") private List<String> friendsIds; @Field("articlesIds") private List<String> articlesIds; //省略getter/setter方法 }
注意,在这个User对象中存在两个数组friendsIds和articlesIds,分别用于保存该用户的好友和所阅读文章的编号,其中好友信息实际上就是User对象,而文章信息则涉及另一个领域对象Article。
有了领域对象之后,我们就可以设计并实现数据访问层组件。这里就需要引入Spring家族中的另一个常用框架Spring Data。Spring Data是Spring家族中专门用于实现数据访问的开源框架,其核心原理是支持对所有存储媒介进行资源配置从而实现数据访问。我们知道,数据访问需要完成领域对象与存储数据之间的映射,并对外提供访问入口。Spring Data基于Repository架构模式抽象出了一套统一的数据访问方式。Spring Data的基本使用过程非常简单,我们在本书第9章中还会对Spring Data详细讲解。
基于Spring Data,我们可以定义一个UserRepository,如代码清单4-38所示。
代码清单4-38 UserRepository接口定义代码
public interface UserRepository extends PagingAndSortingRepository<User, String> { User findUserById(String id); }
可以看到UserRepository扩展了PagingAndSortingRepository接口,而后者针对User对象提供了一组CRUD以及分页和排序方法,开发人员可以直接使用这些方法完成对数据的操作。
注意,这里我们还定义了一个findUserById()方法,该方法实际上使用了Spring Data提供的方法名衍生查询机制。使用方法名衍生查询是最方便的一种自定义查询方式,开发人员唯一要做的就是在Repository接口中定义一个符合查询语义的方法。例如,如果我们希望通过ID来查询User对象,那么只需要提供findUserById()这一符合常规语义的方法定义即可。
类似地,ArticleRepository的定义也非常简单,如代码清单4-39所示。
代码清单4-39 ArticleRepository接口定义代码
public interface ArticleRepository extends PagingAndSortingRepository<Article, String> { Article findArticleById(String id); }
基于数据访问层组件,Service层组件的实现也并不复杂,基本就是对UserRepository和ArticleRepository中的接口方法的合理利用。UserService和ArticleService的实现过程如代码清单4-40所示。
代码清单4-40 UserService和ArticleService类实现代码
@Service public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User findUserById(String id) { return userRepository.findUserById(id); } public List<User> findByIds(List<String> ids) { List<User> list = new ArrayList<>(); ids.forEach(id -> list.add(userRepository.findUserById(id))); return list; } public List<User> findAllUsers() { return (List<User>) userRepository.findAll(); } } @Service public class ArticleService { private final ArticleRepository articleRepository; @Autowired public ArticleService(ArticleRepository articleRepository) { this.articleRepository = articleRepository; } public List<Article> findAllUserArticles(List<String> articleIds) { List<Article> articles = new ArrayList<>(); articleIds.forEach(id -> articles.add(articleRepository.findArticleById(id))); return articles; } }
最后,我们来根据用户ID获取其对应的阅读文章信息。为此,我们实现如代码清单4-41所示的ArticleController。
代码清单4-41 ArticleController类实现代码
@RestController @RequestMapping("/articles") public class ArticleController { private ArticleService articleService; private UserService userService; @Autowired public ArticleController(ArticleService articleService, UserService userService) { this.articleService = articleService; this.userService = userService; } @GetMapping(value = "/{userId}") public List<Article> getArticlesByUserId(@PathVariable String userId){ List<Article> articles = new ArrayList<Article>(); User user = userService.findUserById(userId); if(user != null) { articles = articleService.findAllUserArticles(user.getArticlesIds()); } return articles; } }
ArticleController的实现过程充分展现了使用Spring Boot开发RESTful风格Web API的简便性。完整的案例代码可以参考:https://github.com/tianminzheng/spring-boot-examples/tree/main/SpringWebMvcExample。
在本章4.3节中讨论Spring GraphQL框架时,我们还会使用这个案例并对其重构,从而满足GraphQL的应用场景。