当前位置 博文首页 > 盛夏温暖流年:SpringBoot 使用 @ServerEndpoint 后 @Autowired

    盛夏温暖流年:SpringBoot 使用 @ServerEndpoint 后 @Autowired

    作者:[db:作者] 时间:2021-07-13 15:55

    之前的技术博客中记录了 SpringBoot 如何集成 WebSocket 实现消息群发推送,主要构建了基础的框架:

    SpringBoot 集成 WebSocket 实现消息群发推送

    后续发现使用 @ServerEndpoint 后,@Autowired 就失效了,这是为什么呢?

    问题描述

    在具体的业务场景中,需要等用户连接成功后,从库表中先获取10条数据,作为默认的初始化数据进行显示。

    我们想当然的通过 @Autowired 注解将对应 Service 进行依赖注入。却发现报了空指针的异常,也就是说,所需要的 Service 没有被成功注入。

    当前写法如下:

    @ServerEndpoint("/imserver/{userId}")
    @Component
    public class WebSocketServer {
    	@Autowired
        private OrderService orderService;
    }
    

    问题分析

    Spring管理采用单例模式(singleton),而 WebSocket 是多对象的,即每个客户端对应后台的一个 WebSocket 对象,也可以理解成 new 了一个 WebSocket,这样当然是不能获得自动注入的对象了,因为这两者刚好冲突。

    @Autowired 注解注入对象操作是在启动时执行的,而不是在使用时,而 WebSocket 是只有连接使用时才实例化对象,且有多个连接就有多个对象。

    所以我们可以得出结论,这个 Service 根本就没有注入到 WebSocket 当中。

    解决方案

    方法一 使用 static 静态对象

    将需要注入的 Service 改为静态,让它属于当前类,然后通过 setOrderService 方法进行注入即可解决。

    @ServerEndpoint("/imserver/{userId}")
    @Component
    public class WebSocketServer {
    
        private static OrderService orderService;
        
    	@Autowired
    	public void setOrderService(OrderService service) {
    		WebSocketServer.orderService = orderService;
    	}
    }
    

    方法二 动态的从 Spring 容器中取出 OrderService

    /**
     * 获取spring容器
     * 当一个类实现了这个接口ApplicationContextAware之后,这个类就可以方便获得ApplicationContext中的所有bean。
     * 换句话说,这个类可以直接获取spring配置文件中所有有引用到的bean对象
     * 前提条件需作为一个普通的bean在spring的配置文件中进行注册 
     */
    public class SpringCtxUtils implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        	SpringCtxUtils.applicationContext = applicationContext;
        }
    
    
        public static <T> T getBean(Class<T> type) {
            try {
                return applicationContext.getBean(type);
            } catch (NoUniqueBeanDefinitionException e) {   
            	//出现多个,选第一个
                String beanName = applicationContext.getBeanNamesForType(type)[0];
                return applicationContext.getBean(beanName, type);
            }
        }
    
        public static <T> T getBean(String beanName, Class<T> type) {
            return applicationContext.getBean(beanName, type);
        }
    }
    

    在 WebSocketServer 中调用:

    private OrderService orderService = SpringCtxUtils.getBean(OrderService.class);
    

    方法一比较简单,亲测有效,方法二没有具体测试,大家可以试一下。

    cs