Reputation: 1724
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?
Upvotes: 2
Views: 1612
Reputation: 652
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 :
ApplicationScoped
and Dependent
init()
method on your Encryptor
class which will take runtime argumentsinit()
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()
firstIt should work with this design.
Upvotes: 1
Reputation: 6753
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;
}
Upvotes: 6