我正在开发一个使用 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)
该错误消息并非不正确,因为相关属性没有 setter 方法(通过构造函数获取其值)。但正如我所说,使用 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();
}
Update:
重新启动 Glassfish 实际上可以暂时解决该问题。我怀疑这可能与加载自定义活页夹有关,我们遇到了一些内存不足错误的问题,我认为这与此有关,但已修复但没有修复此问题。
更新2:
在 3.0.1 服务器上,jvm 参数之一是 -client。在 3.1-server 上,它是 -server。我们将其更改为-client,这使得错误的频率下降了很多,使用-server时每隔一天就会发生一次,使用-client时花了两周时间才发生。
更新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 中不存在的字段来重新创建错误。
但是,更重要的是,我注意到了一些事情:系统找不到 getter 的字段通常是 modelattribute 中引用的字段的超类字段。继续我的示例,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]”,但是当somethingelse-list为空时,没有错误!对我来说,这意味着它不能以某种方式查找列表的 get 方法(?))
更新6:
问题似乎与从 jsps 生成 Java 类有关。我们在部署的时候没有使用预编译jsps,所以第一次使用的时候就编译了。第一次访问页面并编译 jsp 时会出现此问题。我还注意到,一旦出现这个问题,之后编译的jsp都会报错。我保留了一些问题生成的 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 的属性。而且,正如堆栈跟踪所示,它似乎具有上下文感知能力。因此,在访问一个页面时未找到某个属性并不意味着在访问另一个页面时该属性不可用。
更新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 时遇到过由于类加载器工作方式发生变化而出现的问题。