【问题标题】:Creating and injecting a per request scoped variable创建和注入每个请求范围的变量
【发布时间】:2018-07-13 02:52:26
【问题描述】:

我希望在 Java EE 中的请求的整个生命周期中都有一个变量。 例如,它可能是一个日志功能,这样我就可以按请求过滤所有日志条目。

我想要了解的关键部分是,它必须相对容易在现有应用程序中实现,因此如果可能的话,可以使用某种依赖注入来获取与特定请求相关的变量。

我已经尝试注入@RequestScoped 变量,但它不起作用,因为它仅作用于容器。我需要能够将相同的对象注入不同的容器。这是可能吗?

编辑:我想要类似这样的东西:

@RequestScoped
public class RequestVariables {
    public String id;
}
@Stateless
public class Logger {
    @Inject
    private RequestVariables requestVariables;
    public void log(String message) {
        System.out.println(requestVariables.id + ":" + message);
    }   
}

@Stateless
public class Service {
    @Inject
    private Logger logger;
    @Inject
    private RequestVariables requestVariables;
    public void save(String data) {
        logger.log("Save");
        session.save(data + requestVariables.id); //Maybe add request parameter to save aswell
    }
}

public class API {
    @Inject
    private Service service;
    @Inject
    private Logger logger;
    @Inject
    private RequestVariables requestVariables;
    @Path("/1")
    @GET
    public Response get(@QueryParam("data") String data) {
        requestVariables.id = UUID.randomUUID().toString()
        service.save(data);
        logger.log("Get");
        return Response.status(204).build();
    }
}

目前这是我尝试过的:

@RequestScoped
public class RequestScope {
    private int test = 0;

    public RequestScope(int test) {
        this.test = test;
    }
    public RequestScope(){}

    public int getTest() {
        return test;
    }

    public void setTest(int test) {
        this.test = test;
    }
}
@Provider
public class RequestScopeFilter implements ContainerRequestFilter {
    @Inject
    private javax.inject.Provider<RequestScope> requestScopeProvider;

    @Context
    private HttpServletRequest request;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        requestScopeProvider.get().setTest(42);
        request.setAttribute("test", "superTest");
    }
}

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
@TransactionAttribute(value=TransactionAttributeType.NOT_SUPPORTED)
public class Service {

    @Context
    private HttpServletRequest httpServletRequest;

    @Inject
    private Provider<RequestScope> requestScopeProvider;

    public void test() {
        RequestScope scope = requestScopeProvider.get();
        String test = (String)httpServletRequest.getAttribute("test");
    }    
}

所以当我从我的服务中获取范围时,它是一个新对象,test 设置为 0,然后它抛出一个 NPE,因为 httpServletRequest 为空

【问题讨论】:

  • 制作Object Singleton,在Spring中有@Scope("Singleton")注解在class级别,你也可以通过xml级别定义。
  • @ArifMustafa 但如果我将其设为单例,它的状态不会在不同请求之间共享吗?
  • docs.oracle.com/javaee/6/tutorial/doc/gipvi.html 阅读有关 Singletons 的信息,看起来好像状态将在请求之间共享(也许不是在会话之间?)
  • 请附上出处,我们会进一步了解您的情况...
  • 我同意 ArifMustafa,我们需要更好的描述。对于日志记录,单例将是理想的。你将不得不解释更多你真正想要做的事情。试着给我们一些代码和至少两到三个用例示例。

标签: java jakarta-ee dependency-injection scope


【解决方案1】:

选项 #1

实现一个拦截器并将请求id设置为HttpServletRequest属性:

@AroundInvoke
public Object setRequestId(InvocationContext ic) throws Exception {
    HttpServletRequest request = [..] // getHttpServletRequest(ic);
    request.setAttribute("request-id", UUID.randomUUID().toString());
    return ic.proceed();
}

然后在需要的任何地方使用HttpServletRequest

@Context
private HttpServletRequest httpRequest;

选项 #2

如果只想按唯一 id 过滤日志,可以配置 Logger 以打印线程名称:[%t]

示例:Log4j PatternLayout

选项 #3

使用自定义 java bean 封装请求数据(查询参数、请求 id 等)并将此 bean 传递给您的应用程序服务。

public class API {

    @Inject
    private Service service;

    @Path("/1")
    @GET
    public Response get(MyCustomRequestBean data) {
        service.doSomejob(data);
        return Response.status(204).build();
    }
}

在ParamConverter中设置请求id和查询参数:

Jax-RS ParamConverter - ParamConverterProvider method return type mismatch

【讨论】:

  • 所以,我可能做错了什么,但我不知道是什么......如果我尝试将 httpRequest 添加到我的无状态服务,那么我只会返回 null。
【解决方案2】:

你可以在你的服务中注入一个提供者:

@Inject
Provider<RequestVariables> vars

然后调用get()获取实例。如果您尝试在请求范围上下文之外的线程中获取 (),您将收到异常。但是,我会尝试以一种不允许这种情况发生的方式来构建

【讨论】:

  • 我刚刚测试了这个方法,虽然调用get() 似乎很好,但我每次都得到一个不同的对象。我通过在我的请求过滤器中将一个整数设置为 42 并在我的服务中获取它来测试这一点。但是我在服务中的整数上得到 0。所以要么它不工作,要么我没有正确设置它
【解决方案3】:

我找到的一个解决方案是使用 ThreadLocal 变量。它看起来很脏,但它可以工作,因为每个请求都是在它自己的线程上执行的(据我所知)。所以这就是我得到的:

public class RequestScope {
    private static final ThreadLocal<String> id = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());

    public static String get() {
        return id.get();
    }
}

这样,如果需要,我还可以轻松地交换 ThreadLocal 以返回更具体的内容。 而且我几乎可以从任何地方获取变量,假设请求没有启动不同的线程

【讨论】:

    猜你喜欢
    • 2013-08-26
    • 2019-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多