Spring Boot:RESTful 控制器中的多层@Services 用于许多入站请求

Posted

技术标签:

【中文标题】Spring Boot:RESTful 控制器中的多层@Services 用于许多入站请求【英文标题】:Spring Boot : Multi-layered @Services in RESTful controllers for many inbound requests 【发布时间】:2020-06-03 01:13:40 【问题描述】:

不确定这是代码审查还是有关 Spring Boot 如何操作的问题,涉及具有许多入站请求的 @Service 组件。如果这是一个问题,我当然可以在 StackExchange > 代码审查中重新发布?我们在 Spring Boot 中基本上有以下模式:

Spring Boot 应用程序 处理 List<DocumentMetadata> 对象的 RESTful 控制器 RESTful 控制器使用数据管理@Service 来处理ocumentMetadata 对象的获取和排序 数据管理@Service 使用自定义排序@Service 对这些documentMetadata 对象进行排序

叙述:本质上,一个 RESTful 端点将返回一个 List<DocumentMetadata> 对象(如果你愿意,可以列出对象列表)作为 ResponseEntity。自定义排序实现为@Service,并带有sort() 的方法。我的理解是 Spring Boot 将创建自定义排序 @Service 实用程序的 "singleton" 实例。我在 Spring Boot @Service 和生命周期上搜索了一些问题,并找到了以下内容。这篇文章描述了一些导致我的问题/评论的用法,Should service layer classes be singletons?,尤其是这条评论“此外,Spring 单例中的可变状态绝对没问题,你只需要知道可以对共享的操作执行哪些操作状态 - skaffman”。我还阅读了这篇帖子,Can Spring Boot application handle multiple requests simultaneously?,这似乎表明我们没问题,因为我们正在创建、排序和返回的对象是在 @GetMapping 内创建的,并传递到服务组件层,即它不是共享的。

我的问题:

这甚至是设计 RESTful 堆栈的正确方法吗?特别是我们需要的实用程序类...应该将它们作为@Service 或在数据管理@Service 层中使用的POJO 类来完成。 在 10-100 个请求同时到达 RESTful 端点的多请求情况下,自定义排序实用程序 @Servicesort() 方法会发生什么情况? sort() 方法是否按顺序服务每个请求?还是在不同的线程中并行? 是否每个请求都使用相同的排序方法,即自定义排序实用程序@Service 是一个“单例”那么如何使用sort() 方法? 如果 sort() 方法确实是单一的,如果两个用户同时点击该方法,一个用户是否可能最终得到另一个用户的排序列表?还是 Spring Boot 只是按顺序或在自己的线程中处理入站请求并避免任何混合。

代码 sn-ps :为简洁起见,我删除了大部分代码。

@RestController
@RequestMapping(value = "/search")
@CrossOrigin(origins = "*")
@Slf4j
public class SearchController 

    // @Service for Data handling...
    @Autowired
    private DataManagementService dataManagementService;

    @GetMapping(value="/viewable-documents")
    public SearchResponse getExternallyViewableDocuments(@RequestParam(required = false) Map<String, String> queryParams) 
        logger.debug("getViewableDocuments is called with ", queryParams);

        SearchResponse searchResponse = new SearchResponse();

        // ENDPOINT : Calls the Data Management handling @Service...
        SearchResponse response = dataManagementService.getDocuments(queryParams);

    


@Service
public class DataManagementServiceImpl implements DataManagementService 

    // CUSTOM SORT UTILITY : Autowired with setter...
    private DocumentSortUtility documentSortUtility;

    @Autowired
    public void setDocumentSortUtility(DocumentSortUtility setValue) 
        this.documentSortUtility = setValue;
    


    @Override
    public SearchResponse getDocuments(Map<String, String> request) 

        if (request.get("sort") != null && request.get("sort").equals("true")) 

            // SORT : Call the custom sort...
            return serviceImpl.populateResponse(this.documentSortUtility.sort(response));

         else 
            return serviceImpl.populateResponse(response);
        
    


@Service
public class DocumentSortUtility 

    public List<DocumentMetadata> sort(List<DocumentMetadata> documentMetadataList) 
        log.debug("Un Sorted:"+documentMetadataList);

        // Do custom sort of documentMetadataList and place in retList...

        log.debug("Sorted:"+retList);   
        return retList;
    

我已在代码中确认@Service 层中没有维护/管理“共享”可变对象。唯一被修改和/或创建的对象是在@Service 层的方法中完成的。

提前谢谢...

【问题讨论】:

【参考方案1】:

如我所见,您的困惑基于 Spring 的 Singleton 范围以及当 @Service 类为 Singleton 时 Spring 如何处理多个请求。

Spring 将beans 创建并绑定为Singleton 对象,除非您使用@Scope 注释或与范围相关的注释定义特定范围,例如@SessionScope。您可能已经知道这一点。

如果您对多个线程上的 Singleton 对象感到困惑,这个 answer 提供了很好的解释。那么,关于你的问题;

这甚至是设计 RESTful 堆栈的正确方法吗?具体来说 我们需要的实用程序类,应该作为@Service 还是作为 POJO 在数据管理中使用的类 @Service 层?

是和不是。如果您的 @Service 类是 singleton(必须是)并且 不可变(无状态),那么这是正确的方法。这意味着,您的 @Service 类不应有任何可修改的字段。否则,您的@Service 会导致不可预知的结果。

如果在此上下文中使用了POJO,则每次启动新线程时对象实例化都会初始化新对象,这将我们带到您的第二个问题;

在 10-100 个请求命中的多请求情况下 一次 RESTful 端点,自定义排序实用程序会发生什么 @Servicesort() 方法? sort() 方法是否为每个服务 按顺序请求?还是在不同的线程中并行?

当 10-100 或数千个或请求到达端点时,如果您使用了 POJO,它还会创建数千个额外的对象,这些对象会不必要地污染堆,因为我们可以使用一个 singleton 对象使用 @Service 注释。因为,singleton 对象是 immutable(不会通过其方法调用保留任何更改)并在多个线程之间共享,能够同时执行而不会对结果产生任何副作用。

如果sort() 发生在内存中并返回排序后的结果,它会在线程的执行中独立执行。

是否每个请求都使用相同的排序方法,即自定义排序 实用程序@Service 是一个 "singleton" 那么sort() 方法如何 使用?

Singleton 并不意味着它的功能不能被共享,除非它们特别是synchronized。在你的情况下,我相信这个sort() 方法不是synchronized。因此,在每个线程上分别执行sort() 方法是没有问题的(没有差异,因为我们的singleton 对象是无状态/不可变的)。

如果 sort() 方法是真正的单一方法,那么 1 个用户可能最终会 如果两个用户都点击了该方法,则与另一个用户的排序 List 同时地?还是 Spring Boot 只是简单地处理入站 按顺序或在它们自己的线程中请求,并避免任何混合。

你知道,Spring boot 是基于 Java EE 的。所以,基本上它使用一个javax.servlet.ServletContext 类实例来处理这个requestresponse 流,并且Java EE 中的许多其他类都参与了这个过程,由于Spring 的巧妙设计,我们看不到。但是流程是一样的。简而言之,每个request 都有单独的thread 绑定到它,并且那些request-threads 永远不会混淆。 thread 涉及整个过程,直到将响应分派给客户端。

你有没有注意到,除了特殊情况,我们不会手动创建线程?那是因为,Java EE 生态系统代表我们管理这些线程。

所以,对您的问题的回答是,不,sort() 不会执行一次,并且永远不会一个请求的结果最终出现在另一个请求的响应对象中,无论有多少千用户同时发出请求。这个answer 还提供了关于这些请求 - 响应机制如何在下面工作的精彩解释。

【讨论】:

你能please vote to undelete and reopen this question

以上是关于Spring Boot:RESTful 控制器中的多层@Services 用于许多入站请求的主要内容,如果未能解决你的问题,请参考以下文章

Spring+Spring Boot+Mybatis框架注解解析

将 GZIP 压缩与 Spring Boot/MVC/JavaConfig 与 RESTful 结合使用

如何在 Spring Boot 中同时公开 SOAP Web 服务和 RESTful API?

使用 Spring Boot 的 RESTful API 中的循环依赖

Spring Boot系列——Restful CURD注意事项

Spring Boot 2 + Spring Security 5 + JWT 的 Restful简易教程!