【问题标题】:How to create a configurable CDI Producer?如何创建可配置的 CDI Producer?
【发布时间】:2018-01-10 13:58:02
【问题描述】:

我有 两个 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,而无需更改源代码。

所以我创建了一个Producer:

@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"

【问题讨论】:

    标签: jakarta-ee cdi


    【解决方案1】:

    这是动态生成器的正确方法,但请注意,CDI 通常是非常静态的,因此尝试动态执行某些操作可能并不总是很顺利。 现在,您遇到的问题是您自己创建实例(使用new),因此 CDI 只知道生成的对象并且无法控制它。例如。它不会注入到您的 impl bean 的字段/构造函数/.. 中。

    我能想到两个选项:

    1. 仅在 bean 基础上运行

    这意味着您不会自己创建 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. 自己创建对象并手动确保注入工作

    在这种方法中,我们保持您的代码原样,只添加一个将注入结果实例的 sn-p。好处是它可能更容易扩展并且非常简单。缺点是你没有得到一个成熟的 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;
    }
    

    }

    【讨论】:

    • 我已经尝试过你的第一个解决方案,但我不知道 @Typed 注释,没有它我得到一个模棱两可的依赖异常。所以那是缺失的。我只有 2 个实现,所以第一个解决方案并不混乱。谢谢!
    • 您还可以通过使用类型化注释组合您的第一种和第二种方法,然后通过 CDI.current().select(RemoteStorage.class).get() 或 CDI.current() 创建 bean .select(LocalStorage.class).get()。这样,您就避免了手动调用注入的麻烦。
    • 没错,你也可以这样做。我的清单远未完成,但应该足以满足 OP 的情况。
    【解决方案2】:

    一个简单的解决方案是使用@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 限定符将这两个实现注入生产者方法:

    @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;
            }
        }
    }
    

    【讨论】:

    • @New 早就被弃用了,取而代之的是@Dependent - 我强烈反对@New。根据定义,OP 的 bean 和生产者已经依赖。
    • 我对 CDI 1.0 很感兴趣 :) 但“@Dependent”从一开始就与“@New”并列。
    • 也不确定你的意思是生产者默认依赖?如果您没有对生产者进行任何范围定义,那是真的。
    • 我使用的是 CDI 1.2。我将尝试@Siliarus 提出的解决方案。然后,我将阅读有关不推荐使用的“@New”的信息。谢谢
    【解决方案3】:

    我认为您可以像现在一样使用您的两个 Storage 实现,但删除限定符并非常明确地键入它们以排除 StorageService(这也假设它们已作为启用的 bean 添加到 CDI 系统通过一种或另一种机制,例如,如果它们也用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;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-19
      • 1970-01-01
      • 2016-04-18
      • 1970-01-01
      • 2016-06-08
      • 2019-08-03
      • 1970-01-01
      相关资源
      最近更新 更多