【问题标题】:Field.get(obj) returns all nulls on injected CDI managed beans, while manually invoking getters return correct valuesField.get(obj) 在注入的 CDI 托管 bean 上返回所有空值,同时手动调用 getter 返回正确的值
【发布时间】:2015-06-21 11:02:29
【问题描述】:

我正在尝试通过反射从 JSF 页面的支持 bean 访问某些字段的值。问题是,当我使用 getter 时,我得到了正确的值,但是当我使用必要字段的 get(obj) 方法时,我总是得到一个 null 值返回。

获取beanObject:

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

要在不使用 getter 的情况下获取字段值,我执行以下操作:

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

要使用 getter 获取值,我执行以下操作:

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

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

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

我可以进一步提到的是,我尝试访问的字段是用 @Inject 注释注入的,但我不认为这是问题,因为其他实例字段(未注入)会受到同样的影响。

通常我会使用 getter,但我在这里尝试做的事情会对我正在开发的应用程序产生全局影响,这意味着返回并修改所有受影响的类以提供 getter 是最后的解决方案。此外,这个应用程序会不断地修改和扩展,我不想冒其他开发人员不提供 getter 的机会,这会导致严重的问题。

谢谢!

【问题讨论】:

  • Java 被滥用最多的 API 反射很多时候只是特定于框架的。如果不是真的需要,请避免使用它。
  • 如果是通过注入的方式,您如何获取 beanObject,它可能已被代理,因此您可能无法获得任何字段值。
  • 我以获取 beanObject 的方式进行了编辑,我认为您使用代理是正确的。有什么办法可以克服吗?

标签: java jsf reflection cdi managed-bean


【解决方案1】:

这确实是预期的行为。 CDI 托管 bean 实例本质上是自动生成类的可序列化代理实例,它扩展了原始支持 bean 类并通过公共方法将所有公共方法进一步委托给实际实例(就像 EJB 的工作方式一样)。自动生成的类大致如下:

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 来内省 bean 并在 bean 实例上调用 getter/setter。

这是一个启动示例:

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 spec,因此您无需摆弄原始反射 API 并计算/猜测正确的方法名称。


与具体问题无关,取决于具体的功能要求,您可能错误地认为这一切都是正确的解决方案,您在问题中没有说明任何内容,那里实现它的方法可能比内省 bean 实例更好。

【讨论】:

    【解决方案2】:

    我怀疑 bean 正在被 CDI 和/或 JSF 实现代理。

    没有可靠的方法来解决这个问题,因为代理实现是特定于服务器的。代理是在运行时或应用程序部署时间生成的,并且至少对于某些实现(例如焊接)代理没有对 bean 本身的引用,但对获取 bean 并调用相应方法所需的内部类有引用。

    关于我能想到的唯一方法是放宽您的属性的安全性,并希望将属性复制到可靠的代理中。

    所有这些都违背了 JavaEE 的精神,并且违反了面向对象的所有规则,所以我强烈建议不要这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-15
      • 1970-01-01
      • 1970-01-01
      • 2016-07-08
      • 2017-07-07
      • 1970-01-01
      • 2019-06-03
      • 1970-01-01
      相关资源
      最近更新 更多