【问题标题】:Tomcat WebSocketServlet and Google GuiceTomcat WebSocketServlet 和 Google Guice
【发布时间】:2012-12-07 03:28:57
【问题描述】:

我有一个需要使用 Tomcat 7 网络套接字的 web 应用程序。

在这个 web 应用程序中,所有标准 Servlet(那些扩展 javax.servlet.http.HttpServlet)都可以很好地(并且正确地)与 Google Guice 一起工作。为了让我的 Servlet 与 Guice 处理程序一起工作,我只是:

  1. 用@Singleton装饰servlet
  2. 为MyHandler 实例声明私有Provider 并生成一个标记为注入的设置器
  3. 用@Inject装饰Servlet的构造函数

举例说明以上几点:

@Singleton
public class MyServlet extends HttpServlet {

    private Provider<MyHandler> myHandler;

    @Inject
    MyServlet() {
    }

    @Override
    protected void service(..) throws ServletException { ... }

    @Inject
    public void setMyHandler(Provider<MyHandler> myHandler) {
        this.myHandler = myHandler;
    }

    ...
}

如何从WebSocketServlet 调用上面称为myHandler 的同一个Guice 处理程序?

我不能采用与标准 servlet 用例相同的样式,因为与标准 servlet 的情况不同,每个 WebSocket 通信都会产生一个扩展 MessageInbound 的实例,而不是使用 Singleton servlet;然后从 MessageInbound 实例中的方法(例如 onOpen 或 onClose)调用将调用 MyHandler 的适当方法; 不是来自HttpServlet 实例中的方法,如上面的MyServlet。

我尝试了什么? 我确实尝试了一些(概念上错误的)解决方案,例如从 MessageInbound 实例中调用 websocket-servlet 的处理程序;这当然会导致降低 Guice 堆栈跟踪的范围问题。这样做的概念正确方法是什么?

【问题讨论】:

  • 如果我理解你,你会覆盖 WebSocketServlet#createWebSocketInbound 在哪里创建 MessageInbound 的新实例?如果这是真的,那么简单地让 Guice 为您创建 MessageInbound(如果需要,辅助注入)。我看到的唯一问题是 http 请求/响应的范围,因为 guice 过滤器使用线程本地来跟踪。但这可以得到补偿。我没听错吗?
  • 感谢您的评论,但我不明白这部分:“那么简单,让 Guice 为您创建 MessageInbound(如果需要辅助注入)”

标签: java servlets websocket tomcat7 guice


【解决方案1】:

我不太明白你想要完成什么。我会在 Guice 中寻找 AOP 支持。在 MessageInbound 子类实例的某些方法中使用之前,您似乎需要设置(注入)MyHandler。一个方面可以做到这一点。

但是,您需要问一个问题:(实例化)控件在哪里?如果有某种方法可以在 Tomcat 应用程序配置中添加某种委托,Guice 可以“增强” MessageInbound 实例,进而在使用前设置正确的 MyHandler。

【讨论】:

    【解决方案2】:

    查看 GitHub 示例后更新:

    您使用 Guice 的方式很好。由于 MessageInbound 只有一种特殊用法,因此不需要像 AbstractGuiceWebSocketServlet 这样的糖。提供者 chatLogHdlr 再做手动构建就可以了。但是你失去了 AOP 支持。如果需要,您可能需要进行辅助注射。但现在这很好。

    附带说明,使用构造注入而不是 setter 注入。

    我立即看到了问题所在。 这不是 Guice,而是你如何使用 Guice-Persist。 我没有经常使用 GP,仍然使用古老的 Warp-persist。但我发现您在代码中使用 Guice-persist 的方式存在 2 个问题:

    1. 你需要注入 PersistService 来启动 Guice-Persist。 WIKI 中对此进行了解释,例如

      public class PocWebApp extends GuiceServletContextListener {
      
      @Inject
      PersistService ps;
      
      @Override
      protected Injector getInjector() {
          Injector injector = Guice.createInjector(new ServletModule() {
      
              @Override
              protected void configureServlets() {
                  install(new JpaPersistModule("DesktopPU"));
                  serve("/socket/main/").with(MainSocket.class);
              }
      
          });
          injector.injectMembers(this);
          return injector;
      }
      
      @Override
      public void contextInitialized(ServletContextEvent servletContextEvent) {
          super.contextInitialized(servletContextEvent);
          ps.start();
      }
      }
      
    2. PersistFilter 没有用,因为只有第一次 WebSocket 会通过过滤器,但所有后续通信都不会通过过滤器。在@Transactional (Session-per-transaction) 附近使用 txn 是可行的方法。

    题外话:

    您打算支持多少用户?如果这将是一个核心聊天服务器,我会改用 Netty,但它涉及更多。谷歌搜索发现:

    http://comoyo.github.com/blog/2012/07/30/integrating-websockets-in-netty/

    原答案:

    所以这是一个关于风格的问题?

    WebSockets != Servlets。 如果它们需要稍微不同的样式,这没有什么问题。我什至更愿意被提醒我不是在处理 vanilla servlet。

    一些观察:

    WebSocketServlet 没什么特别的。您可以轻松地将它与 Guice-Servlet 一起使用。例如:

     @Singleton
     public class FooGuiceWebSocketServlet extends WebSocketServlet {...}
    

    然后将其称为

     serve("/foo").with(FooGuiceWebSocketServlet.class);
    

    现在,MessageInbound 很特别,正如您所解释的,所有这些都由 Tomcat 处理。 MessageInbound 是 WebSocket 范围的。现在 Guice 对这个范围一无所知,因此保留它可能是有意义的。

    对于初学者,我会确保 MessageInbound 是由 Guice 创建的。类似这样的东西:

    @Singleton
    public class ExampleWebSocketServlet extends AbstractGuiceWebSocketServlet {
    
        @Override
        public Class<? extends StreamInbound> serveWith() {
            return Foo.class;
        }
    
        public static class Foo extends MessageInbound {
    
        @Inject GuiceCreatedAndInjected bar;
    
        @Override
        protected void onBinaryMessage(ByteBuffer byteBuffer) throws IOException {
            // do nothing
        }
    
        @Override
        protected void onTextMessage(CharBuffer charBuffer) throws IOException {
            // this getSwOutbonds() is not very friendly to testing
            getWsOutbound().writeTextMessage(bar.echo(charBuffer));
        }
    
       }
    }
    

    在哪里

    public abstract class AbstractGuiceWebSocketServlet extends WebSocketServlet {
    
        @Inject Injector injector;
    
        @Override
        protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {
            return injector.getInstance(serveWith());
        }
    
        public abstract Class<? extends StreamInbound> serveWith();
    
    }
    

    您可以根据需要从这里转到更高的抽象和/或范围。我不是特别喜欢#getWsOutbound(),因为它会妨碍测试。

    不断改进风格,直到您满意为止。说你是否需要更多帮助(将修改答案)。

    【讨论】:

    • 感谢您的回答。问题是这是最简单的情况;我需要跟踪连接和个人连接 ID;看看一个简单的概念验证来复制问题on github,以及我在MainSocket.java 中的createWebSocketInbound 方法,我在其中为每个打开的websocket 连接设置了clientNo。在您建议的这种结构中,我应该在哪里执行此操作?
    • 我将您推荐的结构应用到我的proof of concept 并得到与以前完全相同的java.lang.IllegalStateException。
    • 很棒的东西;我将根据您建议的修复将我的更改推送到 github;有用!非常感谢!
    • 旁注:@Transactional 仅用于最低级别,即与EntityManager 交互时。
    • PersistFilter 在请求开始时启动事务并在返回响应之前提交它。使用 [@Transactional] 事务在方法进入之前启动并在方法返回之后提交。与其他任何地方相比,我更喜欢在业务逻辑级别上标记事务。在您的情况下,ChatLogHandlerImpl 是业务逻辑,我将 logChatMsg 标记为 [@Transactional] 因此插入和刷新方法都在同一个事务中完成(如果不使用 PersistFilter,以防它已经使用相同的 txn) .无需刷新,因为设置了 id。
    猜你喜欢
    • 2017-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-18
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    相关资源
    最近更新 更多