Field.get(obj) returns all nulls on injected CDI managed beans, while manually invoking getters return correct values
Asked Answered
J

2

4

I am trying to access the values of some fields from the backing bean of a JSF page via reflection. The problem is that when I use the getter I get the correct value but when I use the get(obj) method of the necessary fields I always get a null value returned.

Getting the beanObject:

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

To get the fields values without using the getter I do the following:

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

}

The getAllFields method has this implementation:

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

To get the values by using the getter I do the following:

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

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

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

What I can further mention is that the fields I am trying to access are injected with the @Inject annotation, but I don't believe this is the problem as other instance fields, not injected, suffer of the same affection.

Normally I would use the getter but what I am trying to do here has a global impact on the application I am developing, which means that going back and modifying all affected classes to provide getters is a last measure solution. Also this application will be constantly modified and extended and I don't want to take the chance of the other developers not providing the getters, which will result in serious problems.

Thank you!

Jelks answered 15/4, 2015 at 13:2 Comment(3)
The Java's most abused API reflection is many a times only framework specific. Avoid it, if it is not really needed.Isolated
How are you getting hold of the beanObject if it is by injection it may be proxied and so you may not be getting any value for the fields.Mezereum
I edited in the way I get the beanObject and I think you are right with the proxy. Is there any way to get past this?Jelks
L
5

That's indeed expected behavior. The CDI managed bean instance is in essence a serializable proxy instance of an autogenerated class which extends the original backing bean class and delegates in all public methods further to the actual instance via public methods (like as how EJBs work). The autogenerated class looks roughly like this:

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

}

As you see, there are no concrete fields. You should also have noticed the autogenerated class signature while inspecting the class itself too.

After all, you're going about this the wrong way. You should be using java.beans.Introspector API to introspect the bean and invoke getters/setters on bean instances.

Here's a kickoff example:

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

This API respects like JSF and CDI the JavaBeans spec, so you don't need to fiddle around with raw reflection API and figuring/guessing the correct method names.


Unrelated to the concrete problem, depending on the concrete functional requirement for which you possibly incorrectly thought that this all would be the right solution, which you didn't tell anything about in the question, there may be even more better ways to achieve it than introspecting the bean instances.

Lyra answered 16/4, 2015 at 10:55 Comment(0)
M
0

I suspect the beans are getting proxied by the CDI and/or JSF implementation.

There is no reliable way of getting around this as the proxy implementation is server specific. Proxies are generated a runtime or application deployment time and at least for some implementations (eg weld) proxies do not have a reference to the bean itself but do have a reference to the internal classes it needs to get the bean and call the corresponding method.

About the only way I can think of doing this is to relax the security on your properties and hope that the prperty gets copied into the proxy reliable.

All of this is against the spirit of JavaEE and breaks all the rules of Object Orientation so I would strongly recommend against it.

Mezereum answered 16/4, 2015 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.