【问题标题】:How to force CDI/Weld to work with the new keyword?如何强制 CDI/Weld 使用新关键字?
【发布时间】:2018-10-27 17:45:28
【问题描述】:

我有一个命令行 Java SE 应用程序,我想对其进行一些现代化改造。我想在其他 CDI 功能中使用拦截器和依赖注入。然而,该应用程序在设计时并未考虑 CDI 或依赖注入,它广泛使用 new 关键字和构造函数参数,而不是将对象创建委托给 DI 容器。 CDI/Weld 不会在使用 new 创建的对象上注入依赖项或运行拦截器,它根本无法处理构造函数参数。一个简化的例子:

class Main {

    @Inject
    private SomeModule someModule;

    public static void main (String[] args) {
        SeContainer container = ... set up CDI container ...
        Main main = container.select(Main.class).get();
        main.main(args);
    }

    @TraceLog
    public Main () {
        ...
    }

    @TraceLog
    public main (String[] args) {
        Encryptor = new Encryptor(args[1], args[2], args[3]);
        encryptor.run();
    }

}

class Encryptor {

    @Inject
    private SomeModule someModule;

    private String inputFile;
    private String outputFile;
    private String key;

    @TraceLog
    public Encryptor (String inputFile, String outputFile, String key) {
        ...
    }

    @TraceLog
    public run () {
        ...
    }

}

Main 被 CDI 容器实例化, someModule 被注入,@TraceLog 拦截器被构造函数和方法调用。但是 Encryptor 是使用 new 关键字显式创建的,没有注入 someModule,也没有调用 @TraceLog。

CDI 支持以编程方式创建 bean,但仅适用于具有无参数非私有构造函数的类。例子:

CDI.current().select(DefinitelyNotEncryptor.class).get();


@Inject
private Instance<DefinitelyNotEncryptor> instance;

instance.select(DefinitelyNotEncryptor.class).get();

Spring supports injection into objects created with the new keyword, with the use of AspectJ。虽然不知道对构造函数和方法上的拦截器的支持。

@Configurable(preConstruction = true)
@Component
class Encryptor {

    @Autowired
    private SomeModule someModule;

    private String inputFile;
    private String outputFile;
    private String key;

    @TraceLog
    public Encryptor (String inputFile, String outputFile, String key) {
        ...
    }

    @TraceLog
    public run () {
        ...
    }

}

是否有与 CDI/Weld 类似的解决方案?还是我应该求助于使用 Spring?是否支持构造函数和方法拦截器?

【问题讨论】:

    标签: java spring dependency-injection cdi weld


    【解决方案1】:

    如果您想要 CDI 注入,则必须避免使用 new 运算符。

    这就是我如何找到解决您的设计问题的方法

    @ApplicationScoped
    class Main 
    {
        @Inject
        private SomeModule someModule;
    
        @Inject
        private Encryptor encryptor;
    
        public static void main (String[] args) 
        {
            SeContainer container = ... set up CDI container ...
            Main main = container.select(Main.class).get();
            main.run(args);
        }
    
        @TraceLog
        public Main () 
        {
            ...
        }
    
        @TraceLog
        public void run(String[] args) 
        {
            encryptor.init(args[0], args[1], args[2]).run();
        }
    
    }
    
    @Dependent
    class Encryptor 
    {
    
        @Inject
        private SomeModule someModule;
    
        private String inputFile;
        private String outputFile;
        private String key;
    
        @TraceLog
        public Encryptor init(String inputFile, String outputFile, String key)         
        {
            this.inputFile = inputFile;
            this.outputFile = outputFile;
            this.key = key;
            return this;
        }
    
        protected void run()
        {
            // do the real job of the encryptor here
        }
    } 
    

    这段代码的主要内容是:

    • 使用范围注释ApplicationScopedDependent
    • 在您的 Encryptor 类上提供 init() 方法,该方法将采用运行时参数
    • init() 末尾返回Encryptor 实例,以便能够调用run() 方法,该方法受保护以避免直接调用(我认为也可能是private),而无需先调用init()

    它应该适用于这种设计。

    【讨论】:

      【解决方案2】:

      让我们从几句话开始...

      它根本无法处理构造函数参数

      错了。它被称为constructor injection。唯一的限制是所有参数都必须是可解析的 CDI bean。

      @Inject
      public Foo(Bar bar) { // -> CDI will attempt to inject Bar
       // constructor logic
      }
      

      CDI/Weld 不会在使用 new 创建的对象上注入依赖项或运行拦截器

      是的,默认情况下不是。但这可以通过BeanManager.createInjectionTarget(...).inject(...) 实现 不过,这不是转换现有应用程序的首选方式!

      注意:上面的代码只会启用注入。不是拦截。为此,可能需要使用InterceptionFactory。 不过,您的问题不应该需要任何一个。

      CDI 支持以编程方式创建 bean...

      您使用代码 (Instance&lt;T&gt;) 描述的不是创建,而是dynamic/programmatic lookup。它遵循与@Inject 相同的解析规则,只允许您使其动态化而不是一成不变的。 如果你说创造,你可能是指producer methods

      现在,对于您的问题... 如果我理解正确,唯一的问题是Encryptor 的构造函数有参数。好吧,那么您需要确保可以以某种方式注入这些参数。由于它们都是 String 类型的三个,因此您需要将它们包装在一些 bean 中或使用限定符,以便当您有多个 String 类型的 bean 时,typesafe resolution 不会因不明确的分辨率而崩溃。

      这是构造函数在基于限定符的解决方案中的样子。 @Output@Input@Key都是qualifiers

      @Inject
      public Encryptor (@Input String inputFile, @Output String outputFile, @Key String key){...}
      

      以下是这些限定词之一的示例:

      @Qualifier
      @Retention(RUNTIME)
      @Target({METHOD, FIELD, PARAMETER, TYPE})
      public @interface Key {}
      

      最后,您需要生成String bean,您可以使用上面提到的生产者方法来完成。这是一个(获取该值的逻辑除外,因为我不知道您是如何做到的):

      @Produces
      @Key
      public String produceKeyString() { 
      // CDI will invoke this method in order to create bean of type String with qual. @Key
      String key = new String("safeKey") // replace with your logic to get the value
      return key;
      }
      

      【讨论】:

        猜你喜欢
        • 2016-02-14
        • 1970-01-01
        • 2018-09-28
        • 2011-09-19
        • 1970-01-01
        • 2015-02-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多