class.newInstance创建对象,其所有属性均为空

时间:2019-05-14 08:28:00

标签: java java-ee reflection

我有一个需要像这样动态初始化的类:

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();
}

与示例类配对(遵循相同模式的许多示例之一):

public class classnameOne implements TestInterface {

    @Inject
    private Logger log;

    // ...

    @Override
    public void doStuff() {
        // Do stuff 

        log.info("done");
    }
}

问题在于log类中的classnameOne在初始化时将被null进行,因此log.info()调用将引发NullPointerException。

我需要那个记录器,所以在用newInstance()创建类时是否有可能初始化注入的属性?

或者还有其他可能性可以基于字符串动态创建对象吗?

3 个答案:

答案 0 :(得分:1)

首先,您正在使用CDI,因此即使该文件完全为空,也需要将bean.xml文件放入META-INF中,否则它将无法正常工作。

示例:

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

然后,您想注入记录器,但需要一个生产者,一个简单的例子是:

public class LoggerProducer {
    @Produces
    public Logger produceLogger(InjectionPoint injectionPoint) {
        return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
    }
}

同样重要的是,将临时关键字添加到您的logger属性中,因为Produce无法产生不可序列化的实例。

public class classnameOne implements TestInterface{

    @Inject
    private transient Logger log;

// Some more functions and stuff

}

有趣的读物:

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

更新

如果您坚持使用Class :: newInstance()方法,则可以通过以下方式进行操作:

  1. 向您的TestInterface添加一个方法,该方法将返回一个TestInterface对象并将其命名为getInstance()

    public interface TestInterface {
        public TestInterface getInstance();
    }
    
  2. 在您的每个类中实现该方法

    public class classnameOne implements TestInterface {
        @Inject
        private transient Logger log;
    
        public TestInterface getInstance() {
            return new classnameOne();
        }
    }
    
  3. 只需在以前的代码中添加使用构造函数检索具体实例的新方法(将进行适当的依赖注入):

    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();
    
    }
    

它不漂亮,闻起来很香,但它确实可以满足您的要求。

PD:注入注释不适用于Constructor :: newInstance()或Class :: newInstance(),所以我想这将是您想要做的最接近的方法。

答案 1 :(得分:0)

AutowireCapableBeanFactory可能对您的问题有所帮助,但请注意,这种方法有点臭,不建议在典型的用例中使用。请参阅此链接:

https://stackoverflow.com/a/52355649/6223518

答案 2 :(得分:0)

我找到了一个更好的解决方案

使用CDI.current()对象:

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();
    }
}

供参考的示例类:

public class ClassnameOne implements TestInterface {

    @Inject
    private Logger log;

    // ...

    @Override
    public void doStuff() {
        // Do stuff 

        log.info("done");
    }
}

使用此解决方案,无需对现有类进行任何更改。

旧版本

我可能找到的最佳解决方案是这样的:

创建所有可用类的集合:

public class ClassnameCollection {
    @Inject
    public ClassnameOne classnameOne;
    @Inject
    public ClassnameTwo classnameTwo;

    // ...
}

并将其注入到您要使用动态类的类中:

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();
    }
}

供参考的示例类:

public class ClassnameOne implements TestInterface {

    @Inject
    private Logger log;

    // ...

    @Override
    public void doStuff() {
        // Do stuff 

        log.info("done");
    }
}

老实说,这是最好的解决方案,因为它不会更改任何子类,并且维护非常简单(只需在Inject类中添加新的ClassnameCollection)。