You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于JerseyExtension与JUnit5的测试:请求间会话无法保持的解决问询

如何在JerseyExtension(JUnit5)测试中保持会话一致性?

这个问题的核心在于Jersey客户端默认不会自动管理会话Cookie,所以每次请求都会被服务器视为新的会话,生成新的Session ID。要解决这个问题,只需要让客户端启用Cookie存储和自动传递功能,这样后续请求就能带上之前的JSESSIONID Cookie,服务器就能识别同一个会话了。

解决步骤

1. 配置客户端启用Cookie管理

修改configureJerseyClient方法,在客户端配置中添加Cookie处理逻辑,让客户端自动保存和发送Cookie:

import java.net.CookieManager;
import javax.ws.rs.client.ClientProperties;

// ... 其他代码不变

protected ClientConfig configureJerseyClient(ExtensionContext extensionContext, ClientConfig clientConfig) {
    assertNotNull(extensionContext);
    assertNotNull(clientConfig);
    // 启用Cookie管理器,自动维护会话Cookie
    clientConfig.property(ClientProperties.COOKIE_HANDLER, new CookieManager());
    return clientConfig;
}

2. 在测试中复用请求构建器(可选,但更可靠)

在测试方法中,使用同一个Invocation.Builder发起多次请求,它会自动维护Cookie上下文,确保后续请求能带上之前的会话标识:

import javax.ws.rs.client.Invocation;

// ... 其他代码不变

@Test
public void testSessionSet(WebTarget target) {
    // 创建一个请求构建器,后续请求复用它以保持Cookie上下文
    Invocation.Builder requestBuilder = target.path("session").request(MediaType.APPLICATION_JSON_TYPE);
    
    // 执行PUT请求设置会话属性
    Response response0 = requestBuilder.put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
    System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
    response0.close();
    
    // 同一个构建器执行GET请求,自动携带JSESSIONID Cookie
    Response response1 = requestBuilder.get();
    System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
    response1.close();
}

为什么这样有效?

当服务器第一次返回JSESSIONID Cookie时,启用了Cookie管理的客户端会自动保存这个Cookie。后续请求时,客户端会把这个Cookie添加到请求头中,服务器通过JSESSIONID就能找到之前创建的HttpSession,从而获取到之前设置的name属性。

修改后,你应该会看到PUT和GET请求的Session ID一致,并且GET请求能正确返回foo值:

PUT: status=200 response=123456789: Set name attribute called
GET: status=200 response=123456789: foo

完整修改后的代码

package com.test.jerseysession;
import com.github.hanleyt.JerseyExtension;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.CookieManager;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class JerseyTestWithGrizzly {
    private final static TestContainerFactory testContainerFactory;
    private final ServletContainer servletContainer;
    private final ResourceConfig resourceConfig;
    private final DeploymentContext deploymentContext;

    static {
        testContainerFactory = new GrizzlyWebTestContainerFactory();
    }

    @RegisterExtension
    @SuppressWarnings("unused")
    JerseyExtension jerseyExtension = new JerseyExtension(
            this::getTestContainerFactory,
            this::configureDeploymentContext,
            this::configureJerseyClient);

    public JerseyTestWithGrizzly() {
        this.resourceConfig = new ResourceConfig()
                .packages("com.test.jerseysession")
                .register(getClass());
        this.servletContainer = new ServletContainer(resourceConfig);
        this.deploymentContext = ServletDeploymentContext.builder(resourceConfig)
                .servlet(servletContainer)
                .servletPath("api")
                .build();
    }

    @Path("session")
    public static class SessionResource {
        @GET
        public String get(@Context HttpServletRequest request) {
            HttpSession session = request.getSession(true);
            Object obj = session.getAttribute("name");
            return session.getId() + ": " + obj;
        }

        @PUT
        public String put(@Context HttpServletRequest request) {
            HttpSession session = request.getSession(true);
            session.setAttribute("name", "foo");
            return session.getId()+": Set name attribute called";
        }
    }

    protected ClientConfig configureJerseyClient(ExtensionContext extensionContext, ClientConfig clientConfig) {
        assertNotNull(extensionContext);
        assertNotNull(clientConfig);
        // 启用Cookie管理器,自动维护会话Cookie
        clientConfig.property(ClientProperties.COOKIE_HANDLER, new CookieManager());
        return clientConfig;
    }

    protected DeploymentContext configureDeploymentContext(ExtensionContext extensionContext) {
        assertNotNull(extensionContext);
        return deploymentContext;
    }

    protected TestContainerFactory getTestContainerFactory(ExtensionContext extensionContext) {
        assertNotNull(extensionContext);
        return testContainerFactory;
    }

    @Test
    public void testSessionSet(WebTarget target) {
        Invocation.Builder requestBuilder = target.path("session").request(MediaType.APPLICATION_JSON_TYPE);
        
        // 执行PUT请求
        Response response0 = requestBuilder.put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
        System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
        response0.close();
        
        // 执行GET请求,复用同一个构建器以携带Cookie
        Response response1 = requestBuilder.get();
        System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
        response1.close();
    }
}

内容的提问来源于stack exchange,提问作者AlexC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:07:40