多线程Spring注入对象问题的四种解法(@Autowired 注入为空)

多线程Spring注入对象问题的四种解法

前言

当我们使用多线程时,想给线程注入一个service,但是运行时发现service总是为null。
举个:

public class MyThread implements Runnable {
     

    @Autowired
    Service application;

    public void run() {
     }
}

原因

new Thread不在spring容器中,也就无法获得spring中的bean对象。根据SpringBean的生命周期,能够被Spring管理的Bean都是在扫描范围内并且有 @Componen、@Service、@Configuration 等注释,在使用时才能用IOC注入对象。如果使用new 关键字手动生成的对象,Sring感知不到,无法管理。

解决办法

主要有四种,分为两种思路:

  • 继续使用new 关键字
    • 依赖通过构造函数传入
    • 依赖通过 ApplicationContext 手动获取
    • 使用 @Configurable 注解 管理线程内部的依赖
  • 不使用new 关键字
    • 配合 @Scope("prototype") 使用SpringBean的方式创建线程

一 最简单的方法 – 通过构造器传入依赖

public class TaskComments implements Callable<List<String>> {
     

    private RedisClient redisClient;
    private TaskComments(RedisClient redisClient){
     
        this.redisClient = redisClient;
    }
    
    @Override
    public List<String> call() throws Exception {
     
        //业务代码
    }
}

二 在线程内部使用手动获取的方式

  1. 配置类
@Component
public class BeanContext implements ApplicationContextAware {
     

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
     
        BeanContext.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> clz) throws BeansException {
     
        return (T) applicationContext.getBean(clz);
    }
}
  1. 使用
public class TaskComments implements Runable<List<String>> {
     

    private RedisClient redisClient = BeanContext.getBean(RedisClient.class);
    
    @Override
    public void run() throws Exception {
     
        //业务代码
    }
}

三 使用@Configurable

注释的官方解释:
不是基于Spring的应用程序中使用的所有类型(bean)都是Spring管理的。有时,我们需要将一些依赖注入到一些非Spring管理的类型中。最常见的例子是在实体类中注入服务或DAO层。现在实体类的实例使用 new 创建,也即它们不是被Spring管理的。现在我们想要在这个类型中注入对应的Service或DAO类型,被注入类型是被Spring管理的,以上这些操作可以在 @Configurable 的帮助下完成。

@Configurable
public class TaskComments implements Runnable{
     
	@Autowired
    private Service service;
    	
    @Override
    public void run() throws Exception {
     
        //业务代码
    }
}

四 使用@Scope(“prototype”)

这个注释的意思是在注入Bean时不采用Spring默认的单例模式,每次新创建一个对象。由于在创建线程时,通常是用线程池提交Runable任务,两个都是使用new 关键词创建的,因此两个都得改造。但是线程池对象只需要一个,所以采用传统的单例模式即可。

  1. 自己的configuration 类 新增一个bean(也可以使用xml 的方式实现,只要是Spring管理的就好)
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
     
    return new ThreadPoolTaskExecutor();
}
  1. 使用@Scope(“prototype”)修饰线程任务
@Component
@Scope("prototype")
public class MyThread implements Runnable {
      
	@Autowired
    private Service service;
    	
    @Override
    public void run() throws Exception {
     
        //业务代码
    }
}
  1. 使用
@Component
public class MyServiceCreationListener implements ApplicationListener<ContextRefreshedEvent> {
     

    @Autowired
    private ApplicationContext ctx;
    @Autowired
    private TaskExecutor taskExecutor;        

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
     

        if (event.getApplicationContext().getParent() == null) {
     
            System.out.println("\nThread Started");
            //注意这里线程池和线程都没有使用 new 关键字新建
            taskExecutor.execute(ctx.getBean(MyThread.class));
        }
    }
}

参考

Stack Overflow
Stack Overflow
博客

你可能感兴趣的:(Java语言,后端,spring,java,多线程,ioc)