Java 为基于 spring 的 Web 应用程序中的每个请求分配一个唯一的 id

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18823241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 11:23:08  来源:igfitidea点击:

Assign a unique id to every request in a spring-based web application

javaspring

提问by user2257598

I created a spring-based Java web application,

我创建了一个基于 spring 的 Java Web 应用程序,

for each request, an instance of 'controller class' will be created to handle the request.

对于每个请求,将创建一个“控制器类”实例来处理请求。

in the business logic, I want to do some logging with a UNIQUE ID automatically assigned to each request so that I can track what the program exactly did.

在业务逻辑中,我想使用自动分配给每个请求的唯一 ID 进行一些日志记录,以便我可以跟踪程序究竟做了什么。

the log may be like this(2 requests at same time):

日志可能是这样的(同时有 2 个请求):

[INFO] request #XXX: begin.
[INFO] request #XXX: did step 1
[INFO] request #YYY: begin.
[INFO] request #XXX: did step 2
[INFO] request #YYY: did step 1
[INFO] request #XXX: end.
[INFO] request #YYY: end.

from the log, I can realize: req #XXX: begin-step1-step2-end req #YYY: begin-step1-end

从日志中,我可以意识到: req #XXX: begin-step1-step2-end req #YYY: begin-step1-end

I hope the logging can be called easily everywhere in the code, so I don't want to add a parameter of "requestId" to every java function,

我希望在代码的任何地方都可以轻松调用日志记录,所以我不想在每个java函数中都添加“requestId”的参数,

It's perfect if the logging tool can be called in a static way:

如果可以以静态方式调用日志记录工具,那就完美了:

LOG.doLog("did step 1");

any idea of how can I do this? thank you :)

知道我该怎么做吗?谢谢你 :)

采纳答案by MPavesi

You have three different problems to solve:

您需要解决三个不同的问题:

  1. Generate an unique id for each request
  2. Store the id and access it everywhere in the code
  3. Log the id automatically
  1. 为每个请求生成一个唯一的 id
  2. 存储id并在代码中随处访问
  3. 自动记录id

I would suggest this approaches

我会建议这种方法

  1. Use a Servlet filter or a ServletRequestListener(as suggested by M. Deinum) or a Spring Handler Interceptorto intercept the request in a general way, there you can create a unique id, maybe with an UUID

  2. You can save the id as an attribute of the request, in this case the id would propagate just in the controller layer, not in the services. So you can solve the problem using a ThreadLocalvariable or asking Spring to do the magic with the RequestContextHolder: the RequestContextHolder will allow you to access the request of that specific thread, and the request attributes as well, in the service layer. The RequestContextHolder use ThreadLocal variable to store the request. You can access the request in this way:

    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    // Extract the request
    HttpServletRequest request = attr.getRequest();
    
  3. There is an interesting article(2018 alternative), if you are using log4j, on the customization of the pattern layout of the logger. However, youncan simply create a proxy of your logging system interface and append manually the id to every logged string.

  1. 使用 Servlet 过滤器或ServletRequestListener(如 M. Deinum 所建议的)或Spring Handler Interceptor以一般方式拦截请求,您可以在那里创建唯一的 id,可能带有UUID

  2. 您可以将 id 保存为请求的属性,在这种情况下,id 将仅在控制器层中传播,而不是在服务中。所以,你可以解决使用问题的ThreadLocal变量或询问春天做的魔术RequestContextHolder:在RequestContextHolder将允许您访问特定线程的请求,并请求属性以及在服务层。RequestContextHolder 使用 ThreadLocal 变量来存储请求。您可以通过以下方式访问请求:

    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    // Extract the request
    HttpServletRequest request = attr.getRequest();
    
  3. 有一篇有趣的文章2018 替代),如果您使用的是 log4j,关于记录器模式布局的自定义。但是,您可以简单地创建日志系统接口的代理,并手动将 id 附加到每个记录的字符串。

回答by Sharadr

You can also try using MDCclass of Log4j. The MDC is managed on a per thread basis. If you are using a ServletRequestListner then you can set the unique Id in the requestInitialized.

您也可以尝试使用Log4j 的MDC类。MDC 是基于每个线程进行管理的。如果您使用的是 ServletRequestListner,那么您可以在 requestInitialized 中设置唯一的 Id。

import org.apache.log4j.MDC;
import java.util.UUID;

public class TestRequestListener implements ServletRequestListener {    
protected static final Logger LOGGER = LoggerFactory.getLogger(TestRequestListener.class);

 public void requestInitialized(ServletRequestEvent arg0) {
    LOGGER.debug("++++++++++++ REQUEST INITIALIZED +++++++++++++++++");

    MDC.put("RequestId", UUID.randomUUID());

 }

 public void requestDestroyed(ServletRequestEvent arg0) {
    LOGGER.debug("-------------REQUEST DESTROYED ------------");
    MDC.clear(); 
 }
}

Now anywhere in the code if you do a log either debug, warn or error. Whatever you had put in the MDC will be printed out. You need to configure you log4j.properties. Notice the %X{RequestId}. This referes to the key name which is inserted in the requestInitialized() above.

现在在代码中的任何地方,如果您记录调试、警告或错误。您在 MDC 中放入的任何内容都将被打印出来。您需要配置您的 log4j.properties。注意 %X{RequestId}。这指的是插入到上面 requestInitialized() 中的键名。

log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSSS} %p %C %X{RequestId} - %m%n

I also found this link to be helpful -> What is the difference between Log4j's NDC and MDC facilities?

我还发现此链接很有帮助 -> Log4j 的 NDC 和 MDC 设施之间什么区别?

回答by K.E.

You can also use the "Fish Tagging" in Log4j 2. It is the same idea like MDC and NDC (thread-basis) as described in https://logging.apache.org/log4j/2.x/manual/thread-context.html

您还可以使用 Log4j 2 中的“Fish Tagging”。它与https://logging.apache.org/log4j/2.x/manual/thread- 中描述的 MDC 和 NDC(线程基础)的想法相同-上下文.html

Here you can use either the Thread Context Stack or the Thread Context Map. An example for the Map looks like this:

在这里您可以使用线程上下文堆栈或线程上下文映射。Map 的示例如下所示:

//put a unique id to the map
ThreadContext.put("id", UUID.randomUUID().toString()

//clear map
ThreadContext.clearMap();

And for the pattern in log4j2.xml you can also use the %X{KEY} tag.

对于 log4j2.xml 中的模式,您还可以使用 %X{KEY} 标记。

To put a new id to the map (for example for every incoming request) you can do that in an ServletRequestListener implementation how Sharadr described it.

要将新的 id 放入映射中(例如,对于每个传入的请求),您可以在 ServletRequestListener 实现中这样做,Sharadr 如何描述它。

回答by jamesdeath123

If you don't mind using spring 4.1.3 or later, you can wrap your request into a custom subclass of ContentCachingRequestWrapperclass.

如果您不介意使用 spring 4.1.3 或更高版本,您可以将您的请求包装到ContentCachingRequestWrapper类的自定义子类中。

public class MyHTTPServletRequestWrapper extends ContentCachingRequestWrapper {

    private UUID uuid;

    public MyHTTPServletRequestWrapper (HttpServletRequest request) {
        super(request);
        uuid = UUID.randomUUID();
    }

    public UUID getUUID() {
        return uuid;
    }
}

In your spring controller, add the request to the method's param, and cast it to your custom wrapper:

在您的 spring 控制器中,将请求添加到方法的参数中,并将其转换为您的自定义包装器:

@RequestMapping(value="/get", method = RequestMethod.GET, produces="application/json")
    public @ResponseBody String find(@RequestParam(value = "id") String id, HttpServletRequest request) {
        MyHTTPServletRequestWrapper wrappedRequest = (WGHTTPServletRequestWrapper)request;
        System.out.println(wrappedRequest.getUUID());
...
}

You will need to user filter though, to connect the dots:

不过,您将需要用户过滤器来连接点:

public class RequestLoggingFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest requestToCache = new MyHTTPServletRequestWrapper((HttpServletRequest) request);
System.out.println(((WGHTTPServletRequestWrapper)requestToCache).getUUID());
        chain.doFilter(requestToCache, response);

    }
}

You will find that the printlin from RequestLoggingFilter.doFilter() and from controllers getId() will produce the same UUID.

您会发现 RequestLoggingFilter.doFilter() 和控制器 getId() 中的 printlin 将生成相同的 UUID。

You will just need to play around to use the UUID in appropriate places, but at least you have the value in your controller where is the start of your business logic.

您只需要在适当的地方使用 UUID,但至少您在控制器中拥有价值,这是您业务逻辑的起点。

回答by matthiasbe

You could use the @Scopeannotation to load a new instance of the service for each request.

您可以使用@Scope注释为每个请求加载服务的新实例。

See official documentation here

在这里查看官方文档

If you want to inject this in a non-request aware service, you should use proxyMode option. more info here(from thisstack question)

如果你想在非请求感知服务中注入它,你应该使用 proxyMode 选项。更多信息在这里(来自这个堆栈问题)