In business, you need to use modules A and B, which use spring Session to share Session data. The business in module B can only be operated after the user logs in. When A calls B's service, it cannot obtain the user's Session information in module B, resulting in module B determining that the requesting user has not logged in, resulting in module A not getting the required data. The problem is that module A can get the login information of the user, and has used spring Session to share Session data.
Find out the cause of the problem
Sending remote calls using Feign
When the request enters the B service, it is intercepted by its login verification interceptor. When trying to get the login information from the Session, the result is null (login confirmed)
As we all know, the principle of session is to determine a session object through a value in the cookie (jesessionid). The user data cannot be obtained in module B because the session object cannot be obtained by specifying a cookie.
To solve this problem, you need to Debug Feign's process.
Feign process
Query the sending request, go to the remote call code break point, and set into to check
[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-mafi4pb9-1645798237261) (/ users / clover / library / Application Support / typora user images / screenshot 2022-02-25, 8.27.51.png)]
When it is judged that it is not equal, hashCode, toString and other methods, execute the invoke method to make a remote call, and enter setup into
In the invoke method, first create a new request template, which contains our first-class request information
With theout any other special processing, you can directly call Client to send request
From feign's process, we can see that it directly creates A new request for our door, and does not encapsulate the first-class information of the request carried by the browser when sending the request to service A.
solve
During the debug ging process, it is found that there is a this in the executeanddecade method when calling the Client to send the request targetRequest(template); Operation, it returns a request, which is the request object when the Client sends the request.
It is found in the targetRequest method that he will get an interceptor RequestInterceptors, and then it is convenient to call its apply method and pass the request template it creates to the RequestTemplate, and the RequestInterceptors are taken in the container, so we only need to add a RequestInterceptors component to the container.
For example:
@Component public class FeignFillContent implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { // Sync cookie requestTemplate.header("Cookie", "xxx"); } }
So the question is, how should we get cookies? In fact, this problem is also very simple. We can build an interceptor and save HttpServletRequest in ThreadLocal
@Component public class LoginInterceptor implements HandlerInterceptor { public final static ThreadLocal<HttpServletRequest> THREAD_LOCAL_REQUEST = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { THREAD_LOCAL_REQUEST.set(request); ... return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { ... THREAD_LOCAL_REQUEST.remove(); HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } }
SpringBoot is also provided with this tool. We don't need to write extra class RequestContextHolder. The principle of this class is to use ThreadLocal
@Component public class FeignFillContent implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { // Get request context ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); // Sync cookie String myCookies = request.getHeader("Cookie"); requestTemplate.header("Cookie", myCookies); } }
Test it. Service B successfully gets the user login information
Problem recurrence in asynchronous environment
There is no problem in the single thread environment, but there will still be problems in multi threads, such as completable future. This time, I asked the interceptor to throw a null pointer exception.
The problem is that ThreadLocal is the underlying Map, and the key uses the current thread object. Therefore, there is no problem in the single thread environment. There is a problem when asynchrony is used. Because there is a new thread after asynchrony, which is no longer the thread we used to process requests, we can't get the data in ThreadLocal through the current thread object.
// Get shopping items CompletableFuture<Void> cartItemFuture = CompletableFuture.runAsync(() -> { R cartItem = cartFeignService.getCurrentUserCartItem(); ... }, executor);
solve
This problem is also very simple, that is, sharing ThreadLocal, that is, copying the specified ThreadLocal to the ThreadLocal of the specified thread
@Override public OrderConfirmVo confirmOrder() { ... RequestAttributes myReqContext = RequestContextHolder.currentRequestAttributes(); // Query all receiving addresses of members CompletableFuture<Void> memberFuture = CompletableFuture.runAsync(() -> { // Copy a copy of ThreadLocal RequestContextHolder.setRequestAttributes(myReqContext); R memberReceiveAddress = memberFeignService.getMemberReceiveAddress(mrv.getId()); ... }, executor); // Get shopping items CompletableFuture<Void> cartItemFuture = CompletableFuture.runAsync(() -> { // Copy a copy of ThreadLocal RequestContextHolder.setRequestAttributes(myReqContext); R cartItem = cartFeignService.getCurrentUserCartItem(); ... }, executor); CompletableFuture.allOf(memberFuture, cartItemFuture).join(); return vo; }
Welcome to my blog: https://www.ctong.top
Blog address: https://www.ctong.top/2022/02/25/Feign%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%E4%B8%A2%E5%A4%B1%E8%AF%B7%E6%B1%82%E5%A4%B4%E9%97%AE%E9%A2%98/