如何在POJO类中正确注入Jersey的SSEResource?
在普通POJO中注入Jersey的SSEResource(HK2依赖注入)
我已经实现了Jersey的SSE功能,需求是服务器数据变更时向已连接客户端发送通知。目前SSE功能正常运行,现在需要将其与检测数据变更的POJO类关联,让POJO能调用SSEResource的sendEventToSpecificClient()或broadcastEvent()方法,但测试时发现POJO中注入的SSEResource始终为null。
现有代码
SSEResource类
package org.example; import javax.annotation.PostConstruct; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; import javax.ws.rs.sse.OutboundSseEvent; import javax.ws.rs.sse.Sse; import javax.ws.rs.sse.SseEventSink; import org.jvnet.hk2.annotations.Service; @Service @Path("events") public class SSEResource { @Context Sse sse; private SSEConnectionManager connectionManager; @PostConstruct public void initialize() { connectionManager = SSEConnectionManager.getInstance(); connectionManager.initialize(sse); } @GET @Produces(MediaType.SERVER_SENT_EVENTS) public void getServerSentEvents(@Context SseEventSink eventSink, @Context UriInfo uri) { MultivaluedMap<String, String> queryParams = uri.getQueryParameters(); String closeParam = queryParams.getFirst("close"); String clientIdParam = queryParams.getFirst("clientId"); System.out.println("Client ID Parameter: " + clientIdParam); // Check if clientIdParam is missing or empty if (clientIdParam == null || clientIdParam.isEmpty()) { // Send a message to enter the clientId sendEventToClient(eventSink, "Please enter a valid clientId!"); // Close the SSE connection since clientId is not provided eventSink.close(); return; // Return early } if (closeParam != null && clientIdParam != null && !clientIdParam.isEmpty()) { // Close the SSE connection immediately eventSink.close(); return; // Return early without sending the welcome message } // Check if the client has an existing connection if (connectionManager.isConnected(clientIdParam)) { connectionManager.closeConnection(clientIdParam); } // Add the connection to the SSEConnectionManager connectionManager.addConnection(clientIdParam, eventSink); // Send a welcome message to the client sendEventToClient(eventSink, "Welcome to SSE, Client " + clientIdParam + "!"); } // Method to send an SSE event to a specific client private void sendEventToClient(SseEventSink eventSink, String data) { final OutboundSseEvent event = sse.newEventBuilder().name("message-to-client").data(String.class, data).build(); eventSink.send(event); } public void sendEventToSpecificClient(String clientId, String message) { try { System.out.println("Client ID Parameter: " + clientId); if (clientId != null && !clientId.isEmpty()) { // Check if the client is connected if (connectionManager.isConnected(clientId)) { try { sendEventToClient(connectionManager.getConnection(clientId), message); } catch (Exception e) { // Handle any exceptions that occur during sending the event System.out.println("Error sending event to Client " + clientId + ": " + e.getMessage()); } } else { System.out.println("Client " + clientId + " not connected!"); } } } finally { // eventSink.close(); // Close the SseEventSink in the finally block } } @GET @Path("close/{clientId}") @Produces(MediaType.TEXT_PLAIN) public String closeConnection(@PathParam("clientId") String clientId) { if (clientId != null && !clientId.isEmpty()) { if (connectionManager.isConnected(clientId)) { connectionManager.closeConnection(clientId); return "SSE connection closed for client " + clientId; } else { return "Client " + clientId + " not connected!"; } } else { return "Invalid client ID!"; } } public void broadcastEvent(String message) { final OutboundSseEvent event = sse.newEventBuilder().name("broadcast").mediaType(MediaType.TEXT_PLAIN_TYPE).data(String.class, message).build(); // Broadcast the event to all connected clients connectionManager.getBroadcaster().broadcast(event); } }
测试用MainApp类(注入失败,sseResource为null)
import javax.inject.Inject; public class MainApp { @Inject SSEResource sseResource; public static void main(String[] args) { // Simulate the client ID and message String clientId = "123"; String message = "This is a test message."; new MainApp().call(clientId, message); } void call(String clientId, String message) { // Call the sendEventToSpecificClient method to send the event sseResource.sendEventToSpecificClient(clientId, message); } }
MyApplication配置类
import org.glassfish.hk2.utilities.binding.AbstractBinder; import org.glassfish.jersey.server.ResourceConfig; public class MyApplication extends ResourceConfig { public MyApplication() { // Register your resource classes or packages here using packages(), register(), etc. packages("org.example"); // Register the AppLifecycleListener register(AppLifecycleListener.class); // Register the AbstractBinder with the bindings for dependency injection register(new AbstractBinder() { @Override protected void configure() { // Bind the SSEResource class to itself (no need to specify the same class again) bind(SSEResource.class).to(SSEResource.class); } }); } }
解决方案
问题根源
直接通过new MainApp()创建的对象不在HK2容器的管理范围内,@Inject注解不会被HK2处理,因此sseResource始终为null。HK2只会对自身创建和管理的对象执行依赖注入。
正确实现步骤
1. 将POJO纳入HK2容器管理
给你的POJO(比如MainApp)添加HK2的@Service注解,让HK2自动扫描并管理它:
import javax.inject.Inject; import org.jvnet.hk2.annotations.Service; @Service public class MainApp { // ... 原有代码 }
或者在MyApplication的AbstractBinder中显式绑定该POJO:
register(new AbstractBinder() { @Override protected void configure() { bind(SSEResource.class).to(SSEResource.class); bind(MainApp.class).to(MainApp.class); // 绑定POJO到容器 } });
2. 从HK2容器获取POJO实例
不要直接new对象,而是通过HK2的ServiceLocator获取容器管理的实例:
import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.server.ApplicationHandler; import org.glassfish.jersey.server.ResourceConfig; @Service public class MainApp { @Inject SSEResource sseResource; public static void main(String[] args) { // 初始化Jersey应用,获取HK2容器 ResourceConfig config = new MyApplication(); ApplicationHandler appHandler = new ApplicationHandler(config); ServiceLocator locator = appHandler.getServiceLocator(); // 从容器获取MainApp实例 MainApp mainApp = locator.getService(MainApp.class); String clientId = "123"; String message = "This is a test message."; mainApp.call(clientId, message); } void call(String clientId, String message) { sseResource.sendEventToSpecificClient(clientId, message); } }
3. 优化建议:解耦SSE发送逻辑
将SSE事件发送逻辑从SSEResource中抽离,创建独立的SSEService类,让POJO和资源类都依赖这个服务类,符合单一职责原则:
import org.jvnet.hk2.annotations.Service; import javax.inject.Inject; import javax.ws.rs.sse.Sse; import javax.ws.rs.sse.OutboundSseEvent; @Service public class SSEService { private final SSEConnectionManager connectionManager; private final Sse sse; @Inject public SSEService(Sse sse) { this.sse = sse; this.connectionManager = SSEConnectionManager.getInstance(); this.connectionManager.initialize(sse); } public void sendEventToSpecificClient(String clientId, String message) { try { if (clientId != null && !clientId.isEmpty()) { if (connectionManager.isConnected(clientId)) { try { OutboundSseEvent event = sse.newEventBuilder() .name("message-to-client") .data(String.class, message) .build(); connectionManager.getConnection(clientId).send(event); } catch (Exception e) { System.out.println("Error sending event to Client " + clientId + ": " + e.getMessage()); } } else { System.out.println("Client " + clientId + " not connected!"); } } } catch (Exception e) { e.printStackTrace(); } } public void broadcastEvent(String message) { OutboundSseEvent event = sse.newEventBuilder() .name("broadcast") .mediaType(MediaType.TEXT_PLAIN_TYPE) .data(String.class, message) .build(); connectionManager.getBroadcaster().broadcast(event); } }
之后在SSEResource和你的POJO中注入SSEService即可调用发送方法,避免直接依赖资源类。
内容的提问来源于stack exchange,提问作者skr
相关产品推荐
相关产品推荐

