Spring自动连线共享队列NullPointerException

时间:2018-09-17 20:59:25

标签: spring spring-boot apache-kafka autowired blockingqueue

我第一次使用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());
                        }

                    }
            }));
        }
}

队列:

@Configuration
public class QueueConfig {
    private Properties appProps = new AppProperties().get();

    @Bean
    public BlockingQueue<ConsumerRecord> blockingQueue() {
        return new ArrayBlockingQueue<>(
                Integer.parseInt(appProps.getProperty("blockingQueueSize"))
        );
    }
}

ThreadManager:

@Component
public class ThreadManager {
    @Autowired
    private QueueConfig queueConfig;
    private int threads;

    public ThreadManager() {
        Properties appProps = new AppProperties().get();
        this.threads = Integer.parseInt(appProps.getProperty("threadManagerThreads"));
    }


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

最后,是所有内容的主线程:

@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。任何帮助表示赞赏。

2 个答案:

答案 0 :(得分:3)

您使用Spring的程序化方式(即所谓的“ JavaConfig”)来设置Spring bean(以@Configuration注释的类和以@Bean注释的方法)。通常,在应用程序启动时,Spring会在后台调用这些@Bean方法,并将其注册到应用程序上下文中(如果作用域为singleton-默认值,则只会发生一次!)。无需直接在代码中的任何地方调用这些@Bean方法...不必这样做,否则您将获得一个单独的新鲜实例,该实例可能尚未完全配置!

相反,您需要将在BlockingQueue<ConsumerRecord>方法中“配置”的QueueConfig.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<ConsumerRecord>实例也将在应用程序上下文中注册为ID blockingQueue。新的构造函数参数也称为blockingQueue,其类型与BlockingQueue<ConsumerRecord>相匹配。简化,这是Spring查找并注入/关联依赖项的一种方式。

答案 1 :(得分:3)

这是问题所在

ThreadManager threadManager = new 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();
        };
    }

}