在使用@Async情况下,子线程访问父线程中@RequestScope注解修饰的对象

问题描述:

在开发过程中,发现使用@Async修饰的方法,一旦使用到@RequestScope修饰的对象时,就会出现异常。
大致意思是当前线程没有request scope。

搜索过程:

在搜索问题的过程中,发现似乎所有的线索都在指向:How to enable request scope in async task executor
帖子描述问题和我碰到的问题一模一样。既然找到同病相怜的人,那就看怎么解决的吧。于是:

public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
    }
}

public class ContextAwareCallable<T> implements Callable<T> {
    private Callable<T> task;
    private RequestAttributes context;

    public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
        this.task = task;
        this.context = context;
    }

    @Override
    public T call() throws Exception {
        if (context != null) {
            RequestContextHolder.setRequestAttributes(context);
        }

        try {
            return task.call();
        } finally {
            RequestContextHolder.resetRequestAttributes();
        }
    }
}

@Configuration
public class ExecutorConfig extends AsyncConfigurerSupport {
    @Override
    @Bean
    public Executor getAsyncExecutor() {
        return new ContextAwarePoolExecutor();
    }
}

__ 结果并不可行,似乎是因为版本更新,这个方法已经行不通了。 __

随后不甘心的点击RequestContextHolder里面的代码查看。
发现RequestContextHodler里有两个方法:

   public static void setRequestAttributes(RequestAttributes attributes) {
        setRequestAttributes(attributes, false);
    }

    public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
        if (attributes == null) {
            resetRequestAttributes();
        }
        else {
            if (inheritable) {
                inheritableRequestAttributesHolder.set(attributes);
                requestAttributesHolder.remove();
            }
            else {
                requestAttributesHolder.set(attributes);
                inheritableRequestAttributesHolder.remove();
            }
        }
    }

可以看到,其中setRequestAttributes(RequestAttributes attributes, boolean inheritable)是取不一样的AttributesHolder.
于是我尝试将代码改成

public class ContextAwareCallable<T> implements Callable<T> {
    private Callable<T> task;
    private RequestAttributes context;

    public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
        this.task = task;
        this.context = context;
    }

    @Override
    public T call() throws Exception {
        if (context != null) {
            RequestContextHolder.setRequestAttributes(context, true);
        }

        try {
            return task.call();
        } finally {
            RequestContextHolder.resetRequestAttributes();
        }
    }
}

重启测试… …
结果依然是失败的.
之后,我又注意到

try {
    return task.call();
} finally {
    RequestContextHolder.resetRequestAttributes();
}

在finally中调用了RequestContextHolder.resetRequestAttributes();.
其在RequestContextHolder中对应的源码是:

public static void resetRequestAttributes() {
    requestAttributesHolder.remove();
    inheritableRequestAttributesHolder.remove();
}

直接将RequestAttributesHolder给清了. 怒从心头起,恶向胆边生,不清了!
将之注释掉.


public class ContextAwareCallable<T> implements Callable<T> {
    private Callable<T> task;
    private RequestAttributes context;

    public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
        this.task = task;
        this.context = context;
    }

    @Override
    public T call() throws Exception {
        if (context != null) {
            RequestContextHolder.setRequestAttributes(context, true);
        }

        try {
            return task.call();
        } finally {
            // RequestContextHolder.resetRequestAttributes();
        }
    }
}  

重启测试… …
成了!

最终代码

public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
    }
}

public class ContextAwareCallable<T> implements Callable<T> {
    private Callable<T> task;
    private RequestAttributes context;

    public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
        this.task = task;
        this.context = context;
    }

    @Override
    public T call() throws Exception {
        if (context != null) {
            RequestContextHolder.setRequestAttributes(context, true);
        }

        try {
            return task.call();
        } finally {
            // RequestContextHolder.resetRequestAttributes();
        }
    }
}

@Configuration
public class ExecutorConfig extends AsyncConfigurerSupport {
    @Override
    @Bean
    public Executor getAsyncExecutor() {
        return new ContextAwarePoolExecutor();
    }
}

__ 该解决过程仅解决了@Async注释的方法调用到了@RequestScope修饰的对象.并没有测试过是否会出现其他问题.仅保存为自己复习所用. __


2019年8月10日13:01:00
最近老大看了一下spring文档,跟我说了一下我最后注释的那个reset方法,感觉还是要放开. 目前还没有改动,等到实际改了再更新.


   转载规则


《在使用@Async情况下,子线程访问父线程中@RequestScope注解修饰的对象》 echi1995 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Quartz修改代码后仍运行老代码的问题 Quartz修改代码后仍运行老代码的问题
问题描述:今天在使用quartz时,因为一个疏忽,错误的将一个job加入了scheduler中.之后不管怎么改代码都是老的结果,并没有细想原因而是单方面认为是缓存的原因.导致不管是清class文件还是重启都无法解决.直到下班都没有解决.带着
本篇 
在使用@Async情况下,子线程访问父线程中@RequestScope注解修饰的对象 在使用@Async情况下,子线程访问父线程中@RequestScope注解修饰的对象
问题描述: 在开发过程中,发现使用@Async修饰的方法,一旦使用到@RequestScope修饰的对象时,就会出现异常。大致意思是当前线程没有request scope。 搜索过程:在搜索问题的过程中,发现似乎所有的线索都在指向:H
  目录