【问题标题】:class.newInstance creates Object with all properties being nullclass.newInstance 创建所有属性为空的对象
【发布时间】:2026-02-15 15:30:02
【问题描述】:

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

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

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

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

或者有没有其他可能基于字符串动态创建对象?

【问题讨论】:

  • 这是the same question as here。简短版本:尽可能切换到构造函数注入。否则,您必须想办法将新实例传递给 CDI,而我不知道该怎么做。
  • @chrylis 我不允许使用弹簧。
  • 那么是哪个机制注入了Logger实例?
  • @f1sh 这就是我的问题。我不知道。我所知道的是我不允许使用 spring 并且必须初始化一个动态类名的对象。
  • 您正在使用 CDI,它实现了与 Spring 完全相同的功能。 (Spring容器甚至可以为你展示的类提供功能。)问题和解决方法是一样的。

标签: java jakarta-ee reflection


【解决方案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());
    }
}

将 transient 关键字添加到您的 logger 属性也很重要,因为 producer 不能产生不可序列化的实例。

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(),所以我想这将是最接近您想要做的方法。

【讨论】:

  • 感谢您的回答。我已经拥有了所有这些,并且当我正常初始化对象时它可以工作,但是当我使用clazz.newInstance(); 初始化它时,属性log 一直在运行null
  • 为什么要使用 Class.newInstance() 而不是使用 AbstractFactory 来检索您想要的具体类?
  • 因为类名取决于用户输入。大约有 30 个类,名称几乎相同,但结局不同。用户选择一个,点击提交,然后在它后面的代码中应该会自动加载正确的类。
  • 嗯...你可以做一个技巧,在你的类中你可以创建一个方法来返回一个适当的类实例,我将把它添加到前面的响应中来解释这一点。
  • 谢谢,但我真的很想在不改变课程的情况下解决它,因为我知道ApplicationContext.getBean(String classname) 是可能的。我只是想不通 ApplicationContext 是如何做到的。
【解决方案2】:

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

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

【讨论】:

  • 谢谢,但我无法使用 spring 来解决这个问题。
【解决方案3】:

我找到了更好的解决方案

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

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

【讨论】: