简体   繁体   English

如何强制CDI / Weld使用new关键字?

[英]How to force CDI/Weld to work with the new keyword?

I have a command line Java SE application that I would like to modernize a bit. 我有一个命令行Java SE应用程序,我想对其进行现代化。 I want to use interceptors and dependency injection among other CDI features. 我想在其他CDI功能中使用拦截器和依赖项注入。 However the application was not designed with CDI or dependency injection in mind, it extensively uses the new keyword and constructor parameters instead of delegating object creation to the DI container. 但是,该应用程序在设计时并未考虑CDI或依赖项注入,而是广泛使用new关键字和构造函数参数,而不是将对象创建委托给DI容器。 CDI/Weld does not inject dependencies or run interceptors on objects created with new, and it can not handle constructor parameters at all. CDI / Weld不会在使用new创建的对象上注入依赖项或运行拦截器,并且它根本无法处理构造函数参数。 A simplified example: 一个简化的例子:

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 is instantiated by the CDI container, someModule is injected, and @TraceLog interceptor is called for both constructor and method. Main由CDI容器实例化,注入someModule,并为构造函数和方法调用@TraceLog拦截器。 However Encryptor is created explicitly with the new keyword, someModule is not injected, and @TraceLog is not called. 但是,将使用new关键字显式创建Encryptor,不会注入someModule,并且不会调用@TraceLog。

CDI supports programmatic creation of beans, but only for classes with a parameterless non-private constructor. CDI支持以编程方式创建bean,但仅适用于具有无参数非私有构造函数的类。 Examples: 例子:

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 . Spring支持使用AspectJ注入到使用new关键字创建的对象中 No idea about support for interceptors on constructors and methods though. 尽管不知道在构造函数和方法上支持拦截器。

@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 () {
        ...
    }

}

Is there any similar solution to CDI/Weld? CDI / Weld是否有任何类似的解决方案? Or should I resort to using Spring? 还是应该使用Spring? Does it support constructor and method interceptors? 它是否支持构造函数和方法拦截器?

Let's start with few remarks... 让我们从几句话开始...

and it can not handle constructor parameters at all 它根本无法处理构造函数参数

Wrong. 错误。 It's called constructor injection .The only limitation being that all parameters have to be resolvable CDI beans. 这称为构造函数注入 。唯一的限制是所有参数必须是可解析的CDI bean。

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

CDI/Weld does not inject dependencies or run interceptors on objects created with new CDI / Weld不会在使用new创建的对象上注入依赖项或运行拦截器

Yes, not by default. 是的,默认情况下不是。 But it is achievable via BeanManager.createInjectionTarget(...).inject(...) Not a go-to way to convert an existing application though! 但是可以通过BeanManager.createInjectionTarget(...).inject(...)实现,但是这不是转换现有应用程序的首选方法!

NOTE: the above code will only enable injection. 注意:上面的代码将仅启用注入。 Not interception. 不拦截。 For that would perhaps need to use InterceptionFactory . 为此,可能需要使用InterceptionFactory You shouldn't need either for your problem though. 不过,您也不需要解决任何一个问题。

CDI supports programmatic creation of beans... CDI支持以编程方式创建bean ...

What you described with your code ( Instance<T> ) is not creation but rather a dynamic/programmatic lookup . 用代码( Instance<T> )描述的不是创建而是动态/程序化查找 It adheres to the same resolution rules as @Inject only allowing you to make it dynamic and not set in stone. 它遵循与@Inject相同的分辨率规则,仅允许您使其动态而不固定。 If you speak of creation, you may mean producer methods ? 如果说创造,您可能是指生产者方法

Now, to your problem... If I get it correctly, the only problem is that the constructor of Encryptor has parameters. 现在,解决您的问题...如果我正确理解,唯一的问题是Encryptor的构造函数具有参数。 Well, then you need to make sure those parameters can be injected in some way. 好吧,那么您需要确保可以以某种方式注入那些参数。 Since they are all three of type String , you will need to either wrap them in some bean or use qualifiers so that typesafe resolution does not blow up with ambiguous resolution when you have multiple beans of type String . 由于它们都是String类型的三个,因此您将需要将它们包装在某些bean中或使用限定符,以便当您有多个String类型的bean时, typesafe解析不会因模棱两可的解析而String

Here is how the constructor would look like with qualifier-based solution. 这是基于限定符的解决方案的构造函数的外观。 @Output , @Input and @Key are all qualifiers : @Output@Input@Key都是限定词

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

Here is an example of one of these qualifiers: 这是这些限定词之一的示例:

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

And finally, you need to produce those String beans, that you can do with producer methods, mentioned above. 最后,您需要产生可以用上述生产者方法完成的String豆。 Here is one (exception the logic to grab that value as I cannot know how you do that): 这是一个(获取该值的逻辑除外,因为我不知道您是怎么做到的):

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

If you want CDI Injection, you must avoid using the new operator. 如果要进行CDI注入,则必须避免使用new运算符。

This this how I would have find a solution to your design problem 这就是我如何找到您的设计问题的解决方案

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

The main things about this code are : 关于此代码的主要内容是:

  • using scope annotations ApplicationScoped and Dependent 使用范围注释ApplicationScopedDependent
  • providing init() method on your Encryptor class which will take runtime arguments 在您的Encryptor类上提供init()方法,该方法将使用运行时参数
  • returning the instance of Encryptor at the end of init() to be able to call the run() method which is protected to avoid direct call (It could be also private , I think) without calling init() first init()的末尾返回Encryptor的实例,以能够调用run()方法,该方法受保护以避免直接调用(我认为也可以是private ),而无需先调用init()

It should work with this design. 它应与此设计一起使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM