Spring自动连线共享队列NullPointerException

Spring自动连线共享队列NullPointerException,spring,spring-boot,apache-kafka,autowired,blockingqueue,Spring,Spring Boot,Apache Kafka,Autowired,Blockingqueue,我第一次使用Spring,并尝试实现一个共享队列,其中Kafka侦听器将消息放在共享队列上,ThreadManager最终将对从共享队列中取出的项目执行多线程处理。以下是我当前的实现: 听众: @Component public class Listener { @Autowired private QueueConfig queueConfig; private ExecutorService executorService; private List<F

我第一次使用Spring,并尝试实现一个共享队列,其中Kafka侦听器将消息放在共享队列上,ThreadManager最终将对从共享队列中取出的项目执行多线程处理。以下是我当前的实现:

听众:

@Component
public class Listener {
    @Autowired
    private QueueConfig queueConfig;
    private ExecutorService executorService;
    private List<Future> futuresThread1 = new ArrayList<>();
    public Listener() {
        Properties appProps = new AppProperties().get();
        this.executorService = Executors.newFixedThreadPool(Integer.parseInt(appProps.getProperty("listenerThreads")));
    }
    //TODO: how can I pass an approp into this annotation?
    @KafkaListener(id = "id0", topics = "bose.cdp.ingest.marge.boseaccount.normalized")
    public void listener(ConsumerRecord<?, ?> record) throws InterruptedException, ExecutionException
        {
            futuresThread1.add(executorService.submit(new Runnable() {
                    @Override public void run() {
                        try{
                            queueConfig.blockingQueue().put(record);
//                            System.out.println(queueConfig.blockingQueue().take());
                        } catch (Exception e){
                            System.out.print(e.toString());
                        }

                    }
            }));
        }
}
最后,所有内容都从主线程开始:

@SpringBootApplication
public class SourceAccountListenerApp {
    public static void main(String[] args) {
        SpringApplication.run(SourceAccountListenerApp.class, args);
        ThreadManager threadManager = new ThreadManager();
        try{
            threadManager.run();
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }
}
问题


在调试器中运行此命令时,我可以看出侦听器正在向队列中添加内容。当ThreadManager离开共享队列时,它告诉我队列为空,我得到一个NPE。自动连线似乎无法将侦听器使用的队列连接到ThreadManager。非常感谢您的帮助。

您使用Spring的programatic,即所谓的“JavaConfig”,这是一种使用@Configuration注释的SpringBeans类和@Bean注释的方法来设置SpringBeans类的方法。通常在应用程序启动时,Spring会在引擎盖下调用这些@Bean方法,并在其应用程序上下文中注册它们,如果作用域是singleton(默认值),那么这种情况只会发生一次!。不需要在代码中的任何地方直接调用这些@Bean方法。。。您不能这样做,否则您将得到一个可能未完全配置的单独的新实例

相反,您需要将在QueueConfig.BlockingQueue方法中“配置”的BlockingQueue注入ThreadManager。由于队列似乎是ThreadManager工作所必需的依赖项,因此我让Spring通过构造函数注入它:

@Component
public class ThreadManager {

    private int threads;

    // add instance var for queue...
    private BlockingQueue<ConsumerRecord> blockingQueue;

    // you could add @Autowired annotation to BlockingQueue param,
    // but I believe it's not mandatory... 
    public ThreadManager(BlockingQueue<ConsumerRecord> blockingQueue) {
        Properties appProps = new AppProperties().get();
        this.threads = Integer.parseInt(appProps.getProperty("threadManagerThreads"));
        this.blockingQueue = blockingQueue;
    }

    public void run() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(threads);
        try {
            while (true){
                this.blockingQueue.take();
            }
        } catch (Exception e){
            System.out.print(e.toString());
            executorService.shutdownNow();
            executorService.awaitTermination(1, TimeUnit.SECONDS);
        }
    }
}

再澄清一件事:默认情况下,Spring使用@Bean方法的方法名为该Bean分配一个唯一的ID方法名==Bean ID。因此,您的方法称为blockingQueue,这意味着您的blockingQueue实例也将在应用程序上下文中注册ID blockingQueue。新的构造函数参数也称为blockingQueue,其类型与blockingQueue匹配。简化了,这是Spring查找和注入/连接依赖项的一种方式。

您使用Spring的programatic,即所谓的“JavaConfig”,即设置用@Configuration注释的SpringBean类和用@Bean注释的方法。通常在应用程序启动时,Spring会在引擎盖下调用这些@Bean方法,并在其应用程序上下文中注册它们,如果作用域是singleton(默认值),那么这种情况只会发生一次!。不需要在代码中的任何地方直接调用这些@Bean方法。。。您不能这样做,否则您将得到一个可能未完全配置的单独的新实例

相反,您需要将在QueueConfig.BlockingQueue方法中“配置”的BlockingQueue注入ThreadManager。由于队列似乎是ThreadManager工作所必需的依赖项,因此我让Spring通过构造函数注入它:

@Component
public class ThreadManager {

    private int threads;

    // add instance var for queue...
    private BlockingQueue<ConsumerRecord> blockingQueue;

    // you could add @Autowired annotation to BlockingQueue param,
    // but I believe it's not mandatory... 
    public ThreadManager(BlockingQueue<ConsumerRecord> blockingQueue) {
        Properties appProps = new AppProperties().get();
        this.threads = Integer.parseInt(appProps.getProperty("threadManagerThreads"));
        this.blockingQueue = blockingQueue;
    }

    public void run() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(threads);
        try {
            while (true){
                this.blockingQueue.take();
            }
        } catch (Exception e){
            System.out.print(e.toString());
            executorService.shutdownNow();
            executorService.awaitTermination(1, TimeUnit.SECONDS);
        }
    }
}
再澄清一件事:默认情况下,Spring使用@Bean方法的方法名为该Bean分配一个唯一的ID方法名==Bean ID。因此,您的方法称为blockingQueue,这意味着您的blockingQueue实例也将在应用程序上下文中注册ID blockingQueue。新的构造函数参数也称为blockingQueue,其类型与blockingQueue匹配。简化了,这是Spring查找和注入/连接依赖项的一种方式。

这就是问题所在:


ThreadManager ThreadManager=新的ThreadManager

因为您是手动创建实例,所以不能使用Spring提供的DI

一个简单的解决方案是实现CommandLineRunner,它将在完成SourceAccountListenerApp初始化后执行:

@SpringBootApplication
public class SourceAccountListenerApp {
    public static void main(String[] args) {
        SpringApplication.run(SourceAccountListenerApp.class, args);            
    }

    // Create the CommandLineRunner Bean and inject ThreadManager 
    @Bean
    CommandLineRunner runner(ThreadManager manager){
        return args -> {
            manager.run();
        };
    }

}
这就是问题所在:


ThreadManager ThreadManager=新的ThreadManager

因为您是手动创建实例,所以不能使用Spring提供的DI

一个简单的解决方案是实现CommandLineRunner,它将在完成SourceAccountListenerApp初始化后执行:

@SpringBootApplication
public class SourceAccountListenerApp {
    public static void main(String[] args) {
        SpringApplication.run(SourceAccountListenerApp.class, args);            
    }

    // Create the CommandLineRunner Bean and inject ThreadManager 
    @Bean
    CommandLineRunner runner(ThreadManager manager){
        return args -> {
            manager.run();
        };
    }

}

你在哪里打电话给ThreadManager?我更新了帖子以显示作为应用程序入口点的主线程。ThreadManager ThreadManager=new ThreadManager;这就是问题所在。ThreadManager不是bean,因为您需要手动创建对象。你的应用程序是网络应用程序吗?命令行?您需要有一个入口点才能启用ThreadManager。这是一个微服务。第一次也这么做。所以我需要把ThreadManager变成一个bean?我发布了一个答案,我想这就解决了。您需要将ThreadManager用作bean,而不是通过手动创建实例。您在哪里调用了ThreadManager?我更新了帖子以显示用作应用程序入口点的主线程。ThreadManager ThreadManager=new ThreadManager;这就是问题所在。ThreadManager不是bean,因为您需要手动创建对象。你的应用程序是网络应用程序吗?命令行?您需要有一个入口点才能启用ThreadManager。这是一个微服务。第一次也这么做。所以我需要把ThreadManager变成一个bean?我发布了一个答案,我想这就解决了。Y
您需要将ThreadManager用作bean,而不是通过手动创建实例。谢谢Tommy,这是一个很好的解释,我发现它非常有用。既然ThreadManager接受了blockingQueue参数,我需要将它传入调用ThreadManager的地方,因为我已经将主线程添加到了靠近底部的原始帖子中。或者我需要配置Spring以某种方式执行ThreadManager吗?我想Deividi Cavarzan建议的SourceAccountListenerApp类中的@Bean CommandLineRunner runnerThreadManager{}方法应该可以。这样,带有注入BlockingQueue的完全Spring配置的ThreadManager实例将作为方法参数注入…谢谢。希望我能核对一下你们的答案,就像你们两个帮了很多忙一样。谢谢汤米,这是一个很好的解释,我觉得它很有帮助。既然ThreadManager接受了blockingQueue参数,我需要将它传入调用ThreadManager的地方,因为我已经将主线程添加到了靠近底部的原始帖子中。或者我需要配置Spring以某种方式执行ThreadManager吗?我想Deividi Cavarzan建议的SourceAccountListenerApp类中的@Bean CommandLineRunner runnerThreadManager{}方法应该可以。这样,带有注入BlockingQueue的完全Spring配置的ThreadManager实例将作为方法参数注入…谢谢。希望我能核对一下你们的答案,就像你们两个都帮了大忙一样。