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 引数の 1 つが -client でした。3.1 サーバーでは、-server でした。-client に変更したところ、エラーの頻度が大幅に減少しました。-server では 1 日おきに発生していましたが、-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 のフィールドの名前を変更して、エラーを再現しました。
しかし、もっと重要なことに、システムがゲッターを見つけられないフィールドは、多くの場合、モデル属性で参照されているもののスーパークラスのフィールドであることに気付きました。私の例を続けると、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 クラスの生成に関係しているようです。デプロイ時にプリコンパイルjspを使用していないため、最初に使用するときにコンパイルされます。この問題は、ページに初めてアクセスし、jsp をコンパイルしたときに発生します。また、問題が発生すると、後でコンパイルされたすべての jsps でエラーが発生することにも気付きました。問題が発生した Java ファイルをいくつか保存しておき、次回の再起動時にそれらを動作中のファイルと比較します。近くなってる :)
更新 7:
エラーが発生したコンパイル済みの 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 に問題のプロパティが含まれていないことですが、クラスの他のすべてのプロパティが含まれています。最初から欠落している場合、または行のどこかで失われている場合は、欠落している理由を突き止めるために、さらに掘り下げる必要があります。
また、欠落しているプロパティは、セッターを持たず、ゲッターのみであることに気付きました。また、スタックトレースが示すように、コンテキストに対応しているようです。したがって、あるページにアクセスしたときにプロパティが見つからないということは、別のページにアクセスしたときにそのプロパティが利用できないという意味ではありません。
更新 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 の更新時にクラスローダーが動作する方法の変更による問題を以前に経験したため、これはクラスローダーと関係があるのではないかと疑い始めています..