You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中Application类HashMap变量在控制器中未更新问题求助

嘿,这个问题我之前碰到过,核心原因是Spring Boot的CGLIB代理在“搞事情”!让我给你拆解清楚:

问题根源

你的Application类加了@SpringBootApplication注解,这个注解包含了@Configuration——而Spring会对带有@Bean方法的配置类生成CGLIB代理:代理类会继承你的Application类,所以代理对象会拥有自己的sockets实例变量副本。

你在构造函数里把static app赋值为原始的Application实例,但ApplicationRunner的run方法是在代理对象上执行的!也就是说,你往代理对象的sockets里塞了元素,而控制器里通过Application.app拿到的是原始对象的sockets,那自然是空的,size就是0了。

另外提个小细节:HashMap不是线程安全的,在多线程场景下(比如控制器请求和Bean初始化并发)容易出问题,这个后面一起解决。

解决方案

这里给你三个可行的方案,按推荐程度排序:

方案1:用独立的Bean管理Socket集合(最符合Spring规范)

把Socket集合放到一个单独的组件里,通过Spring的依赖注入来获取,彻底避开static引用和代理问题:

@Component
public class SocketManager {
    // 用线程安全的ConcurrentHashMap替代HashMap
    private final ConcurrentHashMap<Integer, Socket> sockets = new ConcurrentHashMap<>();

    public void addSocket(Socket socket) {
        sockets.put(Integer.valueOf(socket.hashCode()), socket);
    }

    public ConcurrentHashMap<Integer, Socket> getSockets() {
        return sockets;
    }
}

修改Application类,注入SocketManager并使用:

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    @Bean
    public ApplicationRunner runner(TaskExecutor exec, SocketManager socketManager) {
        return args -> {
            // ... 你的socket创建逻辑
            socketManager.addSocket(socket);
            System.out.println("size: " + socketManager.getSockets().size()); // 这里会是1
        };
    }
}

控制器里也注入SocketManager:

@Controller
public class NotificationController {
    private final SocketManager socketManager;

    // 构造注入(推荐)
    public NotificationController(SocketManager socketManager) {
        this.socketManager = socketManager;
    }

    @RequestMapping(method = RequestMethod.POST, value = "/test")
    public ResponseEntity<?> test() {
        System.out.println("size: " + socketManager.getSockets().size()); // 现在会是1
        return ResponseEntity.ok().build();
    }
}

方案2:把sockets改成static变量

如果不想新增类,可以把sockets改成static的,这样代理对象和原始对象共享同一个集合:

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    public static Application app;
    // 改成static+线程安全的集合
    public static ConcurrentHashMap<Integer, Socket> sockets = new ConcurrentHashMap<>();

    public Application() {
        app = this;
    }

    public ConcurrentHashMap<Integer, Socket> getSockets() {
        return sockets;
    }

    @Bean
    public ApplicationRunner runner(TaskExecutor exec) {
        return args -> {
            // ...
            sockets.put(Integer.valueOf(socket.hashCode()), socket);
            System.out.println("size: " + sockets.size()); // 1
        };
    }
}

控制器里的代码不用改,不过这种方式不太符合Spring的依赖注入思想,static变量在大型项目里不好维护,尽量少用。

方案3:禁用CGLIB代理

在@SpringBootApplication里加上proxyBeanMethods = false,告诉Spring不要生成代理类,直接用原始对象:

@SpringBootApplication(proxyBeanMethods = false)
public class Application extends SpringBootServletInitializer {
    // 你的原有代码不变
}

这样ApplicationRunner和控制器拿到的就是同一个实例了,不过这种方式会关闭Spring的一些特性(比如@Bean方法的循环依赖处理、Bean的懒加载优化等),只适合简单场景。

内容的提问来源于stack exchange,提问作者ThanhQB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:13:55