class.newInstance создает объект со всеми свойствами, равными нулю - PullRequest
0 голосов
/ 14 мая 2019

У меня есть класс, который нужно динамически инициализировать, как это:

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 ]

1 голос
/ 14 мая 2019

Прежде всего, вы используете 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, поскольку производящий не может создавать несериализуемые экземпляры.

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: Injectаннотация не работает ни с Constructor :: newInstance (), ни с Class :: newInstance (), поэтому я предполагаю, что это будет наиболее близким подходом к тому, что вы хотели сделать.

0 голосов
/ 14 мая 2019

Я нашел еще лучшее решение

Используйте объект 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).

0 голосов
/ 14 мая 2019

AutowireCapableBeanFactory может быть полезным для вашего вопроса, но обратите внимание, что этот подход является вонючим и не рекомендуется для типичных случаев использования.Ссылка на эту ссылку:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...