简体   繁体   English

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

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

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 :)谢谢 :)

You can also try using MDC class of Log4j.您也可以尝试使用 Log4j 的MDC类。 The MDC is managed on a per thread basis. MDC 是基于每个线程进行管理的。 If you are using a ServletRequestListner then you can set the unique Id in the requestInitialized.如果您使用的是 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.您在 MDC 中放入的任何内容都将被打印出来。 You need to configure you log4j.properties.您需要配置您的 log4j.properties。 Notice the %X{RequestId}.注意 %X{RequestId}。 This referes to the key name which is inserted in the requestInitialized() above.这指的是插入到上面 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 设施之间什么区别?

You have three different problems to solve:您需要解决三个不同的问题:

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

I would suggest this approaches我会建议这种方法

  1. Use a Servlet filter or a ServletRequestListener (as suggested by M. Deinum) or a Spring Handler Interceptor to intercept the request in a general way, there you can create a unique id, maybe with an UUID使用 Servlet 过滤器或ServletRequestListener (如 M. Deinum 所建议的)或Spring Handler Interceptor以一般方式拦截请求,您可以在那里创建唯一的 id,可能带有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.您可以将 id 保存为请求的属性,在这种情况下,id 将仅在控制器层中传播,而不是在服务中。 So you can solve the problem using a ThreadLocal variable 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.所以,你可以解决使用问题的ThreadLocal变量或询问春天做的魔术RequestContextHolder :在RequestContextHolder将允许您访问特定线程的请求,并请求属性以及在服务层。 The RequestContextHolder use ThreadLocal variable to store the request. RequestContextHolder 使用 ThreadLocal 变量来存储请求。 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.有一篇有趣的文章2018 替代),如果您使用的是 log4j,关于记录器模式布局的自定义。 However, youncan simply create a proxy of your logging system interface and append manually the id to every logged string.但是,您可以简单地创建日志系统接口的代理,并手动将 id 附加到每个记录的字符串。

You can also use the "Fish Tagging" in Log4j 2. It is the same idea like MDC and NDC (thread-basis) as described inhttps://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 如何描述它。

If you don't mind using spring 4.1.3 or later, you can wrap your request into a custom subclass of ContentCachingRequestWrapper class.如果您不介意使用 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,但至少您在控制器中拥有价值,这是您业务逻辑的起点。

You could use the @Scope annotation 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.如果你想在非请求感知服务中注入它,你应该使用 proxyMode 选项。 more info here (from this stack question)更多信息在这里(来自这个堆栈问题)

You must use a database sequence (if your database is ORACLE) as follows你必须使用一个数据库序列(如果你的数据库是ORACLE)如下

create sequence "sequence_Name";

String uniqueString=sessionFactory.getCurrentSession(). createSQLQuery("SELECT \"sequence_Name\".nextval FROM dual").list().get(0);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 基于模块化Spring的应用程序 - Modular Spring-based application 基于Spring的Web应用程序的特定于环境的配置? - Environment-specific configuration for a Spring-based web application? EJB 中的 Facades 与基于 Spring 的 Web 应用程序中的服务是否相同 - Are Facades in EJB the same thing as service in a spring-based web application 如何在基于Spring的Web应用程序中发现性能瓶颈 - How to find a performance bottleneck in a Spring-Based Web Application 由于配置错误,无法加载基于Spring的应用程序 - Spring-based Application not loading due to misconfiguration 在基于Spring的应用程序中不推荐使用AbstractExcelView - AbstractExcelView is deprecated in Spring-based application 如何在基于Spring的Java应用程序的每个请求中添加唯一的UUID - How to add unique UUID in every Request in Spring Based Java application 是否可以在基于控制台的基于Spring的应用程序中使用@Valid注释? - Is it possible to use @Valid annotation in console Spring-based application? 将基于Spring的Java模块集成到独立的Java应用程序中 - Integrate Spring-based java module into a standalone java application 如何跟踪基于Spring的Web应用程序中所有函数的进入和退出? - How do you trace the entry into and exit from all functions in Spring-based web app?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM