如何创建可配置的CDI Producer?

时间:2018-01-10 13:58:02

标签: java-ee cdi

我有两个bean 来实现接口

@Storage(StorageType.LOCAL)
public class LocalStorage implements StorageService { 
    // [...]
}

@Storage(StorageType.REMOTE)
public class RemoteStorage implements StorageService { 
    // [...]
}

在服务类中,我使用的是注入的StorageService

@Stateless
public class DocumentService {

    @Inject
    @Storage(StorageType.REMOTE)
    private StorageService storageService;

    // [...]

}

这很有效,但我希望能够从外部配置StorageType ,而无需更改源代码。

所以我创建了一个制作人:

@Singleton
public class StorageServiceProducer {

    @Inject
    @ConfigurationValue("storage.type") // Injects values from a properties file
    private String storageType;

    @Produces
    public StorageService produceStorageService(InjectionPoint injectionPoint) {            
        if (storageType.equals("remote")) {
            return new RemoteStorage();
        } else {
            return new LocalStorage();
        }
    }
}

...并从我的bean中删除了@Storage注释:

public class LocalStorage implements StorageService { 
    // [...]
}

public class RemoteStorage implements StorageService { 
    // [...]
}

但是现在我得到了一个模糊的依赖异常,大​​概是因为生产者本身的存在。为了强制使用生产者,我发现我可以使用“@Vetoed”注释。

这似乎有用,但由于bean不再受管理,我的实现中的任何注入值都缺失

public class RemoteStorage implements StorageService {

    @Inject
    @ConfigurationValue("project.id")
    private String projectId;

    // [...]

}

所以这是我的问题:

  • 这是否是可以配置“动态”制作人的正确方法?
  • 我成功使用@Alternatives,但这与Annotation方法有相同的缺点:如果我想更改实现,我需要更改beans.xml文件
  • 我怎样才能做到这一点?

编辑:我正在使用CDI 1.2和bean-discovery-mode =“all”

3 个答案:

答案 0 :(得分:3)

对于动态制作人来说,这是一种正确的方法,但请注意,CDI通常是非常静态的,因此尝试动态制作内容可能并不总是让人感觉顺畅。 现在,您要碰到的是您自己创建实例(使用new),因此CDI只知道生成的对象并且无法控制它。例如。它不会注入你的impl bean的fields / constructor / ..

我可以从头脑中想到两个选项:

  1. 在仅限bean的基础上运行
  2. 这意味着您不会自己创建bean,而您的生产者只会移交正确的bean。这种方法的好处在于CDI可以处理所有这些,因此您的注射将无需任何额外的努力即可工作。此外,拦截,装饰,事件处理程序等都将按预期工作。不那么光明的一面是,如果你有更多的实现,或者随着时间的推移会增加更多,那么这会变得混乱。

    为实现这一目标,您可以(例如)限制类型的实施,以便在您@Inject StorageService时不适合。

    @Typed({Object.class, RemoteStorage.class})
    public class RemoteStorage implements StorageService { ....}
    

    并为其他存储实现做同样的事情。 然后,从你的制作人那里,你可以做这样的事情:

    @ApplicationScoped
    public class StorageServiceProducer {
    
    @Inject
    @ConfigurationValue("storage.type") // Injects values from a properties file
    private String storageType;
    
    // there should be no ambiguity injecting specific impl type
    @Inject
    RemoteStorage remoteStorage;
    
    @Inject
    LocalStorage localStorage;
    
    @Produces
    public StorageService produceStorageService(InjectionPoint injectionPoint) {            
        if (storageType.equals("remote")) {
            return remoteStorage;
        } else {
            return localStorage;
        }
    }
    

    }

    1. 自己创建对象并手动确保注入工作
    2. 在这种方法中,我们保持您的代码按原样,只添加一个片段,它将注入到生成的实例中。好处是,它可能更容易扩展,而且非常简单。缺点是你没有得到一个成熟的CDI bean,你只是把一个实例交给CDI说" 嘿,如果有人要求提供类型' StorageService'的bean,你给他这个,哦,请注入它。"拦截,装饰,观察员和其他东西都不会在那里工作。更准确地说,您只需将该实例转换为CDI调用的InjectionTarget

      以下是:

      @ApplicationScoped
      public class StorageServiceProducer {
      
      @Inject
      @ConfigurationValue("storage.type") // Injects values from a properties file
      private String storageType;
      
      @Inject
      BeanManager bm;
      
      @Produces
      public StorageService produceStorageService(InjectionPoint injectionPoint) {          
          StorageService result = null;  
          if (storageType.equals("remote")) {
              result = RemoteStorage();
          } else {
              result = LocalStorage();
          }
          // make it an injection target, it's gonna be something like this
          CreationalContext<Object> ctx = bm.createCreationalContext(null);
          InjectionTarget<Object> injectionTarget = (InjectionTarget<Object>) beanManager
              .getInjectionTargetFactory(bm.createAnnotatedType(result.getClass())).createInjectionTarget(null);
          injectionTarget.inject(result, ctx);
      
          // return result which was injected into
          return result;
      }
      

      }

答案 1 :(得分:0)

一个简单的解决方案是使用@New限定符。

首先,您需要从自动发现中排除两个bean实现。不确定您正在运行的CDI版本 - 从1.1您可以使用@Vetoed注释。或者在这个答案中使用beans.xml:How to exclude a class from scanning with CDI 1.0(虽然它是特定的Weld)

@Vetoed
public class LocalStorage implements StorageService { 
// [...]
}

@Vetoed
public class RemoteStorage implements StorageService { 
    // [...]
}

然后使用@New限定符将两个实现注入到producer方法中:

@Singleton
public class StorageServiceProducer {

    @Inject
    @ConfigurationValue("storage.type") // Injects values from a properties file
    private String storageType;

    @Produces
    public StorageService produceStorageService(@New LocalStorage localStorage, @New RemoteStorage remoteStorage) {            
        if (storageType.equals("remote")) {
            return remoteStorage;
        } else {
            return localStorage;
        }
    }
}

答案 2 :(得分:0)

我认为您可以像现在一样使用两个Storage实现,但删除限定符并非常明确地键入它们以排除StorageService(这也假设它们已被添加到CDI中系统作为已启用的bean通过一种或另一种机制启用,例如,如果它们也使用bean-defining annotationprogrammatically added by an extension进行注释:

@Typed(LocalStorage.class)
public class LocalStorage implements StorageService { 
    // [...]
}

@Typed(RemoteStorage.class)
public class RemoteStorage implements StorageService { 
    // [...]
}

接下来,让它们被注入生产者方法的依赖项:

@Produces
public StorageService produceStorageService(LocalStorage localStorage, RemoteStorage remoteStorage) {            
    if (storageType.equals("remote")) {
        return remoteStorage;
    } else {
        return localStorage;
    }
}