【问题标题】:Java + Command Pattern + Spring + remoting : How to inject dependencies to Command object?Java + 命令模式 + Spring + 远程处理:如何将依赖项注入命令对象?
【发布时间】:2010-01-26 15:19:54
【问题描述】:

在我当前的项目中,我正在处理实现大型接口的 EJB。 实现是通过业务委托完成的,业务委托实现相同的接口并包含真实的业务代码。

正如一些文章所建议的那样

这个“命令模式”的使用顺序是

  1. 客户端创建一个命令并对其进行参数化
  2. 客户端向服务器发送命令
  3. 可以提供服务器接收命令、日志、审计和断言命令
  4. 服务器执行命令
  5. 服务器返回命令结果给客户端

问题发生在第 4 步:

现在我正在使用 spring 上下文从命令内部的上下文中获取 bean, 但我想将依赖项注入到命令中。

这是一个用于说明目的的幼稚用法。我在有问题的地方添加了 cmets:

public class SaladCommand implements Command<Salad> {    
    String request;

    public SaladBarCommand(String request) {this.request = request;}

    public Salad execute() {    
        //this server side service is hidden from client, and I want to inject it instead of retrieving it
        SaladBarService saladBarService = SpringServerContext.getBean("saladBarService");       
        Salad salad = saladBarService.prepareSalad(request);       
        return salad;
    }
}

public class SandwichCommand implements Command<Sandwich> {    
    String request;

    public SandwichCommand(String request) {this.request = request;}

    public Sandwich execute() {  
        //this server side service is hidden from client, and I want to inject it instead of retrieving it      
        SandwichService sandwichService = SpringServerContext.getBean("sandwichService");       
        Sandwich sandwich = sandwichService.prepareSandwich(request);       
        return sandwich;
    }
}

public class HungryClient {
    public static void main(String[] args) {
        RestaurantService restaurantService = SpringClientContext.getBean("restaurantService");
        Salad salad = restaurantService.execute(new SaladBarCommand(
            "chicken, tomato, cheese"
        ));
        eat(salad);

        Sandwich sandwich = restaurantService.execute(new SandwichCommand(
            "bacon, lettuce, tomato"
        ));
        eat(sandwich);
    }
}

public class RestaurantService {
    public <T> execute(Command<T> command) {
        return command.execute();
    }
}

我想摆脱像SandwichService sandwichService = SpringServerContext.getBean("sandwichService"); 这样的电话 而是让我的服务注入。

如何做到这一点最简单?

【问题讨论】:

  • 这个问题真的与 EJB 有关吗?似乎您只是在问如何在 Spring 中连接事物。 SampleCommand 是什么?谁实例化它?谁使用它?这是什么?
  • 在 skaffman 发表评论后,我已更新我的问题以进行澄清。
  • 如果你曾经为这个问题实现过一些东西,我很想听听。我对执行客户端不需要关心的命令所需的服务端服务有同样的问题。
  • @Jaapjan,不抱歉:我只是保留了说明我的示例的设计:命令正在从上下文中提取服务。使用注解应该可以解决这类问题

标签: java spring client-server command-pattern


【解决方案1】:

在服务器端,在 RestaurantService 中,执行以下操作:

  1. 实现 ApplicationContextAware 接口——这将导致 Spring 将对应用程序上下文的引用注入到 RestaurantService。

  2. 在 RestaurantService.execute(Command) 方法中,执行以下操作:

    AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
    beanFactory.autowireBeanProperties(command, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
    command.execute();
    
  3. 最后,在您的应用程序上下文中,声明每个带有注入依赖项的 Command 对象的实例。

这应该具有允许您在客户端创建对象、序列化它们、将它们发送到服务器以及在使用它们之前注入依赖项的效果。在收到物品时进行注射可能比在使用之前进行注射更干净。

对于如何使用 AutowireCapableBeanFactory 还有一些其他选项——这个例子找到了一个具有适当类的 bean 定义,并设置了在应用程序上下文中定义的属性。如果您可以将每个 Command 实现与应用上下文中的名称相关联,则可以使用 configureBean(Object, String) 代替,它将支持回调。

【讨论】:

  • 这是我正在使用的解决方案。我非常喜欢它。
【解决方案2】:

这不是理解的问题 DI 的工作原理

我同意纪尧姆的观点,因为我遇到了同样的情况。

主要问题是实例化和对象生命周期。

  1. 该命令可以多次创建,例如相同的方法。 并以编程方式。

  2. Spring 作为容器,假设您希望创建一次具有范围(会话、原型 = 线程范围)、应用程序范围的对象...

所以该命令不是spring创建的,它应该使用spring创建的服务。 但是服务没有注入到命令中。

谢谢

【讨论】:

    【解决方案3】:

    您可以使用 Spring @Configurable 注解来允许 AspectJ 将 Spring bean 注入到不是由 Spring 创建的对象中。看看here

    【讨论】:

      【解决方案4】:

      我在过去构建了一些非常相似的东西,只是我们没有像你现在那样使用命令模式。在您的情况下,您的命令似乎除了实际查找和运行服务方法之外什么都不做,那么为什么不简单地将该服务方法呈现为 API 而不是完全使用命令模式。然后,您可以通过 Spring Remoting 将服务调用连接到 EJB,所有 Spring 细节都可以保留在协议特定层(Servlet、EJB、MDB ...)中,并且您的代码完全不知道周围发生了什么.

      我们的基础架构如下所示。 (对于那些会抱怨 EJB 存在的人,这并不是整个基础架构,出于安全和性能原因,我们使用 EJB 到 EJB 调用来进行服务到服务的交互)。

      Eclipse 富客户端 -> (Spring Remoting - HTTP) -> Servlet -> (本地接口) -> EJB -> 服务实现

      Servlet - 使用 Spring 上下文查找本地 EJB 接口,并使用 RemoteInvocation 对象(由 Spring Remoting 的 HttpProxyFactoryBean 生成和发送)和服务接口的名称调用通用 EJB 接口的公共调用方法。

      EJB - 根据其接口名称(也是 bean 名称)查找服务,并使用 RemoteInvocationExecutor 通过 RemoteInvocation 对象调用服务实现上的方法。

      现在 EJB 能够绑定到多个服务(尽管我们使用一对一的部署模型)。您可以将 Spring Remoting 用于来自不同应用程序的基于 Http、EJB 或 JMS 的服务调用。没有服务器部署的测试是微不足道的,因为您只需将测试直接连接到实现。

      注意:如果有机会,我会尝试添加一些代码sn-ps。

      【讨论】:

      • 命令可以执行的不仅仅是调用服务方法:有些可能更复杂,我可以将它们链接起来。我想用命令替换我们怪物 EJB 中不断增长的方法列表(有时非常相似)。命令可以看作是“远程回调”。我不太了解“将服务方法呈现为 API”,但对我来说,这听起来像是我试图摆脱的巨大服务接口......
      • 我认为这不是分布式操作的可行模型。您想在客户端上创建一个仅包含数据的 Command 对象,但在服务器端重构该 Command 时注入该 Command 的依赖项。为此,您可能应该在服务器端构建一个 Command 装饰器,并将客户端生成的 Command 作为依赖项,以及您拥有的任何其他依赖项。然后,您的通用 EJB 将通过 Spring 获取装饰器(已注入服务)并在调用 execute 方法之前注入 Command 对象。
      • 听起来仍然会更简单(更简洁)来简化你的“大界面”,将其分解,制作由其他人组成的服务,诸如此类。然后只需使用这些新界面来隐藏或替换旧界面。这很好地将您的业务数据与您的业务服务分开,这是您想要的远程服务。将数据和动作组合在同一个对象中并不适合网络服务调用。
      • 谢谢罗宾。我知道我的问题有点模糊,你的回答给了我一些希望。我想我会和装饰师一起去。由于多种原因(政治、时间等),我无法破坏“大界面”,但使用命令维护它会不那么痛苦。
      【解决方案5】:

      如果 Spring ApplicationContext 将 SimpleCommand 注入到使用它的类中(确实应该如此),那么您只需将它的依赖关系表达为构造函数参数或设置器并注入它们。

      如果不了解谁在使用 SimpleCommand、它来自哪里等,很难给出更多细节。

      【讨论】:

      • SampleCommand 只是一个示例......它是一个虚构的命令。命令在“客户端”中实例化并发送到“服务器”。所以我们在创建命令时不能使用 Spring DI:依赖是服务器端的。
      【解决方案6】:
      public class SampleCommand implements Command {    
          private final String parameter;
          private final ServiceBean service;
      
          //the client build the command using a parameter
          public SampleCommand(ServiceBean service, String parameter) {
               this.parameter = parameter;
               this.service = service;
          }
      
          //this code will be executed by the server
          public Object execute() {
              //do something using the parameter and return the result
              return service.doSomethingWith(parameter);            
          }
      }
      

      您可以在有或没有 Spring 的情况下注入服务:

      <bean id="sampleCommand" class="package.SampleCommand">
           <constructor-arg ref="serviceBean" />
           <constructor-arg value="Param" />
      </bean>
      

      一些通话应用:

      ServiceBean service = ServiceProxy.getService(SampleCommand.class);
      Command command = new SampleCommand(service, "Param");
      

      依赖注入不依赖于框架。

      【讨论】:

      • 这不是理解 DI 如何在有或没有 Spring 或 PicoContainer 或其他的情况下工作的问题,而是关于当组件在网络中的其他地方实例化时如何提供 DI。
      • 我认为您可能有点过度设计了一些东西。也许看看策略模式。服务应该使用命令,而不是相反。
      • 如果命令需要一个 DAO 来完成它的工作怎么办?还是外部服务?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-27
      • 2011-09-30
      • 2014-04-19
      • 1970-01-01
      • 2016-10-05
      • 2020-05-01
      • 1970-01-01
      相关资源
      最近更新 更多