Spring Boot进阶:原理、实战与面试题分析
上QQ阅读APP看书,第一时间看更新

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的应用场景。