Reputation: 6757
I have a class that needs to be initialized dynamically like this:
void doSth(classsuffix) throws Exception {
String classname = "org.test.classname" + classsuffix; // classsuffix is dynamic
Class<?> clazz;
clazz = Class.forName(classname);
TestInterface test = (TestInterface) clazz.newInstance();
test.doStuff();
}
Paired with a example class (one of many following the same pattern):
public class classnameOne implements TestInterface {
@Inject
private Logger log;
// ...
@Override
public void doStuff() {
// Do stuff
log.info("done");
}
}
The problem is that log
in the classnameOne
class will be null
when initialized and the log.info()
call will therefore throw a NullPointerException.
I need that logger to be there though, so is there any possibility to initialize injected properties when creating the class with newInstance()
?
Or is there any other possibility to create objects dynamically based on a string?
Upvotes: 0
Views: 780
Reputation: 6757
Use the CDI.current() object:
class TestClass {
@Inject
ClassnameCollection collection; // Inject
void doSth(classsuffix) throws Exception {
dynamicObject = CDI.current().select(
(Class<TestInterface>) Class.forName("org.test.Classname" + suffix)).get();
dynamicObject.doStuff();
}
}
An example class for reference:
public class ClassnameOne implements TestInterface {
@Inject
private Logger log;
// ...
@Override
public void doStuff() {
// Do stuff
log.info("done");
}
}
With this solution there is no need for any changes to existing classes or something like that.
The best solution I could possibly find is something like this:
Create a collection of all the available classes:
public class ClassnameCollection {
@Inject
public ClassnameOne classnameOne;
@Inject
public ClassnameTwo classnameTwo;
// ...
}
And inject it in the class you want the dynamic class:
class TestClass {
@Inject
ClassnameCollection collection; // Inject
void doSth(classsuffix) throws Exception {
Class collectionClass = ClassnameCollection.class;
Field collectionField = collectionClass.getDeclaredField("classname" + suffix); // Get the declared field by String
TestInterface dynamicObject = (TestInterface) collectionField.get(collection); // There you have the dynamic object with all the subclasses (for example Logger) initialized
dynamicObject.doStuff();
}
}
An example class for reference:
public class ClassnameOne implements TestInterface {
@Inject
private Logger log;
// ...
@Override
public void doStuff() {
// Do stuff
log.info("done");
}
}
I honestly think this is the best possible solution, because it does not change any of the subclasses and the maintaining for this is pretty easy (Just add a new Inject
in the ClassnameCollection
class).
Upvotes: 0
Reputation: 2594
First of all, you're using CDI, so you need a bean.xml file to be in META-INF even if the file is completely empty, otherwise it won't work.
Example:
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>
Then, you want to Inject your logger, but you need a Producer for it, an easy one would be:
public class LoggerProducer {
@Produces
public Logger produceLogger(InjectionPoint injectionPoint) {
return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
}
Important also to add the transient keyword to your logger attribute because produces cannot produce non-serializable instances.
public class classnameOne implements TestInterface{
@Inject
private transient Logger log;
// Some more functions and stuff
}
Interesting readings:
Update
If you insist into use the Class::newInstance() method, then you can do it in the following way:
Add a method to your TestInterface that would return a TestInterface object and name it getInstance()
public interface TestInterface {
public TestInterface getInstance();
}
Implement that method in each of your classes
public class classnameOne implements TestInterface {
@Inject
private transient Logger log;
public TestInterface getInstance() {
return new classnameOne();
}
}
Just add to your previous code the new way to retrieve a concrete instance using the constructor (that will make the proper dependency injections):
void doSth(classsuffix) throws Exception {
String classname =
"org.test.classname"+classsuffix; //classsuffix is dynamic
Class<?> clazz;
clazz = Class.forName(classname);
TestInterface test = ((TestInterface) clazz.newInstance()).getInstance();
}
It's not beautiful and it smells a lot, but it does exactly what you want.
PD: The inject annotation doesn't work with Constructor::newInstance() nor with Class::newInstance(), so I guess that this would be the closest approach to what you wanted to do.
Upvotes: 1
Reputation: 1475
AutowireCapableBeanFactory
may be helpful to your question, but note that this approach is kind of smelly and not advised for typical use cases. Refer to this link:
https://stackoverflow.com/a/52355649/6223518
Upvotes: 0