request is not active anymore主线程退出request失效,异步线程getAttribute报错。重写RequestAttribute或更改范围为ScopeSession

1、需求与问题复现

需求:a方法(a线程)里异步执行b方法(b线程),无需等待b执行完(因为b业务复杂,执行太慢,无需等待),a就结束,返回结果给前端。b方法里涉及调用其他微服务,需要用到http请求头信息做鉴权之类的,所以需要设置请求头,将a线程的请求头放到b线程中。

问题:报错了,如下图!因为a线程一结束,requestAttributes就失效了,b线程里获取attribute就会报错。

java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Cannot ask for request attribute - request is not active anymore!
	at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) ~[?:1.8.0_252]
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908) ~[?:1.8.0_252]

Caused by: java.lang.IllegalStateException: Cannot ask for request attribute - request is not active anymore!
	at org.springframework.web.context.request.ServletRequestAttributes.getAttribute(ServletRequestAttributes.java:149) ~[spring-web-5.3.4.jar:5.3.4]

代码复现下问题。a方法里异步调用b方法,b方法获取attribute报错。

    public void a() {
        // 获取requestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 异步执行b方法
        CompletableFuture.runAsync(() -> {
            try {
                b(requestAttributes);
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        });
        System.out.println("主线程:a结束");
    }


    private void b(RequestAttributes requestAttributes) {
        System.out.println("b方法");
        // 假设有复杂逻辑,运行个2s
        try {
            System.out.println("异步:睡2s");
            Thread.sleep(2000);
            System.out.println("异步:醒了");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 模拟b方法中设置requestAttributes、获取requestAttributes
        try {
            RequestContextHolder.setRequestAttributes(requestAttributes); // 不报错
            System.out.println("异步:requestAttributes保存了——"+requestAttributes);
            
            ServletRequestAttributes re = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();// 不报错
            System.out.println("异步:获取到的requestAttributes: "+ re);
            
            HttpServletRequest request = re.getRequest(); // 不报错
            System.out.println("异步:获取到的request: "+ request);
            
            String header = request.getHeader("userId"); // 不报错
            System.out.println("异步:获取到的Header: "+ header);
            
            Object attribute = re.getAttribute("gray", 0); // 报错
            System.out.println("异步:获取到的attribute: "+ attribute);
            
            RequestContextHolder.resetRequestAttributes(); // 不报错
            System.out.println("异步:清空requestAttributes: "+ header);
            
        } catch (Exception e) {
            System.out.println("出异常了:" + e);
            throw new RuntimeException(e);
        }
    }

运行结果如下:

7e00832f855045568c6241a1ec525e34.png

插播其他小知识-----------------------------------------------

header和attribute的区别:

header是客户端信息。记录http请求信息(请求url、get\post、Cookie等),服务端一般不能修改。

attribute是服务端信息。用于服务器之间存储、传递数据,服务器端可以增删改attribute。

回到正轨-----------------------------------------------

发现当a主线程退出时,b中获取attribute会报错,提示request不是激活状态。这是因为源码里有这么一层判断:

cfbe462f5a834fb29ab3f8d8677726c0.png

插播其他小知识-----------------------------------------------

上图源码146行中的scope有两个值,如下图。request范围和session范围。

00771fe1d5f741aebd25b2b116be6773.png

request和session的区别:

request是一次http请求。生命周期是请求发起到请求响应

session是一次会话,对应多次http请求。生命周期是用户与web应用程序的一次交互,通常是用户访问网站到用户关闭浏览器。

如何判断请求是在同一个session里面呢?

http是无状态的。所以session是服务端这边的管理机制。web服务器(如Tomcat、Jetty、LLS等)都会引入会话的概念来记性状态管理。

会话id!!!在http请求头中加入JSESSIONID来记录会话id,如果请求是同一个会话的请求,他们的会话id是一样的。web服务器端一般是通过Cookie或url重写的方式,把会话id给到浏览器客户端,这样客户端下次可以把这个会话id再传回服务端。

回到正轨-----------------------------------------------

2、解决办法

2.1、范围扩大,request范围会报错,但是session范围不会报错。

跳过147行的检查。

re.getAttribute("gray", 0)会报错,但re.getAttribute("gray", 1)不会报错。所以设置attribute的时候,范围选择1,这样获取的时候范围就是1了。

2.2、自己重写getAttribute方法

这里有两个概念Request和ServletRequestAttributes。后者是对前者的封装,所以先有前者,再有后者。前者是tomcat的,后者是spring的。

2.2.1、继承Request类,来自定义Request类。我这叫HttpServletRequestCopy。

注意:这里需要自己记录下成员变量attributes。当然你也可以记录header,但header也可以不记录,因为header用源代码的getHeader方法获取没有报错。

注意!!!这里需要重写getAttribute方法。这样就可以不用走147行的判断,就不会报错了!!

import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class HttpServletRequestCopy extends Request {
        private Map<String, String> header = new HashMap<>();

        private Map<String, Object> attributes = new HashMap<>();

        /**
         * Create a new Request object associated with the given Connector.
         *
         * @param connector The Connector with which this Request object will always
         * be associated. In normal usage this must be non-null. In
         * some test scenarios, it may be possible to use a null
         * Connector without triggering an NPE.
         */
        public HttpServletRequestCopy(Connector connector) {
            super(connector);
        }

        public HttpServletRequestCopy(Map<String, String> header, Map<String, Object> attribute) {
            super(null);
            if (header != null) {
                this.header = header;
            }
            if (attribute != null) {
                this.attributes = attribute;
            }
        }

        @Override
        public String getHeader(String name) {
            return header.get(name);
        }

        @Override
        public Enumeration<String> getHeaderNames() {
            return Collections.enumeration(header.keySet());
        }

        @Override
        public Object getAttribute(String name) {
            return attributes.get(name);
        }
    }

2.2.2、继承ServletRequestAttributes类来自定义类。我这叫RequestAttributesCopy

import org.springframework.web.context.request.ServletRequestAttributes;

public class RequestAttributesCopy extends ServletRequestAttributes {
        public RequestAttributesCopy(HttpServletRequestCopy request) {
            super(request);
        }
    }

2.2.3、开整!

下面就是在a方法中将Spring的requestAttributes复制到自己的RequestAttributesCopy中,这样传递的时候就传递自己的RequestAttributesCopy,获取attribute的时候也是从自己的RequestAttributesCopy中获取。

public void a() {
        // 获取requestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 将requestAttributes的header、attribute信息复制到自己的httpServletRequestCopy中
        HttpServletRequestCopy httpServletRequestCopy = copyToRequest(((ServletRequestAttributes) requestAttributes).getRequest());
        // 生成自己的requestAttributesCopy
        RequestAttributesCopy requestAttributesCopy = new RequestAttributesCopy(httpServletRequestCopy);
        // 异步执行b方法
        CompletableFuture.runAsync(() -> {
            try {
                // b(requestAttributes);
                b(requestAttributesCopy); // b方法什么都不用改,入参传入自己的requestAttributesCopy,他是继承自RequestAttributes
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        });
        System.out.println("主线程:a结束");
    }
    
public HttpServletRequestCopy copyToRequest(HttpServletRequest request) {
        // 存储/复制header
        Map<String, String> headerMap = new HashMap();
        Enumeration<String> headers = request.getHeaderNames();
        if (headers != null) {
            while (headers.hasMoreElements()) {
                String header = headers.nextElement();
                headerMap.put(header, request.getHeader(header));
            }
        }
        // 存储/复制attribute
        Map<String, Object> attributeMap = new HashMap();
        Enumeration<String> attributeNames = request.getAttributeNames();
        if (attributeNames != null) {
            while (attributeNames.hasMoreElements()) {
                String attributeName = attributeNames.nextElement();
                attributeMap.put(attributeName, request.getAttribute(attributeName));
            }
        }
        return new HttpServletRequestCopy(headerMap, attributeMap);
    }

运行结果如下,这样就不会报错了。这里是null是因为gray这个key,我没有设值,设下值就好了

41dc1338f0cf4fc88a542f7c762bead5.png

 

在这里不得不感慨下继承的伟大,不愧是java的三大特性之一。第三方变成是针对抽象编程,然后我们继承下这个抽象类,重写具体的实现,就能用走第三方的整体逻辑,然后局部逻辑走自己的具体实现。妙哉~

 

  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值