0

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?

MauriceNino
  • 6,214
  • 1
  • 23
  • 60

3 Answers3

1

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:

  1. https://dzone.com/articles/cdi-di-p1
  2. http://www.devsniper.com/injectable-logger-with-cdi/

Update

If you insist into use the Class::newInstance() method, then you can do it in the following way:

  1. Add a method to your TestInterface that would return a TestInterface object and name it getInstance()

    public interface TestInterface {
        public TestInterface getInstance();
    }
    
  2. Implement that method in each of your classes

    public class classnameOne implements TestInterface {
        @Inject
        private transient Logger log;
    
        public TestInterface getInstance() {
            return new classnameOne();
        }
    }
    
  3. 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.

Daniel Campos Olivares
  • 2,262
  • 1
  • 10
  • 17
  • Thanks for answering. I already have all of that and it works when I initialize the object normally, but when I initialize it with `clazz.newInstance();` the property `log` keeps beeing `null`. – MauriceNino May 14 '19 at 09:35
  • Why would you like to use Class.newInstance() instead of use an AbstractFactory to retrieve the concrete class that you want? – Daniel Campos Olivares May 14 '19 at 09:41
  • because the classname depends on user input. There are like 30 classes with amost the same name, but different endings. And the user selects one, clicks submit and then in the code behind it should automatically load the right class. – MauriceNino May 14 '19 at 09:46
  • Hmm... You could do a trick, in your classes you could create a method that will return a proper instance of the class, I'm gonna add it to the previous response to explain that. – Daniel Campos Olivares May 14 '19 at 10:01
  • Thanks, but I would really like to solve it without changing the class because I know it is possible with `ApplicationContext.getBean(String classname)`. I just cant figure out how ApplicationContext does it. – MauriceNino May 14 '19 at 10:42
  • BeanFactory is part of the Spring Framework and I understood that you weren't able to use it in this concrete problem. – Daniel Campos Olivares May 14 '19 at 11:50
  • Thats right. Thats why I want to know how it works and implement it myself. – MauriceNino May 14 '19 at 11:59
  • As I said to you in previous comments, BeanFactory works using an internal Factory design pattern, just that it specify the retrieval contents of the Factory in the Beans.XML More info about: https://www.quora.com/What-is-BeanFactory-in-Spring-Framework-in-Java – Daniel Campos Olivares May 14 '19 at 12:05
  • Thanks for your help. I have found another solution that makes me kind of satisfied because its simple, does not need spring and does not change the original classes. I cant really accept the answer because it does not really solve the problem, but have a upvote for helping me a lot! – MauriceNino May 14 '19 at 12:10
0

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

Dave Pateral
  • 1,415
  • 1
  • 14
  • 21
0

I found a even better solution

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.

Old version

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

Community
  • 1
  • 1
MauriceNino
  • 6,214
  • 1
  • 23
  • 60