Field.get(obj) 在注入的 CDI 托管 bean 上返回所有空值,同时手动调用 getter 返回正确的值

Posted

技术标签:

【中文标题】Field.get(obj) 在注入的 CDI 托管 bean 上返回所有空值,同时手动调用 getter 返回正确的值【英文标题】:Field.get(obj) returns all nulls on injected CDI managed beans, while manually invoking getters return correct values 【发布时间】: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 的方式进行了编辑,我认为您使用代理是正确的。有什么办法可以克服吗? 【参考方案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 的精神,并且违反了面向对象的所有规则,所以我强烈建议不要这样做。

【讨论】:

以上是关于Field.get(obj) 在注入的 CDI 托管 bean 上返回所有空值,同时手动调用 getter 返回正确的值的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 CDI 进行方法参数注入?

使用CDI注入接口实现

具有 CDI 注入属性的 ViewScoped ManagedBean 上的 NotSerializableException

启用 CDI 注入到由生产者方法创建的 bean

cdi bean 中的资源注入

CDI:由于多重继承和泛型抽象导致的属性注入问题