class.newInstance 创建所有属性都为 null 的对象

class.newInstance creates Object with all properties being null

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

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

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

public class classnameOne implements TestInterface {

    @Inject
    private Logger log;

    // ...

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

        log.info("done");
    }
}

问题是 classnameOne class 中的 log 在初始化时将是 null,因此 log.info() 调用将抛出 NullPointerException。

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

或者是否有任何其他可能基于字符串动态创建对象?

首先,你使用的是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());
    }
}

将 transient 关键字添加到记录器属性也很重要,因为 produces 不能产生 non-serializable 个实例。

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 添加一个方法,该方法将 return 一个 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(),所以我猜这将是最接近您想要执行的操作。

AutowireCapableBeanFactory 可能对您的问题有帮助,但请注意,这种方法有点难闻,不建议在典型用例中使用。参考这个link:

我找到了更好的解决方案

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

一个例子class供参考:

public class ClassnameOne implements TestInterface {

    @Inject
    private Logger log;

    // ...

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

        log.info("done");
    }
}

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

旧版本

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

创建所有可用的集合 classes:

public class ClassnameCollection {
    @Inject
    public ClassnameOne classnameOne;
    @Inject
    public ClassnameTwo classnameTwo;
    
    // ...
}

并将其注入 class 你想要动态 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();
    }
}

一个例子class供参考:

public class ClassnameOne implements TestInterface {

    @Inject
    private Logger log;

    // ...

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

        log.info("done");
    }
}

老实说,我认为这是最好的解决方案,因为它不会更改任何子classes,而且维护起来非常容易(只需在ClassnameCollection class).