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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 14:12:02