java - 如果缓存了 MockHttpServletRequestBuilder,则 MockMvc.perform 会抛出带有并行流的 ConcurrentModificationException

标签 java spring spring-security spring-test

我正在编写一些 spring 集成测试来测试我的应用程序中的 Spring Security。我使用 RequestPostProcessor 创建具有不同权限的测试用户。我还缓存它们以便在所有测试中重用。见下面的代码:

public final class Users {
    public static final RequestPostProcessor
        ANONYMOUS = anonymous();

    public static final RequestPostProcessor
        PERMISSIONS_READ = buildUser(Permissions.PERMISSIONS_READ);
    public static final RequestPostProcessor
        PERMISSIONS_WRITE = buildUser(Permissions.PERMISSIONS_WRITE);
    public static final RequestPostProcessor
        PERMISSIONS_DELETE = buildUser(Permissions.PERMISSIONS_DELETE);

    public static final RequestPostProcessor
        ROLES_READ = buildUser(Permissions.ROLES_READ);
    public static final RequestPostProcessor
        ROLES_WRITE = buildUser(Permissions.ROLES_WRITE);
    public static final RequestPostProcessor
        ROLES_DELETE = buildUser(Permissions.ROLES_DELETE);

    public static final RequestPostProcessor
        USERS_READ = buildUser(Permissions.USERS_READ);
    public static final RequestPostProcessor
        USERS_WRITE = buildUser(Permissions.USERS_WRITE);
    public static final RequestPostProcessor
        USERS_DELETE = buildUser(Permissions.USERS_DELETE);


    private Users() {}

    private static RequestPostProcessor buildUser(Permissions permission) {
        return buildUser(permission.toString(), permission.toString());
    }

    private static RequestPostProcessor buildUser(String name, String... authorities) {
        return user(name).authorities(SecurityUtils.authoritiesFromStrings(authorities));
    }
}

当我在测试中使用它们时,我得到了一个 ConcurrentModificationException

用法:

....................
@Autowired private WebApplicationContext context;
@Autowired private Filter springSecurityFilterChain;
MockMvc mvc = MockMvcBuilders
        .webAppContextSetup(context)
        .addFilters(springSecurityFilterChain)
        .build();
....................
MockHttpServletRequestBuilder req = get("some-url");
mvc.perform(req.with(Users.ANONYMOUS))
        .andExpect(status().isFound())
        .andExpect(header().string("Location", "login-url"));
Stream.of(Users.PERMISSIONS_WRITE, Users.PERMISSIONS_DELETE,
        Users.ROLES_WRITE, Users.ROLES_DELETE,
        Users.USERS_WRITE, Users.USERS_DELETE)
        .parallel()
        .forEach(Unchecked.consumer(user -> mvc.perform(req.with(user)) //Exception is here and caused by .with(user)
                .andExpect(status().isForbidden())));
....................

一个异常(exception):

....................
Caused by: java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder.postProcessRequest(MockHttpServletRequestBuilder.java:754)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:145)
at com.ipan.fin.man.integrational.PermissionsTest.lambda$checkReadSecurity$5(PermissionsTest.java:162)//line with my code
....................

org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder 中抛出异常,因为我正在缓存它 (MockHttpServletRequestBuilder req) 并在多个流中使用。因此,以防万一,当一个流遍历 postProcessors

@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
    for (RequestPostProcessor postProcessor : this.postProcessors) { //here
        request = postProcessor.postProcessRequest(request);
        if (request == null) {
            throw new IllegalStateException(
                    "Post-processor [" + postProcessor.getClass().getName() + "] returned null");
        }
    }
    return request;
}

第二个将新的 postProcessor 添加到 postProcessors

@Override
public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) {
    Assert.notNull(postProcessor, "postProcessor is required");
    this.postProcessors.add(postProcessor); //here
    return this;
}

ConcurrentModificationException 将被抛出。

正如我在上面的代码中所理解的那样,由于两个原因,不允许缓存 MockHttpServletRequestBuilder:

  1. 不是线程安全的
  2. MockHttpServletRequestBuilder.with 调用添加了新的 RequestPostProcessor,而不是像我预期的那样替换旧的。

我说得对吗?

附言使用顺序流,测试工作正常,看起来 MockHttpServletRequestBuilder.with 的调用替换了旧的 RequestPostProcessor 对象,因为我总是得到正确的测试结果(来自服务器的响应状态)

最佳答案

是的,您的分析是正确的:MockHttpServletRequestBuilder 并非设计为那样同时使用。

无论如何,创建 MockHttpServletRequest 实际上并没有任何明显的开销。

因此,我建议您每次需要时都创建一个新请求,使用并行流并不能真正为此类测试带来太多好处(如果有的话)。

总而言之,尽量不要过度设计您的测试。 ;-)

问候,

Sam(Spring TestContext Framework 的作者)

关于java - 如果缓存了 MockHttpServletRequestBuilder,则 MockMvc.perform 会抛出带有并行流的 ConcurrentModificationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47458232/

相关文章:

java - JButton 更改文本字段的文本颜色

java - css 和 js 不适用于我的页面?

java - 如何测试 spring-security-oauth2 资源服务器安全性?

Spring OAuth @EnableResourceServer 阻止来自 OAuth 服务器的登录页面

java - "Error starting ApplicationContext"执行spring应用时

java - Spring安全permitall()适用于@RestController但不适用于@Controller

java - 如果多个工作表包含 RichText,则将它们合并到一个工作簿中时,RichText 单元格从工作表 2 开始显示为空白

java - 在实现检测输入是否为整数的系统时遇到困难

java - Swing 中的窗口最小化

java - HttpHeaders getFirst() 不区分大小写