Field.get(obj) возвращает все значения null для введенных управляемых компонентов CDI, в то время как вручную вызываемые геттеры возвращают правильные значения

#java #jsf #reflection #cdi #managed-bean

Вопрос:

Я пытаюсь получить доступ к значениям некоторых полей из резервного компонента страницы JSF с помощью отражения. Проблема в том, что когда я использую геттер, я получаю правильное значение, но когда я использую метод get(obj) для необходимых полей, я всегда получаю возвращаемое значение null.

Получение объекта beanObject:

 ELContext elcontext = FacesContext.getCurrentInstance().getELContext();
Object beanObject = FacesContext.getCurrentInstance().getApplication().getELResolver().getValue(elcontext, null, beanName);
 

Чтобы получить значения полей без использования геттера, я делаю следующее:

 List<Field> fields = new ArrayList<Field>();
ParamsBuilder.getAllFields(fields, beanClass);

for(Field field: fields) {

    field.setAccessible(true);
    System.out.println(field.getName()   ": "   field.get(beanObject)); //just to see if it works

}
 

Метод getAllFields имеет такую реализацию:

 public static List<Field> getAllFields(List<Field> fields, Class<?> type) {
    for (Field field: type.getDeclaredFields()) {
        fields.add(field);
    }

    if (type.getSuperclass() != null) {
        fields = getAllFields(fields, type.getSuperclass());
    }

    return fields;
}
 

Чтобы получить значения с помощью геттера, я делаю следующее:

 private ClassX getValue(Object beanObject, Class<?> beanClass) throws Exception {

    Method getter = beanClass.getDeclaredMethod("myMethod",(Class<?>[]) null);

    return (ClassX)getter.invoke(beanObject, (Object[])null);
}
 

Что я могу еще упомянуть, так это то, что поля, к которым я пытаюсь получить доступ, вводятся с аннотацией @Inject, но я не верю, что это проблема, поскольку другие поля экземпляра, не введенные, страдают от той же проблемы.

Обычно я бы использовал геттер, но то, что я пытаюсь здесь сделать, оказывает глобальное влияние на приложение, которое я разрабатываю, а это означает, что возврат и изменение всех затронутых классов для предоставления геттеров-это решение последней меры. Кроме того, это приложение будет постоянно модифицироваться и расширяться, и я не хочу рисковать тем, что другие разработчики не предоставят геттеры, что приведет к серьезным проблемам.

Спасибо!

Комментарии:

1. Наиболее часто используемым отражением API Java во многих случаях является только специфичный фреймворк. Избегайте этого, если это действительно не нужно.

2. Как вы получаете объект beanObject, если он вводится путем инъекции, он может быть проксирован, и поэтому вы можете не получать никакого значения для полей.

3. Я отредактировал то, как я получаю объект beanObject, и я думаю, что вы правы с прокси. Есть ли какой-нибудь способ преодолеть это?

Ответ №1:

Это действительно ожидаемое поведение. Экземпляр управляемого компонента CDI, по сути, является сериализуемым прокси-экземпляром автоматически созданного класса, который расширяет исходный класс резервного компонента и делегирует все общедоступные методы в фактический экземпляр с помощью общедоступных методов (например, как работают EJBS). Автоматически созданный класс выглядит примерно так:

 public CDIManagedBeanProxy extends ActualManagedBean implements Serializable {

    public String getSomeProperty() {
        ActualManagedBean instance = CDI.resolveItSomehow();
        return instance.getSomeProperty();
    }

    public void setSomeProperty(String someProperty) {
        ActualManagedBean instance = CDI.resolveItSomehow();
        instance.setSomeProperty(someProperty);
    }

}
 

Как видите, конкретных полей нет. Вы также должны были заметить автогенерированную подпись класса при проверке самого класса.

В конце концов, ты идешь по этому неправильному пути. Вы должны использовать java.beans.Introspector API для самоанализа компонента и вызова геттеров/сеттеров в экземплярах компонента.

Вот пример для начала:

 Object beanInstance = getItSomehow();
BeanInfo beanInfo = Introspector.getBeanInfo(beanInstance.getClass());

for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
    String name = property.getName();
    Method getter = property.getReadMethod();
    Object value = getter.invoke(beanInstance);
    System.out.println(name   "="   value);
}
 

Этот API уважает, как JSF и CDI, спецификацию JavaBeans, поэтому вам не нужно возиться с необработанным API отражения и вычислять/угадывать правильные имена методов.


Не связанный с конкретной проблемой, в зависимости от конкретного функционального требования, для которого вы, возможно, неправильно подумали, что все это будет правильным решением, о котором вы ничего не сказали в вопросе, может быть даже больше лучших способов его достижения, чем самоанализ экземпляров компонентов.

Ответ №2:

Я подозреваю, что компоненты передаются через CDI и/или реализацию JSF.

Надежного способа обойти это не существует, так как реализация прокси-сервера зависит от конкретного сервера. Прокси создаются во время выполнения или во время развертывания приложения, и, по крайней мере, для некоторых реализаций (например, weld) прокси не имеют ссылки на сам компонент, но имеют ссылку на внутренние классы, необходимые для получения компонента и вызова соответствующего метода.

Пожалуй, единственный способ, который я могу придумать, чтобы сделать это, — ослабить защиту ваших свойств и надеяться, что prperty будет надежно скопирован в прокси-сервер.

Все это противоречит духу JavaEE и нарушает все правила объектной ориентации, поэтому я бы настоятельно рекомендовал этого не делать.