简体   繁体   中英

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. I want to use interceptors and dependency injection among other CDI features. 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/Weld does not inject dependencies or run interceptors on objects created with new, and it can not handle constructor parameters at all. 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. However Encryptor is created explicitly with the new keyword, someModule is not injected, and @TraceLog is not called.

CDI supports programmatic creation of beans, but only for classes with a parameterless non-private constructor. 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 . 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? Or should I resort to using 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.

@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

Yes, not by default. But it is achievable via BeanManager.createInjectionTarget(...).inject(...) Not a go-to way to convert an existing application though!

NOTE: the above code will only enable injection. Not interception. For that would perhaps need to use InterceptionFactory . You shouldn't need either for your problem though.

CDI supports programmatic creation of beans...

What you described with your code ( Instance<T> ) is not creation but rather a dynamic/programmatic lookup . It adheres to the same resolution rules as @Inject only allowing you to make it dynamic and not set in stone. 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. 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 .

Here is how the constructor would look like with qualifier-based solution. @Output , @Input and @Key are all qualifiers :

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

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
  • providing init() method on your Encryptor class which will take runtime arguments
  • 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

It should work with this design.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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