java.io.NotSerializableException

問題

java.io.NotSerializableException

とあるオブジェクトをこのtoByte()でbyteに変換しようとしたらエラーがでた。
Javaで大量データをメモリに展開するテクニックの考察 - Symfoware

解決方法

java.io.NotSerializableException - How to solve Not Serializable Exception | Examples Java Code Geeks

import java.io.Serializable;

public class TransientExample implements Serializable {
	private static final long serialVersionUID = 6128016096756071380L;
	private transient Pair pair = null;
	
	public TransientExample(String key, Integer value) {
		this.pair = new Pair(key, value);
	}
	
	@Override
	public String toString() {
		return pair.toString();
	}
}

適当なクラスを作って、
・implements Serializableつける
・問題のあるクラスをtransient で定義しなおす
でエラーきえた。

~~~
あれ、逆か。transientは不要っぽい。

それと、そもそもOAuthConsumerをSerializeしようとしていてエラーが発生したのがきっかけだが、少し複雑な感じ。
結論からいくと、OAuthConsumerはサーバ側で保持する必要はなく、Providerだけ保持していればいいっぽい。
Issue 11 - oauth-signpost - Make OAuthProvider and OAuthConsumer serializable - Simple OAuth message signing for Java - Google Project Hosting


そして、OAuthConsumerはnewで初期化した時点ではserialize可能だが、たぶんproviderと混ぜてURL生成なんかをしていると、途中からserializeできなくなる。(しようとするとnullが返された。serializeするライブラリによってはエラーを吐く)

url = provider.retrieveRequestToken(consumer, callback);

~~~
最終的にうまくいったのでまとめ。

oauthの各ステップ

  • STEP01:認証URLの生成
    • consumerとproviderを初期化
    • sessionやDBとかでproviderは保持しておく
    • consumerが持っているaccessTokenKeyとSecペアをsessionやDBで保持しておく(これがわかりにくかった)
  • STEP02:認証ページからのCALLBACK
    • callback先に認証キーがGETパラメータで返ってくるので、それをsessionとかDBで保持しておく
  • STEP03:利用
    • providerは保持しておいたやつをそのまま使う
    • consumerはSTEP01と同様に初期化した後、STEP01で保持しておいたaccessTokenKeyとSecをconsumerに適用する(これがわかりにくかった)
    • providerにconsumerとSTEP02で得た認証キーを適用する
    • 後は公式通りに利用

公式には

consumer.setTokenWithSecret(ACCESS_TOKEN, TOKEN_SECRET);

とだけあり、ACCESS_TOKENやTOKEN_SECRETは、callbackで得られるkeyとsecのペアのことかと思っていた。
しかし、これはSTEP01でcosumerが持っているやつを使わなければいけない。


STEP01

OAuthConsumer consumer = new DefaultOAuthConsumer("XXX","XXX");
OAuthProvider provider = new DefaultOAuthProvider("XXX","XXX");
authUrl = provider.retrieveRequestToken(consumer, CALLBACK_URL);
// accessTokenをsessionやDBに保持 ↓こいつら
System.out.println(consumer.getToken());
System.out.println(consumer.getTokenSecret());
//...

STEP02

//認証元からGETで受け取った認証キーをsessionやDBに保持
//...

STEP03

OAuthProvider provider = (OAuthProvider)session.getAttribute("provider");
OAuthConsumer consumer = new DefaultOAuthConsumer("XXX","XXX");
consumer.setTokenWithSecret("STEP01のaccessTokenKey", "STEP01のaccessTokenSec");
provider.retrieveAccessToken(consumer, "STEP02で認証元からうけとった認証キー");
//あとは普通通り
URL url = new URL("認証が必要なリソースURL");
//...略


~~~
さらにハマった。。

上の手順だとSTEP03を2回以上くりかえすと、2回目からは401がでてしまう。
理由は、retrieveAccessTokenするとaccessTokenとaccessTokenSecretが更新されるから。
なので、

STEP02でretrieveAccessTokenを済ませて置き、そこで新しく発行されるtokenを保持し、STEP03で何度も同じものを使うというふうにしなければいけない。

STEP01

OAuthConsumer consumer = new DefaultOAuthConsumer("XXX","XXX");
OAuthProvider provider = new DefaultOAuthProvider("XXX","XXX");
authUrl = provider.retrieveRequestToken(consumer, CALLBACK_URL);
// accessTokenをsessionやDBに保持 ↓こいつら
System.out.println(consumer.getToken());
System.out.println(consumer.getTokenSecret());
//...

// providerもsessionなんかで保持しておく
session.setAttribute("provider", provider);

STEP02 (retrieveAccessTokenを済ませて置き、そこで新しく発行されるtokenを保持)

OAuthProvider provider = (OAuthProvider)session.getAttribute("provider");
OAuthConsumer consumer = new DefaultOAuthConsumer("XXX","XXX");
provider.retrieveAccessToken(consumer, "GETパラメータでうけとった認証キー");
// ここで実はconsumerのaccesstokenが更新されている。
// のでそいつらを保持しておく
System.out.println(consumer.getToken());
System.out.println(consumer.getTokenSecret());
//...

// providerはもう用済みなので消してよい

STEP03

OAuthConsumer consumer = new DefaultOAuthConsumer("XXX","XXX");
consumer.setTokenWithSecret("STEP02のaccessTokenKey", "STEP02のaccessTokenSec");

//あとは普通通り
URL url = new URL("認証が必要なリソースURL");
//...略