Spring,NotReadablePropertyException和Glassfish版本


问题内容

我正在使用Spring MVC的Web应用程序上工作。

它在Glassfish 3.0.1上一直运行良好,但是当迁移到Glassfish
3.1时,它开始表现出奇怪的效果。有些页面仅部分显示或完全不显示,并且在日志中,很多此类消息:

    [#|2012-08-30T11:50:17.582+0200|WARNING|glassfish3.1|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=69;_ThreadName=Thread-1;|StandardWrapperValve[SpringServlet]: PWC1406: Servlet.service() for servlet SpringServlet threw exception
    org.springframework.beans.NotReadablePropertyException: Invalid property 'something' of bean class [com.something.Something]: Bean property 'something' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
        at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:729)
        at org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper(BeanWrapperImpl.java:576)
        at org.springframework.beans.BeanWrapperImpl.getBeanWrapperForPropertyPath(BeanWrapperImpl.java:553)
        at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:719)
        at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99)
        at org.springframework.validation.AbstractBindingResult.getFieldValue(AbstractBindingResult.java:226)
        at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:120)
        at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:178)
        at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:198)
        at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:164)
        at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:127)
        at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:421)
        at org.springframework.web.servlet.tags.form.TextareaTag.writeTagContent(TextareaTag.java:95)
        at org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:102)
        at org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:79)

错误消息是不正确的,因为有问题的属性没有设置方法(通过构造函数获取其值)。但是就像我说的那样,当使用Glassfish
3.0.1时,这并不是问题,仅当在具有Glassfish 3.1的新服务器上使用它时才是问题。

有人知道Glassfish版本中是否有可能导致此问题的消息?还是新服务器上缺少某种配置?

一些代码:

控制器:

@ModelAttribute
public SomethingContainer retriveSomethingContainer(@PathVariable final long id {
    return somethingContainerDao.retrieveSomethingContainer(id);       
}

@InitBinder("somethingContainer")
public void initBinderForSomething(final WebDataBinder binder) {
    binder.setAllowedFields(new String[] {
        "something.title",
        "something.description",
    });
}

SomethingContainer:

@Embedded
private final Something something = new Something();

public Something getSomething() {
    return something;
}
//no setter

public String getDescription() {
    return something.getDescription();
}

更新:

重新启动Glassfish实际上可以暂时解决问题。我怀疑这可能与自定义活页夹的加载有关,我们遇到了内存不足错误的问题,我认为这与它有关,但是在未解决此问题的情况下已得到解决。

更新2:

在3.0.1服务器上,jvm参数之一是-client。在3.1服务器上,它是-server。我们将其更改为-
client,这使得错误的发生率降低了很多,它与-server每隔一天发生一次,而-client则花费了2周的时间。

更新3:

有关服务器的一些信息(如果需要,可以添加更多信息。)

Server1(正在运行的服务器):

Windows Server 2003
Java jdk 6 build 35
Glassfish 3.0.1 build 22
-xmx 1024m

Server2(有问题的服务器):

Windows Server 2008 64-bit
Java jdk 6 build 31
Glassfish 3.1 build 43
-xmx 1088m
-xms 1088m

我们正在使用Spring版本3.1.0。

更新4:

我通过将jsp中的字段重命名为modelattribute中不存在的字段来重新创建错误。

但是,更重要的是,我注意到了一些东西:系统找不到吸气剂的字段通常是model属性引用的超类的字段。继续我的示例,SomthingContainer确实是这样的:

public class SuperSomethingContainer {
    [...]
    private Something something;
    public Something getSomething() {
        return something;
    }
}

public class SomethingContainer extends SuperSomethingContainer {
    [...]
}

控制器中的引用保持不变,因此它引用的是该对象超类中的字段。

更新5:

错误发生后,我尝试使用调试器连接到生产服务器。我在控制器方法的return语句上设置了一个断点,该方法返回带有错误的对象,并尝试查看我是否可以同时访问有问题的字段。而且我可以,所以问题必须出在Spring
MVC /生成的jsp类之内。

(此外,错误字段的类型为“ someobject.something [0] .somethingelse [0]”,但是当someselse-
list为空时,没有错误!对我来说,这意味着它某种程度上无法找到列表的获取方法(?)

更新6:

问题似乎与从jsps生成Java类有关。部署时我们没有使用预编译jsps,因此它们是在首次使用时进行编译的。第一次访问页面并编译jsp时会出现此问题。我还注意到,一旦出现问题,之后编译的jsps都会给出错误。我保留了一些问题生成的Java文件,在下次重新启动时,我会将它们与工作文件进行比较。越来越近
:)

更新7:

将导致错误的已编译jsp java文件与未导致错误的jsp java文件进行了比较,两者之间没有差异。这样就可以了。

因此,我现在知道离开控制器的Java对象很好(通过调试器检查),并且从jsp生成的Java类也很好。所以这一定是介于两者之间,现在我需要找出什么…

更新8:

另一轮调试,将问题缩小了更多范围。事实证明,spring对属于各个类的属性进行了一些缓存。在org.springframework.beans.BeanWrapperImpl方法getPropertyValue中,包含以下内容:

private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
    String propertyName = tokens.canonicalName;
    String actualName = tokens.actualName;
    PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
    if (pd == null || pd.getReadMethod() == null) {
        throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
    }

问题是cachedIntrospectionResults不包含所讨论的属性,但是包含了该类的所有其他属性。将需要挖掘更多内容以尝试找出丢失的原因,从一开始就丢失的内容,或者丢失的内容。

另外,我注意到缺少的属性是那些没有setter的属性,只有getter的属性。而且,它似乎是上下文感知的,如stacktrace所示。因此,在访问一个页面时未找到属性并不意味着在访问另一页面时不可用。

更新9:

改天,更多调试。居然发现了一些好东西。上一个代码块中的getCachedIntrospectionResults()调用调用CachedIntrospectionResults#forClass(theClassInQuestion)时很麻烦。这返回了一个CachedIntrospectionResults对象,其中包含与预期的所有属性相距甚远的对象(21个中的11个)。进入forClass方法,我发现:

static CachedIntrospectionResults forClass(Class beanClass) throws BeansException {
    CachedIntrospectionResults results;
    Object value = classCache.get(beanClass);
    if (value instanceof Reference) {
        Reference ref = (Reference) value;
        results = (CachedIntrospectionResults) ref.get();
    }
    else {
        results = (CachedIntrospectionResults) value;
    }
    if (results == null) {
    //build the CachedIntrospectionResults, store it in classCache and return it.

原来,返回的CachedIntrospectionResults是由classCache.get(beanClass)找到的。因此,存储在classCache中的内容已损坏/没有包含所有应有的内容。我在classCache.get(beanClass)行上放置了一个断点,并尝试通过调试器运行它:

classCache.put(beanClass,null);

当方法完成并重建CachedIntrospectionResults时,事情又开始起作用。因此,存储在classCache中的内容与允许重建的内容不同步。这是否是由于第一次构建时出错,还是classCache在当前我不知道的地方被破坏了。

我开始怀疑这与类加载器有关,因为在更新Glassfish时,由于类加载器工作方式的变化,我以前曾遇到过问题。


问题答案:

我的一位同事调查了这个错误,他能够在单元测试中重新创建它。这是通过调用为类构建CachedIntroSpectionResults的方法来完成的,同时通过在内存设置非常低的情况下向内存中添加字符串来对jvm施加压力。这种方法使其失败20/30000次。

至于原因,我只作了口头解释,所以我没有所有的细节,但这是这样的:Java有自己的自省结果,这些都由Spring封装。问题在于java结果使用软引用,这使它们易于出现垃圾回收。因此,当Spring恰好在垃圾收集器运行的同时,围绕这些软引用构建其包装器时,实际上清除了Spring使用的某些基础,从而导致属性“丢失”。

解决方案似乎是从Spring 3.1.0.RELEASE升级到Spring
3.1.3.RELEASE。这里有一些变化,Spring在确定类的属性时不再包装软引用(软引用在极少数特殊情况下使用,而不是一直使用)。升级Spring版本后,通过单元测试无法重现该错误,仍然可以通过实际使用来查看是否是这种情况。

更新:已经有几个星期了,没有错误的迹象。所以更新Spring版本有效:)