MauriceNino
MauriceNino

Reputation: 6757

class.newInstance creates Object with all properties being null

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

Answers (3)

MauriceNino
MauriceNino

Reputation: 6757

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

Upvotes: 0

Daniel Campos Olivares
Daniel Campos Olivares

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:

  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.

Upvotes: 1

Dave Pateral
Dave Pateral

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

Related Questions