使用自定义注释和CDI注入进行消息路由/处理

时间:2012-10-24 17:44:30

标签: dependency-injection annotations java-ee-6 cdi

我有一个Java EE 6 Web应用程序,并使用WebSocket协议与浏览器进行通信。浏览器可以发送各种类型的消息,并且在服务器 onMessage 方法中,我希望根据消息类型将消息路由(或调度)到特定的消息处理程序类。我想通过注释配置或注册这些消息处理程序,类似于servlet的机制(@WebServlet(“/ there”))。就像在servlet中一样,我希望能够在消息处理程序中使用CDI注入。

现在我有一个 MessageType 注释,一个 MessageHandler 接口和3个实现。

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MessageType
{
    String value();
}


public interface MessageHandler
{
    public void processMessage(String inputMesssage);
}


@MessageType("first")
public class FirstMessageHandler implements MessageHandler
{
    @Inject
    ResourceBundleProvider resourceBundleProvider;

    @Override
    public void processMessage(String inputMesssage)
    {
        System.out.println("FirstMessageHandler#processMessage: " + inputMesssage);
        System.out.println("InjectionTest: " + resourceBundleProvider.getValue("label.language"));
    }
}


@MessageType("second")
public class SecondMessageHandler implements MessageHandler
{
    @Override
    public void processMessage(String inputMesssage)
    {
        System.out.println("SecondMessageHandler#processMessage: " + inputMesssage);
    }
}


public class DefaultMessageHandler implements MessageHandler
{
    @Override
    public void processMessage(String inputMesssage)
    {
        System.out.println("DefaultMessageHandler#processMessage: " + inputMesssage);
    }
}

我还有一个类 MessageDispatcher ,它使用reflections扫描带注释的消息处理程序的类路径,实例化它们并将它们放入映射中:

@ApplicationScoped
public class MessageDispatcher
{
    private Map<String, MessageHandler> messageHandlerMap = new HashMap<String, MessageHandler>();

    @Inject
    DefaultMessageHandler defaultMessageHandler;

    public MessageDispatcher()
    {
        registerAnnotatedHandlers();
    }

    private void registerAnnotatedHandlers()
    {
        Reflections reflections = new Reflections("namespace");

        try
        {
            for (Class<?> annotatedClass : reflections.getTypesAnnotatedWith(MessageType.class))
            {
                String annotationValue = annotatedClass.getAnnotation(MessageType.class).value();

                for (Class<?> interfaceClass : annotatedClass.getInterfaces())
                    if (!annotationValue.isEmpty() && interfaceClass.equals(MessageHandler.class))
                        messageHandlerMap.put(annotationValue, (MessageHandler) annotatedClass.newInstance());
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }       
    }

    public MessageHandler getMessageHandler(String key)
    {
        MessageHandler messageHandler = messageHandlerMap.get(key);

        return messageHandler != null ? messageHandler : defaultMessageHandler;
    }
}

最后在我的websocket servlet的 onMessage 方法中,我从入站消息中提取密钥并将其用于消息路由:

public synchronized void onMessage(String data)
{
    String[] message = data.split(":");

    // Choose the message handler from the message
    MessageHandler messageHandler = messageDispatcher.getMessageHandler(message[0]);

    // Process the message by the message handler
    messageHandler.processMessage(message[1]);
}

我的3条传入示例消息是:

"first:Message to handle with FirstMessageHandler"
"second:Message to handle with SecondMessageHandler"
"third:Message to handle with DefaultMessageHandler"

这很好用,第一个和第二个消息分别由FirstMessageHandler和SecondMessageHandler处理。第三条消息由缺省消息处理程序处理,因为没有注册其他处理程序来处理密钥“third”。

我的问题:我不能在消息处理程序中使用注入,因为它们是使用Java反射创建的。有谁知道如何获得注释处理和CDI注射“结婚”?或者有人认为这种方法是胡说八道,还有另一种解决办法吗?

最诚挚的问候 塞巴斯蒂安

3 个答案:

答案 0 :(得分:1)

这是我最后的方法:

我将PostConstruct方法用于MessageDispachter,在那里我查找所有的消息处理程序bean。对于每个这些bean,我得到它们的注释值和对bean的引用(也包括bean的创建)。然后我将注释值和bean引用存储到我的messageHandlerMap中。有很多CDI委托和拦截,但它有效:

public class MessageDispatcher
{
    private Map<String, MessageHandler> messageHandlerMap = new HashMap<String, MessageHandler>();

    @Inject
    DefaultMessageHandler defaultMessageHandler;

    @Inject
    BeanManager beanManager;

    @PostConstruct
    public void registerHandlers()
    {
        Set<Bean<?>> messageHandlerBeans = beanManager.getBeans(MessageHandler.class, new MessageTypeLiteral());
        for (Bean<?> bean : messageHandlerBeans)
        {
            String key = bean.getBeanClass().getAnnotation(MessageType.class).value();

            if (!key.isEmpty())
            {
                CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean);
                MessageHandler messageHandler = (MessageHandler) beanManager.getReference(bean, MessageHandler.class, creationalContext);
                messageHandlerMap.put(key, messageHandler);
            }
        }
    }

    public MessageHandler getMessageHandler(String key)
    {
        MessageHandler messageHandler = (MessageHandler) messageHandlerMap.get(key);
        return messageHandler != null ? messageHandler : defaultMessageHandler;
    }
}


@Documented
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MessageType
{
    @Nonbinding
    String value();
}


@SuppressWarnings("all")
public class MessageTypeLiteral extends AnnotationLiteral<MessageType> implements MessageType
{
    private static final long serialVersionUID = 1L;

    @Override
    public String value()
    {
        return "";
    }
}


public class DefaultMessageHandler implements MessageHandler
{
    @Inject
    ResourceBundleProvider resourceBundleProvider;

    @Override
    public void processMessage(String inputMesssage)
    {
...


@MessageType("first")
public class FirstMessageHandler implements MessageHandler
{
    @Inject
    ResourceBundleProvider resourceBundleProvider;

    @Override
    public void processMessage(String inputMesssage)
    {
...

@NonBinding注释中的@MessageType注释似乎对于查找所有使用@MessageType("xxx")注释的bean而言非常重要,而且与实际注释值无关(此处为:xxx)。

我希望这能解释重要的事情。有关详细信息,请与我联系

塞巴斯蒂安

答案 1 :(得分:0)

我认为你最简单的解决办法就是保留你拥有的东西,删除扫描因为你不需要它,把你的注释改成限定符并用限定符激活一个CDI事件(你需要为三个不同的限定符中的每一个创建一个AnnotationLite,因为值是绑定的)并且消息作为有效负载。

如果你需要,我可以解释更多。

答案 2 :(得分:0)

查看并调整Dynamically fire CDI event with qualifier with members 它是运行时决策的动态运行时选择服务的CDI方式。 TypeEnum也可以是String。