在spring项目启动时,有一些功能需要在项目启动时加载的,一般我们都会写一个实现ServletContextListener接口的监听类来完成

public class CollectorBoot implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        logger.info("启动订阅启动类...");
        String[] topicTypeArr = ConsumerResource.TOPIC_TYPE.split(",");
        for (int i = 0; i < topicTypeArr.length; i++) {
            if("1".equals(topicTypeArr[i])){ startPointStorageConsumer();}//点位设备数据入库
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if(pointStorageCollector!=null){pointStorageCollector.close();}
    }

    /**
     * @描述 点位设备数据入库,KAFKA订阅
     */
    private void startPointStorageConsumer() {
	//获取bean
        PointStorageConsumer pointStorageConsumer = SpringApplicationContext.getBean(pointStorageConsumer .class);
        this.pointStorageCollector = new LogCollector(POINT_DATA_PLUS,threadNum,pointStorageConsumer);
        try {
            this.pointStorageCollector.start();
            logger.info("点位设备数据入库,KAFKA订阅成功!");
        } catch (Exception e) {
            logger.error("点位设备数据入库,KAFKA订阅失败!", e);
            e.printStackTrace();
        }
    }
}

而在项目中,我们会有一个实现ApplicationContextAware接口来获取上下文bean的类

public class SpringApplicationContext implements ApplicationContextAware {
    private static ApplicationContext context;

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

    public static ApplicationContext getContext() {
        return context;
    }

    // name表示注入的注解name名
    public static Object getBean(String name) {
        return context.getBean(name);
    }

    // T表示注入的类
    public static <T> T getBean(Class<T> c){
        return context.getBean(c);
    }
}

项目启动时监听类CollectorBoot通过调用SpringApplicationContext.getBean方法获取pointStorageConsumer,最后实现pointStorageConsumer业务的kafka主题订阅。

最近在把这套代码搬到一个spring boot的项目中后,突然发现一直获取不到bean,我们先看下原来是怎么处理的:
原spring项目是在web.xml中配置监听类,tomcat启动时检查web.xml的信息加载ContextLoaderListener,此时会Spring容器会自动把上下文环境对象调用ApplicationContextAware接口中的setApplicationContext方法,之后再调用其他监听类,CollectorBoot通过SpringApplicationContext.getBean就可以获取到spring容器中的bean了

   <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>classpath:spring_config_*.xml</param-value>
   </context-param>
   <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
	
   <!-- Open Session In View Filter -->
   <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

而在spring boot中,却获取不到,初步怀疑是类加载顺序的不同导致的。debug了一下,发现通过@ServletComponentScan注册listener,每次都是先加载listener后再加载ApplicationContextAware,因此导致每次获取的bean都是null,查了一下其他人的情况,最后发现可以通过WebApplicationContextUtils来获取SpringContext,因此把上面的监听类改成如下情况,至此顺利在listener中获取到想要的bean了

@WebListener
public class CollectorBoot implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        logger.info("启动订阅启动类...");
        WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
        String[] topicTypeArr = ConsumerResource.TOPIC_TYPE.split(",");
        for (int i = 0; i < topicTypeArr.length; i++) {
            if("1".equals(topicTypeArr[i])){ startPointStorageConsumer(wac);}//点位设备数据入库
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if(pointStorageCollector!=null){pointStorageCollector.close();}
    }

    /**
     * @描述 点位设备数据入库,KAFKA订阅
     */
    private void startPointStorageConsumer(WebApplicationContext wac) {
	//获取bean
        PointStorageConsumer pointStorageConsumer = wac.getBean(PointStorageConsumer.class);
        this.pointStorageCollector = new LogCollector(POINT_DATA_PLUS,threadNum,pointStorageConsumer);
        try {
            this.pointStorageCollector.start();
            logger.info("点位设备数据入库,KAFKA订阅成功!");
        } catch (Exception e) {
            logger.error("点位设备数据入库,KAFKA订阅失败!", e);
            e.printStackTrace();
        }
    }
}