Hatena::ブログ(Diary)

あさとの @drillbits このページをアンテナに追加 RSSフィード

2010/10/15

Google App Engine for Java 本番環境で NoClassDefFoundError

開発環境でさくさくっと開発して鼻歌交じりにデプロイ&画面確認したら 500 ERROR …

java.lang.NoClassDefFoundError: com/google/apphosting/runtime/security/shared/stub/java/net/InetAddress

NoClassDefFoundError って? と思ったら、クラス名を見るにホワイトリストにない(使用が禁止されている)クラスっぽい。

でもログが指し示す箇所を見る限り、前回デプロイ時と変えてない…っていうかこれライブラリだし!

というわけで該当のコードを見てみました。ちなみにライブラリJSONIC という POJO <-> jsonデコード/エンコードができる素敵ライブラリです。
Seasar.org の Maven リポジトリから取得しているので、バージョンは1.2.0。

net.arnx.jsonic.JSON#preformat

protected Object preformat(Context context, Object value) throws Exception {
  Object data = null;
  
  JSONHint hint = context.getHint();
  
  if (value == null 
    || value instanceof CharSequence
    || value instanceof Character
    || value instanceof Boolean
    || value instanceof Map<?, ?>
    || value.getClass().isArray()
    || value instanceof Iterable<?>
    || value instanceof Iterator<?>
    || value instanceof Enumeration<?>
    || value instanceof Element
  ) {
    data = value;

  /* 中略 */

  } else if (value instanceof InetAddress) {  // ここ(★)で NoClassDefFoundError
    data = ((InetAddress)value).getHostAddress();
  } else if (value instanceof Charset) {

  /* 中略 */

  } else {
    data = value;
  }
  
  return data;
}

見た感じ、value をひたすら instanceof 比較して型ごとに処理してるみたいですね。

で、実際に開発環境で実行して、該当箇所(★)にブレークポイントを指定してみたところ、そこに達した時の value の型が com.google.appengine.api.datastore.Key でした…

つまり、★以前の instanceof 比較 で引っかからなかったのでここに達した結果、NoClassDefFoundError が吐かれたってことですね。

というか、ホワイトリストにない型でも、import しただけでは NoClassDefFoundError って吐かれないんですね…
参考 via / @nekop

JSONIC は appengine に特化されたライブラリではないので、ホワイトリストにないクラスを使ってるのはしょうがない、ということで JSON#encode に渡す POJO の中身を気をつけるしかないのかなー

ちなみに、slim3 のモデルは POJO なので最初はそのまま渡してたのですが、ModelRef なんかがあると JSON#encode にやたら時間がかかったり、OutOfMemoryError になったりするので、ぼくはこんな風にしてます。

// Foo model;
com.google.appengine.api.datastore.Entity e = FooMeta.get().modelToEntity(model);
Map<String, Object> result = e.getProperties();
/*
 * Entity#getProperties で返ってくる Map は UnmodifiableMap なので、
 * 上記のKeyとかをMap#removeしたりするとき、UnsupportedOperationException が発生するため…
 */
result = new HashMap<String, Object>(result);
// 以下略

ngtnngtn 2010/10/15 23:40 > ModelRef なんかがあると JSON#encode にやたら時間がかかったり、OutOfMemoryError になったりする

slim3に詳しくないので外してるかもしれませんが、モデルとModelRefが循環参照になっているのであればJSON.encode(e)の部分を、new JSON(2).format(e)のようにフォーマットする階層を指定すればOutOfMemoryErrorを回避できるかも知れません。

s2jdbcでテーブル結合して取得したデータをJSONIC#encodeに投げてOutOfMemoryErrorが発生した時には、この方法で回避できました。

arnarn 2010/10/16 00:49 InetAdress をReflectionで参照するようにして環境に依存しないようにしたバージョンを作ってみました。大変お手数なのですが、問題なく動作するかご確認いただくこと可能でしょうか。

http://sourceforge.jp/projects/jsonic/releases/
から jsonic-1.2.4-beta1.zip をダウンロードください。

> ModelRef なんかがあると JSON#encode にやたら時間がかかったり、OutOfMemoryError になったりする

jsonicは一段階しか循環参照を解決しないので、二階層にまたがった場合はngtnさんがおっしゃるように、maxDepthを制限するしかないのですよね……。階層のオブジェクトを保持してチェックすればよいのかなぁ。

arnarn 2010/10/16 15:22 > 大変お手数なのですが、問題なく動作するかご確認いただくこと可能でしょうか。

どうもGAEではクラスローダー関連のAPIにアクセスできないようで1.2.3だと動作しないようです。クラスローダーも使わないような方法を考えて再度出直したいと思います(泣)

ms2satoms2sato 2010/10/16 22:26 jsonic-1.2.4-beta1.zipを私の環境で組み込んだところ、正常動作が確認できました。もう既に動作情報が入っているかもしれませんが、ご参考までに。

こちらの皆さんの情報が大変参考になりました。ありがとうございました。

arnarn 2010/10/19 02:27 本日、InetAddressとgetSystemClassLoaderの対応を行ったJSONIC 1.2.4 ベータ2をリポジトリにアップしました(http://sourceforge.jp/projects/jsonic/releases/)。特に不具合報告などないようであれば、数日中に同内容を正式版としてリリースする予定です。

drillbitsdrillbits 2010/10/20 10:15 うわあ、見てない間にものすごく話が進んでいる!すみません!

> ngtnさん
階層指定できるんですね、ありがとうございます
ただ、ModelRefは該当のModelに関連するModelのKeyだけもってて、肝心の中身の方はLazyLoadなので、やっぱりそのままModelを突っ込むということはできないですね…
なので関連するModelは個別にLazyLoad→Map化して、元のModelのMapしたやつにputしてます

> ms2sato さん
> arn さん
ご確認や対応などありがとうございます
ぼくもこれから試してみます

drillbitsdrillbits 2010/10/20 13:14 とりあえず簡単に記事のバージョン(1.2.0)と最新(1.2.4)で確認してみました

1.2.4ではInetAddressのところの問題起きませんでした
Modelをそのまま突っ込んでいけます

ちなみに、記事ではKeyとか言っちゃいましたけど、そもそもModelをそのまま突っ込んだ場合はModel自体がUnknownな型なのでInetAddressの判定にところに達しちゃいますね
ModelRefのところで問題ありそうだと思って、ModelそのものではなくMapにしてから突っ込んでたので気づきませんでした…

あと、ModelRefでOutOfMemoryErrorとか言うのも大嘘でした
ModelRefはそれなりに中身を含めて出力してくれてますね
問題が起きるのはInverseModelListRefのほうでした

こっちはslim3の中身をもうちょっと調べないとわからないですね…

まーくんまーくん 2011/03/09 03:26 こんにちわ、こんばんわ。。。

ここに行き着き来ました。

当方も、テスト(ローカル)ではうまくいっても、本番(Linux)環境だとjava.lang.NoClassDefFoundError: net.arnx.jsonic.JSON
でExceptionになって困ってました。

最初はJSON-libでの実装をしましたが、うまくいかず、しゃーないJSONICだ!と思い、さくっと実装はできテスト環境=OK、本番=NGっておぃ!!またかいな!!
と嘆いています。

JSONはどうも推奨クラスを利用していないようですね。

実装はBeanオブジェクトにSQLの戻り値(Resultset)をinvokeした結果(Bean object)を
JSON.encode(結果obj)とし、Listに突っ込んでます。ちなみにJson-libのJsonArrayに突っ込んでもみました。が、両方ローカルのみOKという結果です。

いやはや困りましたなぁ。また拝見します。いまからgoogleを徘徊してまいります。

まーくんまーくん 2011/03/09 16:08 ただいま〜

結局、どれもダメで、ソース追っかけ自分で修正しました。

ちなみに、JSONICはそれでも駄目だったので、Json-libで対応しました。

これに3日も費やすとは、、、、

怖い怖い

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/drillbits/20101015/appengine_NoClassDefFoundError