TaskDecorator使用笔记

  1. 定义TaskDecorator实例
    public class ContextCopyingDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            try {
                RequestAttributes context = RequestContextHolder.currentRequestAttributes();  
                Map<String,String> previous = MDC.getCopyOfContextMap(); 					  
                SecurityContext securityContext = SecurityContextHolder.getContext();	      
                return () -> {
                    try {
                        RequestContextHolder.setRequestAttributes(context);	
                        runnable.run();
                    } finally {
                        RequestContextHolder.resetRequestAttributes();		
                    }
                };
            } catch (IllegalStateException e) {
                return runnable;
            }
        }
    }
    
  2. 线程池使用TaskDecorator
    @Configuration
    public class AppConfig {
        @Bean
        public TaskExecutor taskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(corePoolSize);
            executor.setMaxPoolSize(maxPoolSize);
            executor.setQueueCapacity(queueCapacity);
            executor.setThreadNamePrefix("MyExecutor-");
            // for passing in request scope context
            executor.setTaskDecorator(new ContextCopyingDecorator());
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.initialize();
            return executor;
        }
    }
    
  3. 存在问题
    从父线程取出的RequestContextHolder对象,此为持有线程上下文的request容器,将其设置到子线程中,按道理只要对象还存在强引用,
    就不会被销毁,但由于RequestContextHolder的特殊性,在父线程销毁的时候,会触发里面的resetRequestAttributes方法(即清除threadLocal里面的信息,
        即reques中的信息会被清除),此时即使RequestContextHolder这个对象还是存在,子线程也无法继续使用它获取request中的数据了
    
  4. 完善思路
    既然是RequestContextHolder的特殊性,那我们就让绕过他的销毁清除,思路不变,还是继续使用threadLocal来传递我们需要使用到的变量,
    在父线程装饰前将所需变量取出来,然后在子线程中设置到threadLocal,业务使用的时候从threadLocal中取即可。
    
  5. 完善代码
    public class ContextCopyingDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            try {
               HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                String ua = request.getHeader("user-agent");
                return () -> {
                    try {
                        ThreadLocalData.setUa(ua);
                        runnable.run();
                    } finally {
                        //清除线程threadLocal的值
                        ThreadLocalData.remove();
                    }
                };
            } catch (IllegalStateException e) {
                return runnable;
            }
        }
    }
    
  6. 参考:https://www.jianshu.com/p/d000a0a44956

你可能感兴趣的:(spring,java,开发语言)