7

我正在开发一个使用 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",
    });
}

东西容器:

@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 上,它是 -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 中的字段重命名为模型属性中不存在的内容来重新创建错误。

但是,更重要的是,我注意到了一点:系统找不到 getter 的字段通常是模型属性中引用的超类的字段。继续我的例子,SomthingContainer 真的是这样的:

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

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

控制器中的引用保持原样,因此它引用了相关对象的超类中的字段。

更新 5:

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

(此外,错误字段的类型为“someobject.something[0].somethingelse[0]”,但是当 somethingelse 列表为空时,没有错误!对我来说,这意味着它不能查找列表的获取方法(?))

更新 6:

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

更新 7:

将编译后的jsp java文件中导致错误的文件与没有错误的jsp文件进行比较,没有区别。所以这有点忽略了。

所以,我现在知道离开控制器的 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 不包含有问题的属性,但它包含该类的所有其他属性。将需要更多地挖掘以找出它丢失的原因,是否从一开始就丢失了,或者它是否在沿线的某个地方丢失了。

另外,我注意到缺少的属性是那些没有设置器的属性,只有获取器。而且,它似乎是上下文感知的,如堆栈跟踪所示。因此,在访问一个页面时没有找到属性并不意味着在访问另一个页面时它不可用。

更新 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)-line 上放了一个断点,并尝试通过调试器运行它:

classCache.put(beanClass, null);

当允许该方法完成并重建 CachedIntrospectionResults 时,事情又开始工作了。因此,存储在 classCache 中的内容与如果允许重建它将会和应该创建的内容不同步。这是否是由于第一次构建时出现问题,或者 classCache 在我目前不知道的某处损坏。

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

4

4 回答 4

2

可能的原因不止一个。我不确定实际情况,但我可以为您提供找出问题的方法

第 1 步:服务器 2机器上在Glassfish 3.0.1 build 22上部署应用程序,现在如果它在服务器 2 机器上工作正常,则意味着 Glassfish 的库可能存在问题,以下可能是导致此问题的原因

  1. Glassfish 3.1 build 43中缺少的任何库都在Glassfish 3.0.1 build 22中。您可以通过将所有库从工作 Glassfish 服务器复制到新服务器来解决。
  2. 我的 Glassfish 库与春季版本冲突。[我在 tomcat 上遇到过类似的问题,当我将 spring 库从 3.0.1 替换为 3.0.3 时,它对我有用],所以用最新的替换你的 spring 库。

第 2 步: 如果第 1 步的结果是应用程序未在 Glassfish 3.0.1 build 22 上的服务器 2 机器上运行,则可能有以下原因

  1. 如果您粘贴在java lib上的任何库未包含在此服务器计算机中或具有不同的版本。

  2. 在类路径上设置或使用服务器 1 上的任何环境变量的任何文件夹,要么在服务器 2 上不存在,要么没有 jars 或具有不同版本的 jars

于 2012-09-24T10:16:02.457 回答
2

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

至于它的原因,我只得到了口头解释,所以我没有详细说明,但大致是这样的:Java有它自己的introspection-results,这些都是Spring封装的。问题是 java-results 使用了软引用,这使得它们容易被垃圾回收。因此,当 Spring 在垃圾收集器运行的同时围绕这些软引用构建其包装器时,它实际上清除了 Spring 使用的一些基础,导致属性“丢失”。

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

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

于 2013-03-08T11:44:48.630 回答
1

我想我实际上已经找到了一个候选人。

在很短的时间和很少使用之后在其中一个测试服务器上得到错误后,我们对原因进行了一些额外的检查。事实证明,测试服务器只有一半的可用内存,这让我们对它进行了更彻底的研究。原来它并没有用完所有的内存,但是当使用JConsole调查堆上新生空间不同部分的内存使用情况时,结果发现其中一个幸存空间被挤满了。我猜这使得它的一部分溢出,导致溢出的部分被 GC-ed 或由于不在它应该在的地方而无法访问。

我们还没有验证这实际上也是生产环境中的问题,但是一旦错误再次出现,我们将检查,如果是这种情况,我们将更改一些内存设置,以便为生存区留出更多空间新一代堆。(-XX:SurvivorRatio=6 或类似的东西)。

因此,似乎较大的 Spring MVC 应用程序需要较大的幸存者空间,特别是在较新版本的 Glassfish 中。

于 2012-10-09T09:12:49.157 回答
0

实际上,ExtendedBeanInfoSpring 3.1.0 中新引入的类存在问题,该问题已在 Spring 3.1.1 中修复 - 请参阅 ( https://jira.spring.io/browse/SPR-8347 )。

于 2014-11-12T09:44:30.533 回答