javareflectioncdi

java.lang.reflect.field.set(obj,value) fails for applicationscoped


The following code runs successfully but the value isn't changed when the reference is an applicationscoped bean. When the bean is a Singleton EJB the value does change.

Changing the value via AnnotatedParameter and Method.invoke does change the value.

Does anyone know what might cause the difference in behaviour? I cannot find the reason in (java)docs, specs or elsewhere.

InjectionPoint ip = ....;                
Class bc = ip.getMember().getDeclaringClass();
Object reference = CDI.current().select(bc).get();
Annotated a = ip.getAnnotated();
Object value = ....;
if (annotated instanceof AnnotatedField af) {
    Field f = af.getJavaMember();
    try {
        boolean ac = f.canAccess(reference);
        f.setAccessible(true);
        f.set(reference, value);
        f.setAccessible(ac);
    } catch (IllegalAccessException e) {
        log.error(String.format("error updating %s with %s",
                        f, value));
    }
}

Solution

  • With CDI, the actual instance that's injected is a proxy. It's an instance of a generated sub class of the bean class. That's why CDI has some limitations like requiring a no-arg constructor and no final methods. The generated sub class has a no-arg constructor that calls the no-arg constructor of the bean class (super()), and each method is overridden. Inside the proxy there is (in some way) a reference to the single instance; the overridden methods each call the same method of this instance.

    The reference you have has all of the fields of the bean class, but if you inspect them in a debugger you'll probably see they are all null. That's my experience so far. The only field that matters is the internal reference to the single instance. The field you set is simply ignored by the proxy.

    With @Singleton on the other hand, both in JBoss and in Quarkus, there is no proxy. The injected instance is the singleton instance itself.

    Some links that document this: