基本的な使い方からソースコード学習への改修
AndroidとJavaのためのタイプセーフHTTPクライアント
RetrofitはAndroidとJavaで使えるタイプセーフなHttpクライアントです。基礎となるWebリクエストはokHttpに依存しています。
基本的な使い方
依存関係の紹介
implementation 'com.squareup.retrofit2:retrofit:2.0.2'
implementation 'com.squareup.retrofit2:converter-gson:2.0.2'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
レトロフィット
converter-gsonはコンバーターです。
インタフェースの定義
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
}
インターフェイスメソッドのひとつはバックエンドサービスのインターフェイスに対応します。listReposメソッドはString型のパラメータを渡す必要があり、@Path("user")を通してパラメータをリクエストパスに埋め込む変数にマークすることができ、URLパスを動的に生成することができます。戻り値は Call 型の変数で、Paradigm パラメータは返される結果の型を定義します。
インターフェース・オブジェクトの生成
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHub github = retrofit.create(GitHubService.class);
上記のコードでは、builderパターンを使ってRetrofitオブジェクトインスタンスを生成し、そのRetrofitインスタンスのcreateメソッドを使って、ステップ2のインターフェイスで定義されたインスタンスオブジェクトgithubを生成しています。
リクエストの開始
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");
// 非同期リクエストを開始する
call.enqueue(new Callback<List<Contributor>>() {
//リクエスト成功時にコールバックする
@Override
public void onResponse(Call<List<Contributor>> call,
Response<List<Contributor>> response) {
//リクエスト処理、結果出力
response.body().show();
}
//リクエスト失敗時のコールバック
@Override
public void onFailure(Call<Reception> call, Throwable throwable) {
System.out.println("接続に失敗した");
}
});
// 同期リクエストを開始する
List<Contributor> contributors = call.execute().body();
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
上記の GitHubService インスタンスオブジェクトを使って、定義されているインターフェイスメソッドを呼び出し、Call オブジェクトを取得します。
call.enqueue は非同期リクエストを開始し、コールバックでレスポンスを処理します。
call.executeは同期リクエストを開始し、レスポンス処理の前にリクエストが戻るのを待ちます。
II プロキシ・オブジェクトの作成
インターフェースオブジェクトの作成
Retrofit
retrofit.create(GitHubService.class);上記の使い方でインターフェイスを作成し、インターフェイス GitHubService に対応する実装クラスのインスタンスオブジェクトを取得します。Retrofit のソースコードを学ぶには、まず create メソッドから始めましょう。
public <T> T create(final Class<T> service) {
// 入力されるサービスが、インターフェイスでなければならない、インターフェイスは他のインターフェイスを継承してはならないというルールを満たしているかチェックする。
Utils.validateServiceInterface(service);
// validateEagerly サービスインターフェイスクラスで宣言されたメソッドを事前にキャッシュするかどうかのフラグを立て、キャッシュする場合はキャッシュからオブジェクトインスタンスを検索する。
if (validateEagerly) {
eagerlyValidateMethods(service);
}
// JavaのDynamic Proxyを使って、サービス・インターフェイスを実装したプロキシ・クラスを作成する。
// クラスローダー、実装済みインターフェースの配列、InvocationHandlerを渡す。
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override public @Nullable Object invoke(Object proxy, Method method,
@Nullable Object[] args) throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
JDKダイナミック・エージェント
Proxy
次に、Javaの動的プロキシがオブジェクトを生成する方法を見てみましょう。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h)throws IllegalArgumentException{
// 空ロジックの判定
Objects.requireNonNull(h);
// 外部配列の変更がプロキシ処理に影響するのを防ぐため、入力インターフェース配列のコピーを作成するのは良いアイデアだ。
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
* クラスローダーとインターフェースの配列を渡す。ここでバイトコードオブジェクトが生成される。
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// パーミッションの設定
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 最終的には、バイトコード・オブジェクトのコンストラクタ・メソッドに必要なパラメータを渡すことで、必要なオブジェクトが生成される。
// このパラメータはInvocationHandler
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
......
}
上記では、getProxyClass0(loader, intfs)によって、疲れたオブジェクトをプロキシできるクラスを取得しました。
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
* このメソッドを呼び出す前にパーミッションのチェックが必要であることを明示する
*/
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
// インターフェース配列の数をチェックする
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
// Tipは、目的のClassオブジェクトがキャッシュに存在すればそれを直接返す。存在しない場合は、ProxyClassFactoryファクトリ経由でClassオブジェクトを生成する。
return proxyClassCache.get(loader, interfaces);
}
WeakCache
WeakCacheはProxyのキャッシュクラスで、入力されたクラスローダとインターフェイス配列に基づいて第2レベルのキャッシュを作成します。 第1レベルのキャッシュのキーはクラスローダから生成され、第2レベルのキャッシュのキーはインターフェイス配列から生成されます。キャッシュの値は、WeakCache の Factory によって作成されたクラス・オブジェクトです。
このキャッシュ・クラスで興味深いのは、以下の点です。
final class WeakCache<K, P, V> {
// Reference引用キュー
private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
// キャッシュの実装で、第一階層キャッシュのキーはクラスローダーによって生成され、nullとしてサポートされる;
// セカンドレベルキャッシュのキーは、インターフェイスの配列から生成される。
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();
// reverseMapキャッシュされたClassのビルドが利用可能かどうかを記録する。
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
= new ConcurrentHashMap<>();
// セカンダリキャッシュキー生成用ファクトリ、クラスローダを使う+インターフェースリスト生成キー(key, parameter) -> sub-key
private final BiFunction<K, P, ?> subKeyFactory;
// クラスローダーを使って、セカンダリキャッシュVlaue用のファクトリを生成する+インターフェースリスト生成 Value(key, parameter) -> value
private final BiFunction<K, P, V> valueFactory;
// コンストラクタには、2つのファクトリ・クラスを渡す必要がある。
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
。。。。
}
WeakCache データ構造体、get メソッドを参照してください。
public V get(K key, P parameter) {
// インターフェイスを空にすることはできない
Objects.requireNonNull(parameter);
// 期限切れのキャッシュをクリアする
expungeStaleEntries();
// CacheKeyは内部クラスで、すぐに解析される!
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
// プライマリキャッシュのKeyを元にセカンダリキャッシュを取得する。
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
// セカンダリキャッシュが空の場合、新しいセカンダリキャッシュを作成する
if (valuesMap == null) {
// CASとして入れ、存在しなければ入れ、そうでなければ元の値に戻す
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());
// oldValuesMapに値がある場合は、その値を
// ???? getが空で、putIfAbsentに古い値が含まれているとどうなるか。
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// セカンダリキャッシュ用のキーを生成する,
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// セカンダリキャッシュの値を取得する
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
// RETURNまでのポーリング条件。
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
// サプライヤーはキャッシュされることもあれば、ループ内で新規に作成されたサプライヤーファクトリーであることもある
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
// サプライヤーとしてファクトリーを作成し、最終的にこのファクトリーでバイトコードを作成する
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
// つまり、セカンダリキャッシュの値はnullなので、先ほど作成したファクトリーがサプライヤーとして使われる。
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
// 他のスレッドがL2キャッシュに対応する値を変更している可能性がある。
} else {
// 古いサプライヤーを新しい工場に置き換える
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
// 置換に失敗した場合に使用する値
supplier = valuesMap.get(subKey);
}
}
}
}
スレッドセーフティ
proxyClassCacheは、プロキシの静的メンバ変数であり、呼び出しのproxyClassCacheのgetメソッドがロックされていない、それはどのようにスレッドの安全性を確保するのですか?まず第一にWeakCacheは、コンテナはConcurrentMap、実際にはConcurrentHashMapインスタンスオブジェクトをキャッシュする責任があります。スレッドセーフは、実際にデータを格納するコンテナに委譲されます。
KeyFactory
上記のコードでは、L2キャッシュのKeyはコンストラクタから渡されたKeyFactoryから作成され、コンストラクタ時にサブKeyFactoryに格納されます。
KeyFactoryはProxyの静的な内部クラスです。
/**
* A function that maps an array of interfaces to an optimal key where
* Class objects representing interfaces are weakly referenced.
*/
private static final class KeyFactory
implements BiFunction<ClassLoader, Class<?>[], Object>
{
@Override
public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
switch (interfaces.length) {
case 1: return new Key1(interfaces[0]); // the most frequent
case 2: return new Key2(interfaces[0], interfaces[1]);
case 0: return key0;
default: return new KeyX(interfaces);
}
}
}
KeyFactoryの設計は非常に興味深いもので、1つのインターフェイス、2つのインターフェイス、0つのインターフェイス、3つ以上のインターフェイスを区別するために適用されるこれらのケースでは、なぜこの区別? ケースのほとんどは、クラスが1つのインターフェイスを実装しているため、2つのインターフェイス、0インターフェイスの実装に続いて、3つ以上のインターフェイスの実装は、最小の場合のインターフェイス。統計によると、コードを最適化するために、実装の効率を向上させます。
返されたオブジェクトはHashMapのKeyとして使用されるので、hashCodeメソッドとequalsメソッドを実装する必要があります。Key1の実装を見てみましょう。
/*
* a key used for proxy class with 1 implemented interface
*/
private static final class Key1 extends WeakReference<Class<?>> {
private final int hash;
Key1(Class<?> intf) {
super(intf);
this.hash = intf.hashCode();// インターフェース配列のハッシュコードを使う
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
Class<?> intf;
return this == obj ||
obj != null &&
obj.getClass() == Key1.class &&
(intf = get()) != null &&
intf == ((Key1) obj).get();
}
}
等しいかどうかの判定には、アドレス->型->値の比較ロジックを使用します。ロジックはKey2とKeyXでも同様です。key0はスタティック変数で、Objectオブジェクトです。
Factory
L2キャッシュで生成されたキーを使用してL2キャッシュの値を取得し、WeakCacheにある、実際にはFactory型である値を見つけることができます。
インターフェースから生成される最終的なプロキシ・クラスは、このファクトリーで生成されます。
/**
* A factory {@link Supplier} that implements the lazy synchronized
* construction of the value and installment of it into the cache.
*/
private final class Factory implements Supplier<V> {
private final K key;// クラスローダー
private final P parameter;// インターフェースリストの配列
private final Object subKey;// 二次キャッシュ用キー
private final ConcurrentMap<Object, Supplier<V>> valuesMap;// L2キャッシュマップ
Factory(K key, P parameter, Object subKey,
ConcurrentMap<Object, Supplier<V>> valuesMap) {
this.key = key;
this.parameter = parameter;
this.subKey = subKey;
this.valuesMap = valuesMap;
}
// このメソッドは、第2階層キャッシュからキー経由でファクトリーを取得した後、ファクトリーからClassオブジェクトを取得する際に使用する。
// getメソッドをロックする必要がある
@Override
public synchronized V get() { // serialize access
// セカンダリーキャッシュに保存されているファクトリーが現在のファクトリーであることをダブルチェックする。
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// something changed while we were waiting:
// might be that we were replaced by a CacheValue
// or were removed because of failure ->
// return null to signal WeakCache.get() to retry
// the loop
// 現在のファクトリーでない場合は、現在のファクトリーが失敗したことを意味する。nullを返すと外側のループを継続できる。
return null;
}
// else still us (supplier == this)
// create new value
V value = null;
try { // 現在のファクトリーは、バイトコードオブジェクトを構築するために、構築時に渡されたvalueFactoryに委譲する。
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { //ビルドに失敗したら現在のL2キャッシュを削除する
valuesMap.remove(subKey, this);
}
}
// the only path to reach here is with non-null value
assert value != null;
// wrap value with CacheValue (WeakReference)
// 生成されたバイトコードオブジェクトをラップするには、弱い参照を使う。
CacheValue<V> cacheValue = new CacheValue<>(value);
// ラップしたバイトコードオブジェクトをセカンダリキャッシュの
if (valuesMap.replace(subKey, this, cacheValue)) {
// ラップされたバイトコードオブジェクトを、期限切れかマップでないかにかかわらず、trueをマークして格納する。
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
// 最後に返されるのは、弱い参照でラップされていないバイトコードオブジェクトだ。
return value;
}
}
false の get メソッドは、セカンダリキャッシュにキャッシュされた現在のファクトリが現在のオブジェクトそのものであることを確認するために同期を使用する必要があります。バリデーションが通過した後、プロキシクラスのバイトコードオブジェクトを作成するために valueFactory に渡されます。この時点で、作成が成功したかどうかを判断します。成功しなかった場合は、ファクトリに問題があることを意味し、キャッシュから現在のファクトリを削除します。
生成に成功した場合、ラッパークラスの最初の世代は、行くためにキャッシュの2番目のレベルにクラスオブジェクトの後にラップされます; reverseMapにラッパーオブジェクトが続いて、有効期限をマークするかどうかを使用します。
ProxyClassFactory
ProxyクラスのProxyClassFactory内部クラスが、バイトコード・オブジェクトを生成します。
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// このファクトリーが作成するプロキシクラスのバイトコードオブジェクトの名前のプレフィックスを定義する。
private static final String proxyClassNamePrefix = "$Proxy";
// アトミッククラスを使ってプロキシクラスの通し番号を生成し、ユニークなプロキシクラスとして保持する。
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// まず、入力されるインターフェイス配列が一貫していることを確認する; IdentityHashMap
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
* 入力されたインターフェイスクラスがクラスローダー経由で読み込めるかどうかを判定する。
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
// クラスローダーが読み込むインターフェイスバイトコードオブジェクトは、配列で渡されるインターフェイスバイトコードオブジェクトと同じか?
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface. ロードされたインターフェイス・バイトコード・オブジェクトがインターフェイス型であることを確認する。
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
* 読み込まれたバイトコードオブジェクトが重複していないこと、IdentityHashMapのキーが等しいこと、アドレスが等しいかどうかの比較が行われていることを確認する。
* 異なるアドレスは重複しないとみなす
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
// プロキシクラスのパッケージ名を生成する
String proxyPkg = null; // package to define proxy class in
// プロキシクラスのアクセストークンを生成する public final
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
* パッケージ名を生成するためにインターフェースの配列を繰り返し処理する
*/
for (Class<?> intf : interfaces) {
// インターフェースのアクセストークンを取得する
int flags = intf.getModifiers();
// 非公開インターフェースの場合、複数の非公開インターフェースのパッケージ名を比較して整合性を確認する。矛盾があるとエラーになる。
if (!Modifier.isPublic(flags)) {
// publicでないインターフェイスがある場合、生成されるプロキシクラスオブジェクトはpublicにできないので、アクセスフラグを再定義する
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
// プロキシクラスのパッケージ名が空の場合、インターフェイスのパッケージ名が使われる。
if (proxyPkg == null) {
proxyPkg = pkg;
// もちろん、パッケージ名が異なる非公開インターフェースが複数存在する場合はエラーが報告される。
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 非公開インターフェースがない場合パッケージ名は任意、デフォルトのパッケージ名comを使う.sun.proxy
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
* パッケージ名を使う+プロキシクラスのプレフィックス+いいえ。生成されるプロキシ・クラスの最終的なフル・パス名。
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
* ここからがキモなのだが、ProxyGeneratorを使ってプロキシクラスの最終的なバイトコードを作成する。
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {// バイナリー・バイトコードを解析して、対応するClassインスタンスを生成する。
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
上記のProxyClassFactoryクラスは、まずインターフェイス配列が
着信インターフェース・クラスが着信クラス・ローダーによってロードできるかどうか、またロード後に元のインターフェース・クラスと整合性があるかどうかを判断します。
入力されるインターフェース・クラス・オブジェクトがすべてインターフェース・タイプであることをチェックします。
配列内で重複しているインターフェイスをチェックします。
インターフェイスのチェックがすべて通ったら、今度はプロキシ・クラスのパッケージ名とアクセス修飾子を準備します。
デフォルトのパッケージ名は com.sun.proxy で、デフォルトのアクセス修飾子は public final です。
すべてのインターフェースがpublic型の場合、パッケージ名とアクセス修飾子にはデフォルト値が使われます。
非公開のインターフェースがある場合、パッケージ名はインターフェースのパッケージ名に変わり、アクセス修飾子はfinalに変わります。
異なるパッケージ名を持つ複数の非公開インターフェースが存在する場合にエラーを報告します。
パッケージ名を決定した後、最終的なプロキシクラスのフルパス名はパッケージ名 + $Proxy + シリアル番号で生成されます。
最後に、ProxyGenerator.generateProxyClassメソッドによってプロキシ・クラス・オブジェクトのバイナリ・バイトコードが生成されます。次に、ネイティブ・メソッド defineClass0 によってプロキシ・クラスの Class インスタンスを生成します。
ProxyGenerator
ProxyClassFactoryクラスの上記の説明では、最終的にProxyGenerator.generateProxyClass生成プロキシクラスのバイナリストリームであることを言及し、具体的にどのように生成するのですか? 今日はソースコードを探索します。パッケージsun.misc;パッケージ内のこのクラス。
// 静的メソッド内の最初のステップは、やはりProxyGeneratorオブジェクトを生成することだ。
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
// それともgenerateClassFileメソッドがバイナリストリームを生成しているのだろうか?
final byte[] var4 = var3.generateClassFile();
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
バイナリ・ストリームの生成
private byte[] generateClassFile() {
// まず、プロキシクラスのhashCode、equals、toStringメソッドを生成する。
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
Class[] var1 = this.interfaces;
int var2 = var1.length;
int var3;
Class var4;
// インターフェイスの配列を繰り返し、各インターフェイスのメソッドを走査し、それらのメソッドをプロキシ・クラスに追加する。
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
Method[] var5 = var4.getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method var8 = var5[var7];
this.addProxyMethod(var8, var4);
}
}
Iterator var11 = this.proxyMethods.values().iterator();
// プロキシオブジェクトのメソッド一覧を繰り返し、同じメソッドシグネチャで戻り値の型が異なるケースがないか調べる。
List var12;
while(var11.hasNext()) {
var12 = (List)var11.next();
checkReturnTypes(var12);
}
// プロキシクラスのClassファイルを生成するために必要なフィールドとメソッドの情報をアセンブルする。
Iterator var15;
try {
// InvocationHandlerとして渡される固定コンストラクタ・メソッドを追加する
this.methods.add(this.generateConstructor());
var11 = this.proxyMethods.values().iterator();
while(var11.hasNext()) {
var12 = (List)var11.next();
var15 = var12.iterator();
while(var15.hasNext()) {
ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
// プロキシクラスにフィールドを追加する
this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
// エージェントメソッドを生成する
this.methods.add(var16.generateMethod());
}
}
// プロキシクラスに静的フィールド初期化メソッドを追加
this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
throw new InternalError("unexpected I/O Exception", var10);
}
// 検証メソッド数とフィールド数は65535を超えることはできない
if (this.methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
} else if (this.fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
} else {
// プロキシクラスの完全修飾名が定数プールにあることを確認する。. /
this.cp.getClass(dotToSlash(this.className));
// プロキシの親クラスの完全修飾名が定数プールにあることを確認する
this.cp.getClass("java/lang/reflect/Proxy");
var1 = this.interfaces;
var2 = var1.length;
// プロキシクラスインターフェイスのリストの完全修飾名を検証する
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
this.cp.getClass(dotToSlash(var4.getName()));
}
// 定数プールを読み取り専用にする
this.cp.setReadOnly();
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);
try {
// 1.マジックナンバーを書く
var14.writeInt(-);
// 2.マイナーバージョン番号を書く
var14.writeShort(0);
// 3.マスターバージョン番号を書く
var14.writeShort(49);
// 4.定数プールに書き込む
this.cp.write(var14);
// 5.書き込みアクセス修飾子
var14.writeShort(this.accessFlags);
// 6.定数プールにクラス名インデックスを書く
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
// 7.親クラスのインデックスを書く
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
// 8.書き込むインターフェースの数
var14.writeShort(this.interfaces.length);
// 9.インターフェイスを繰り返し、各インターフェイスのインデックスを書く
Class[] var17 = this.interfaces;
int var18 = var17.length;
for(int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
// 10.書き込むフィールド数
var14.writeShort(this.fields.size());
· // 11.フィールドの配列を繰り返し、フィールドを
var15 = this.fields.iterator();
while(var15.hasNext()) {
ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
var20.write(var14);
}
// 12.書き込みメソッド数
var14.writeShort(this.methods.size());
// 13.便利なメソッドの配列。
var15 = this.methods.iterator();
while(var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
var21.write(var14);
}
// 14.プロキシクラスファイルに属性がない場合、属性カウント値を0として書き込む
var14.writeShort(0);
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
}
上記のプロセスは非常に明確ですが、generateClassFileは、プロキシクラスのフルパス名、インターフェイス配列の実装、プロキシクラスによって生成されたクラスオブジェクトのアクセス修飾子に基づいて、プロキシクラスの最初のコレクションは、メソッドを実装する必要がありますプロキシクラスのメソッドとフィールド情報の生成に続いて、最後にクラスファイルの仕様に従って、プロキシクラスのクラスファイルをスプライシングコードを介して、次の書き込みここでは、最終的に生成されたClassファイルを見るためのデモです。なお、上記のコンストラクタは固定で、InvocationHandlerオブジェクトがデフォルトで渡されます。
public static void main(String[] args) throws IOException {
// 1.プロキシクラスのフルパス名を用意する
String proxyName = "wux.text.WxProxy";
// 2.プロキシクラスが実装するインターフェースの配列
Class[] interfaces = new Class[]{WxInterfaceTest.class};
// 3.修飾子へのアクセス
int modifier = Modifier.PUBLIC|Modifier.FINAL;
// 4.プロキシクラスのバイトコードバイナリストリームを生成する
byte[] classResult = ProxyGenerator.generateProxyClass(proxyName,interfaces,modifier);
// 5.生成されたバイトコードのバイナリストリームをファイルに書き出す
FileOutputStream fileOutputStream = new FileOutputStream("WuxProxy.class");
fileOutputStream.write(classResult);
fileOutputStream.flush();
fileOutputStream.close();
}
上記のコードを次のように逆コンパイルして、WuxProxy.classファイルを取得します。
package wux.text;
import com.bennyhuo.retrofit.tutorials.WxInterfaceTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
// ProxyクラスはProxyを継承している。
public final class WxProxy extends Proxy implements WxInterfaceTest {
// 2. 静的メソッドを生成する
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
// 1.コンストラクタにInvocationHandlerを渡す必要がある。
public WxProxy(InvocationHandler var1) throws {
super(var1);
}
// 3. エージェントメソッドを生成する
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello() throws {
try {// プロキシメソッドの実装は、InvocationHandlerのinvokeメソッドに依存している。
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 静的メソッドの初期化
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.bennyhuo.retrofit.tutorials.WxInterfaceTest").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
上記で生成されたプロキシ・クラスは、Javaの単一継承の制限により、デフォルトでProxyを継承しているため、JDKの動的プロキシはインターフェースに対してのみ使用できます。
プロキシクラスのメソッドの実行は、最終的にh.invokeに実行され、h.invokeはプロキシメソッド呼び出しをInvocationHandlerのinvokeに転送し、invoke内のコードを具体的に実行します。プロキシオブジェクトのインスタンス、メソッド、パラメータ配列などが渡されます。
Cglibダイナミック・エージェント
JDKに付属しているダイナミックプロキシはインターフェースしかサポートしていません。 Cglibのダイナミックプロキシはクラスのダイナミックプロキシ機能を実装しています。
プロキシ」の目的は、プロキシされるオブジェクトと同じ振る舞いをするオブジェクトを構築することです。 オブジェクトの振る舞いはクラスで定義され、オブジェクトはクラスのインスタンスに過ぎません。ですから、プロキシを構築するためにオブジェクトを保持したりラップしたりする必要はありません。
cglibの考え方は、「継承」によって親クラスのパブリックメソッドを全て継承し、そのメソッドを書き換える際に、そのメソッドを書き換えたり、拡張したりすることができるというものです。リヒターの置換の原則によれば、親クラスが存在する必要があるところには子クラスを存在させることができるので、cglibのプロキシの実装は普通に使うことができます。
最初に依存関係を紹介します。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
まず、プロキシされるクラスを作成します。
public class CglibDemoBaseCalss {
public String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Cglib利用の流れ
public static void main(String[] args) {
// 1. cglibプロキシ・クラスの設定と作成は、Cglibのバイトコード・エンハンサーであるEnhancerを通して行われる。
// プロキシ・クラスを強化できる。
Enhancer enhancer = new Enhancer();
// 2. プロキシするクラスを設定する
enhancer.setSuperclass(CglibDemoBaseCalss.class);
// 3. cglib インターフェースへのプロキシもサポートされている
enhancer.setInterfaces(new Class[]{WxInterfaceTest.class});
// 4. InvocationHandlerのinvokeと同様に、メソッド呼び出し時にコールバックを設定することで、プロキシクラスがメソッド呼び出しの最終的な振る舞いを決定できるようになる
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("before " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("after: " + method.getName());
return result;
}
});
// 5. 生成されたプロキシクラスのメソッドを呼び出す
CglibDemoBaseCalss cglibDemoBaseCalss = (CglibDemoBaseCalss) enhancer.create();
cglibDemoBaseCalss.setName("プロキシテスト:");
cglibDemoBaseCalss.sayName();
}
出力結果は
before: setName
after: setName
before: sayName
プロキシテスト:
after: sayName
動的プロキシとJDKの動的プロキシの原則のCglibの実装は、プロキシクラスのバイトコードファイルの生成に似ていますし、オブジェクトのインスタンスを生成するためにメモリにロードされます。以下は、特定の実装プロセスを見てください。
Enhancer
Enhancerはcreateメソッドで作成されるプロキシクラスオブジェクトで、ここからソースコードが解析されます。
/**
* Generate a new class if necessary and uses the specified
* callbacks (if any) to create a new object instance.
* Uses the no-arg constructor of the superclass.
* @return a new instance
*/
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}
次に、createHelperメソッドの実装を見てみましょう。
private Object createHelper() {
// 事前チェックを行う
preValidate();
// クラスの作成とオブジェクトのインスタンス化
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
KEY_FACTORYとは一体何なのでしょうか?
private static final EnhancerKey KEY_FACTORY =
(EnhancerKey)KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null);
EnhancerKeyはインターフェイスで、KeyFactory.createはKeyFactory型を返します。返されるKeyFactoryインスタンスがインターフェイスを実装していない限り。さらに戻ってみると、おそらくKeyFactoryを強化するためにEnhancerKey.classをパラメータとして渡しています。
KeyFactory
次に、KeyFactoryのcreateメソッドを見てください。
public static KeyFactory create(Class keyInterface, KeyFactoryCustomizer first, List<KeyFactoryCustomizer> next) {
return create(keyInterface.getClassLoader(), keyInterface, first, next);
}
内部呼び出しはまだcreateメソッドで
public static KeyFactory create(ClassLoader loader, Class keyInterface, KeyFactoryCustomizer customizer,List<KeyFactoryCustomizer> next) {
Generator gen = new Generator();
// プロキシクラスが実装するインターフェースの設定
gen.setInterface(keyInterface);
// この部分が何なのかはまだわからない
if (customizer != null) {
gen.addCustomizer(customizer);
}
if (next != null && !next.isEmpty()) {
for (KeyFactoryCustomizer keyFactoryCustomizer : next) {
gen.addCustomizer(keyFactoryCustomizer);
}
}
// クラスローダーの設定
gen.setClassLoader(loader);
return gen.create();
}
Factory
このGeneratorはAbstractClassGeneratorを継承したKeyFactoryの内部クラスで、いくつかのパラメータを設定し、最後にcreateメソッドを呼び出して目的のKeyFactoryを生成するだけのものです。
public KeyFactory create() {
setNamePrefix(keyInterface.getName());
return (KeyFactory)super.create(keyInterface.getName());
}
AbstractClassGenerator
上のGeneratorが呼び出すcreateメソッドは、結局親クラスのcreateメソッドを呼び出すことになるので、ここでは親クラスの実装を見てみましょう。
protected Object create(Object key) {
try {
// クラスローダーを取得する。実際には、前のステップでKeyFactoryに設定したクラスローダーである
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
// キャッシュ内のクラス読み込みデータを取得する
ClassLoaderData data = cache.get(loader);
// ロックはダブルチェックされる
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
// 新しいキャッシュデータを作成する前に、キャッシュにクラスローダーがないことを確認する
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
// データを読み込む新しいクラスを作る
data = new ClassLoaderData(loader);
newCache.put(loader, data);
// キャッシュを更新する
CACHE = newCache;
}
}
}
this.key = key;
// このステップが重要で、ClassLoaderDataのgetで、強化されたプロキシクラスのClassオブジェクトを返す。
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
上記のcreateメソッドでは、まずクラス・ローダーをキーとして、キャッシュされたバイトコード・オブジェクトのデータをチェックします。キャッシュされた ClassLoaderData を見つけ、そこからバイトコードオブジェクトを取得します。
ClassLoaderData
ClassLoaderData は AbstractClassGenerator の内部クラスで、その get メソッドは明示的に外部クラスへの参照を保持し、外部クラスの generate メソッドを呼び出します。
public Object get(AbstractClassGenerator gen, boolean useCache) {
if (!useCache) {
// キャッシュなしでバイトコードオブジェクトを生成する手順
return gen.generate(ClassLoaderData.this);
} else {
Object cachedValue = generatedClasses.get(gen);
return gen.unwrapCachedValue(cachedValue);
}
}
だから結局、具体的な実装を見るためにはAbstractClassGeneratorクラスに戻らなければなりません。
protected Class generate(ClassLoaderData data) {
Class gen;
// ここでのCURRENTはThreadLocal型で、正確な役割は今のところ不明だ。
Object save = CURRENT.get();
CURRENT.set(this);
try {
// 1.クラスローダーを取得する
ClassLoader classLoader = data.getClassLoader();
if (classLoader == null) {// 空だとエラーになる
throw new IllegalStateException("ClassLoader is null while trying to define class " +
getClassName() + ". It seems that the loader has been expired from a weak reference somehow. Please file an issue at cglib's issue tracker.");
}
// 2.クラスローダーをロックしてプロキシクラス名を生成する。プロキシのクラス名は、namingPolicyで定義する。
synchronized (classLoader) {
String name = generateClassName(data.getUniqueNamePredicate());
data.reserveName(name);
this.setClassName(name);
}
// 3.プロキシクラスのバイトコードオブジェクトをまずクラスローダーから読み込んでみて、それがあれば直接返す。
if (attemptLoad) {
try {
gen = classLoader.loadClass(getClassName());
return gen;
} catch (ClassNotFoundException e) {
// ignore
}
}
// 4. プロキシクラスのバイトコードオブジェクトが読み込まれていないので、プロキシクラスの生成処理を紹介する。 クラスのバイナリストリーム。
byte[] b = strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
ProtectionDomain protectionDomain = getProtectionDomain();
synchronized (classLoader) { // just in case
if (protectionDomain == null) {
gen = ReflectUtils.defineClass(className, b, classLoader);
} else {
gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
}
}
return gen;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
} finally {
CURRENT.set(save);
}
}
最後に、クラスファイルの生成プロセスを見てきましたが、主に以下のステップに分かれます:
クラス・ローダーを取得し、途中で例外チェックを行います。
クラス・ローダに基づいてプロキシ・クラス名を生成します。
String name = generateClassName(data.getUniqueNamePredicate());
private String generateClassName(Predicate nameTestPredicate) {
return namingPolicy.getClassName(namePrefix, source.name, key, nameTestPredicate);
}
private NamingPolicy namingPolicy = DefaultNamingPolicy.INSTANCE;
/**
* Override the default naming policy.
* @see DefaultNamingPolicy
* @param namingPolicy the custom policy, or null to use the default
*/
public void setNamingPolicy(NamingPolicy namingPolicy) {
if (namingPolicy == null)
namingPolicy = DefaultNamingPolicy.INSTANCE;
this.namingPolicy = namingPolicy;
}
プロキシ・クラスが読み込まれているかどうかを調べ、 読み込まれていればそれを直接返します。
ストラテジーによる代理クラス生成
byte[] b = strategy.generate(this);
private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE;
/**
* Override the default naming policy.
* @see DefaultNamingPolicy
* @param namingPolicy the custom policy, or null to use the default
*/
public void setNamingPolicy(NamingPolicy namingPolicy) {
if (namingPolicy == null)
namingPolicy = DefaultNamingPolicy.INSTANCE;
this.namingPolicy = namingPolicy;
}
上記の namingPolicy と strategy は AbstractClassGenerator のメンバ変数で、ほとんどの場合デフォルトの設定を使用しますが、あなた自身の実装で置き換えることができます。
NamingPolicyとGeneratorStrategyをカスタマイズし、この2つのロジックをここでカスタマイズします。
//
class MyNameAndBytesFactory implements NamingPolicy,GeneratorStrategy{
private NamingPolicy mNamingPolicy = new DefaultNamingPolicy();
private GeneratorStrategy mGeneratorStrategy = new DefaultGeneratorStrategy();
private String name;
@Override
public byte[] generate(ClassGenerator cg) throws Exception {
byte[] result = mGeneratorStrategy.generate(cg);
FilesKt.writeBytes(new File(name+".class"),result);
return result;
}
@Override
public String getClassName(String prefix, String source, Object key, Predicate names) {
name = mNamingPolicy.getClassName(prefix,source,key,names);
return name;
}
}
MyNameAndBytesFactory myNameAndBytesFactory = new MyNameAndBytesFactory();
enhancer.setStrategy(myNameAndBytesFactory);
enhancer.setNamingPolicy(myNameAndBytesFactory);
上記では、まずNamingPolicyとGeneratorStrategyインタフェースを実装するメソッド内部でローカルクラスを実装します。もちろん、ここではプロキシのアプローチが使用され、バイトコードと名前は依然としてデフォルトの実装に依存しています。
生成されたバイトコード・ファイルでは
private MethodInterceptor CGLIB$CALLBACK_0;
public final void sayName() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$sayName$2$Method, CGLIB$emptyArgs, CGLIB$sayName$2$Proxy);
} else {
super.sayName();
}
}
setCallback は MethodInterceptor 型に設定されているので、生成されたダイナミックプロキシクラスも MethodInterceptor 型のメンバ変数を持っていることがわかります。
ダイナミック・エージェントの違い
JDKの動的エージェントはインターフェイスのみをサポートし、メソッド呼び出しはリフレクション呼び出しです。
CgLibの動的プロキシはインタフェースとクラスをサポートし、リフレクションと直接呼び出しをサポートします。
動的プロキシは通常、実行時にバイトコードを生成してクラスをロードし、オブジェクトを生成します。
静的プロキシはコンパイル時にプロキシ構造を決定します。
コンパイル時のバイトコード生成
III インターフェース・メソッドの呼び出し
Retrofit
レトロフィットの研究を続ける
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");
インターフェイスオブジェクトがダイナミックプロキシによってRetrofitによって生成された後、インスタンスオブジェクトのメソッドを呼び出すコードの実行パスはどのようになるのでしょうか?まず、ダイナミックプロキシオブジェクトの生成プロセスを思い出してください。
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override public @Nullable Object invoke(Object proxy, Method method,
@Nullable Object[] args) throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
プロキシはインターフェイスなので、すべてのインターフェイスメソッド呼び出しは、実際にはInvocationHandlerのinvokeメソッドを呼び出します。
private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {// 最後に呼び出すserviceMethodを作成する。
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
サービスメソッド
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// 1. マークアップリクエストのアノテーションを解析する
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
// 2. リターン・タイプを解析する
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
// 3. ServiceMethodの解析と生成
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract @Nullable T invoke(Object[] args);
}
ServiceMethodを作成するプロセスは、主に3つのステップに分かれています。
インターフェイスのメソッド注釈とパラメータ注釈を解析して RequestFactory を生成します。
メソッドの戻り値の型が正常かどうかを解析し、検出します。
インターフェイスメソッドを対応する Http リクエストオブジェクトに生成します。
RequestFactory
ここではbuilderパターンが使われています。
final class RequestFactory {
private final Method method;// 対応するインターフェイスメソッドをリクエストする
private final HttpUrl baseUrl;// リクエストパス
final String httpMethod;// リクエスト方法
private final @Nullable String relativeUrl;// 相対パス
private final @Nullable Headers headers;//
private final @Nullable MediaType contentType;// メディアタイプをリクエストする
private final boolean hasBody;// ボディがあるかどうかリクエストする
private final boolean isFormEncoded;
private final boolean isMultipart;
private final ParameterHandler<?>[] parameterHandlers;//パラメータ解析プロセッサ
final boolean isKotlinSuspendFunction;
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();// メソッドの注釈を取得する
this.parameterTypes = method.getGenericParameterTypes();// メソッドのパラメータ型の配列を取得する
this.parameterAnnotationsArray = method.getParameterAnnotations();// メソッドアノテーションの配列を取得する。
}
}
RequestFactoryには多くのメンバ変数があり、ビルダによって作成される必要があります。
RequestFactory build() {
// 1. ループトラバーサルリクエストメソッドのアノテーション
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
// 2. リクエストパラメータだけでなく、その他のパラメータも決定する
if (httpMethod == null) {
throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(method,
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError(method, "FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
// 3.メソッドパラメータハンドラを作成する
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
// 4.例外かどうかを判断する
if (relativeUrl == null && !gotUrl) {
throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError(method, "Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError(method, "Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError(method, "Multipart method must contain at least one @Part.");
}
return new RequestFactory(this);
}
- ループトラバーサルリクエストメソッドのアノテーション
RequestFactoryを構築する最初のステップは、インターフェイスメソッドのアノテーションを解析することです:
private void parseMethodAnnotation(Annotation annotation) {
// リクエストメソッドを解析する部分
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
// リクエストパスを解析する
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError(method, "@Headers annotation is empty.");
}
// リクエストヘッダの注釈を解析する
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}
上のコードは、Retrofitのメソッドで使えるアノテーションと、その処理方法を示しています。主な処理はリクエストパスの生成、リクエストヘッダ、その他のパラメータです。ここでは、メソッドアノテーションとリクエストパラメータから、 現在リクエストされているメソッドの URL を組み立てる方法を見てみましょう。
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
// 入力パラメーターのシンプルなチェックサム
if (this.httpMethod != null) {
throw methodError(method, "Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody;
// 注釈がなければ戻るだけで、パスを解決する必要はない
if (value.isEmpty()) {
return;
}
// リクエストURLにパラメータが付加されているかチェックする。.com?name=tony&age=18 URLのパラメータにこのタイプを指定する。
int question = value.indexOf('?');
if (question != -1 && question < value.length() - 1) {
// Ensure the query string does not have any named parameters.
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
// URLにリクエストパラメータを使用する場合、ダイナミックパラメータを使用しないでください、さもなければエラーが表示されますと説明する。
throw methodError(method, "URL query string "%s" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
}
// 相対パスを保存する
this.relativeUrl = value;
// パラメータパスを解析する
this.relativeUrlParamNames = parsePathParameters(value);
}
// パスの動的パラメータを解析してsetに保存するので、重複したパラメータは一度しか要求されない。
static Set<String> parsePathParameters(String path) {
Matcher m = PARAM_URL_REGEX.matcher(path);
Set<String> patterns = new LinkedHashSet<>();
while (m.find()) {
patterns.add(m.group(1));
}
return patterns;
}
// リクエストヘッダの処理
private Headers parseHeaders(String[] headers) {
Headers.Builder builder = new Headers.Builder();
for (String header : headers) {
int colon = header.indexOf(':');
if (colon == -1 || colon == 0 || colon == header.length() - 1) {
throw methodError(method,
"@Headers value must be in the form "Name: Value". Found: "%s"", header);
}
String headerName = header.substring(0, colon);
String headerValue = header.substring(colon + 1).trim();
if ("Content-Type".equalsIgnoreCase(headerName)) {
try {
contentType = MediaType.get(headerValue);
} catch (IllegalArgumentException e) {
throw methodError(method, e, "Malformed content type: %s", headerValue);
}
} else {
// は、リクエストヘッダにキーと値のペアとして格納される。
builder.add(headerName, headerValue);
}
}
return builder.build();
}
上のコードでは、アノテーション内のパスを解析して動的パラメータを取り出しています。これは、リクエストパラメータとして Retrofit では推奨されていません。
- メソッドパラメータハンドラの生成
// 3.メソッドハンドラを作成する
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
RequestFactory.Builder.build()メソッドの3番目のステップは、メソッドパラメータハンドラを作成することです。
// pはパラメータインデックス、 parameterType はパラメータタイプ、 annotations は現在のパラメータを指す注釈の配列、 allowContinuation は最後のパラメータであることを示す。
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
ParameterHandler<?> result = null;
//1. 現在のパラメータのアノテーションがnullでなければアノテーションをパースし、アノテーションがnullで最後のパラメータでなければリターンする。
if (annotations != null) {
for (Annotation annotation : annotations) {
// Retrofitで定義されたアノテーションを繰り返し解析する
ParameterHandler<?> annotationAction =
parseParameterAnnotation(p, parameterType, annotations, annotation);
// Retrofit以外の注釈はスキップされて次の注釈に移る。
if (annotationAction == null) {
continue;
}
// 1つのパラメータに複数のRetrofitアノテーションがある場合にエラーを投げる。
if (result != null) {
throw parameterError(method, p,
"Multiple Retrofit annotations found, only one allowed.");
}
// 要約すると、1つのパラメータに使用できるRetrofitアノテーションは1つだけである。
result = annotationAction;
}
}
if (result == null) {
if (allowContinuation) {
try {
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
throw parameterError(method, p, "No Retrofit annotation found.");
}
return result;
}
次に、具体的な注釈処理の流れを見てみましょう。
@Nullable
private ParameterHandler<?> parseParameterAnnotation(
int p, Type type, Annotation[] annotations, Annotation annotation) {
if (annotation instanceof Url) {
...
} else if (annotation instanceof Path) {
// 1. パラメータタイプがサポートされているか確認する
validateResolvableType(p, type);
// 2. 例えば、PathはQueryに、PathはUrlと混ぜてはいけない。
if (gotQuery) {
throw parameterError(method, p, "A @Path parameter must not come after a @Query.");
}
if (gotQueryName) {
throw parameterError(method, p, "A @Path parameter must not come after a @QueryName.");
}
if (gotQueryMap) {
throw parameterError(method, p, "A @Path parameter must not come after a @QueryMap.");
}
if (gotUrl) {
throw parameterError(method, p, "@Path parameters may not be used with @Url.");
}
if (relativeUrl == null) {
throw parameterError(method, p, "@Path can only be used with relative url on @%s",
httpMethod);
}
gotPath = true;
// 3. 注釈のパラメータを取り出し、相対パスにあるかどうかをチェックする。
Path path = (Path) annotation;
String name = path.value();
validatePathName(p, name);
// 4. どんな種類のパラメータであっても、最終的にはStringに変換されないと動かないので、パラメータの種類に応じてコンバータを探そう。
Converter<?, String> converter = retrofit.stringConverter(type, annotations);
// 5. アノテーションタイプに対応したParameterHandlerを作成する
return new ParameterHandler.Path<>(method, p, name, converter, path.encoded());
} else if (annotation instanceof Query) {
...
} else if (annotation instanceof QueryName) {
...
} else if (annotation instanceof QueryMap) {
...
} else if (annotation instanceof Header) {
...
} else if (annotation instanceof HeaderMap) {
...
} else if (annotation instanceof Field) {
...
} else if (annotation instanceof FieldMap) {
...
} else if (annotation instanceof Part) {
...
} else if (annotation instanceof PartMap) {
...
} else if (annotation instanceof Body) {
...
} else if (annotation instanceof Tag) {
...
}
return null; // Not a Retrofit annotation.
}
このタイプの構文解析におけるパラメータ注釈の処理の流れ:
サポートされているパラメータタイプかどうかをチェックします。
現在の注釈が適切な場所に属していることを確認します。例えば、Path は Query にあるべきで、Path は Url と混在してはなりません。
注釈内のパラメータを取得し、それらが相対パスにあるかどうかをチェックします。
パラメータがどのような型であっても、最終的にStringに変換されなければ動作しないからです。
対応する注釈タイプの ParameterHandler を作成します。
ParameterHandlerは抽象クラスです、ここで抽象メソッドの適用を見て、主にRequestBuilderと値を渡す、RequestBuilderに追加する処理後のインターフェイスメソッドパラメータ値。
abstract class ParameterHandler<T> {
abstract void apply(RequestBuilder builder, @Nullable T value) throws IOException;
.....
}
ここでは、Path クラスのパラメーターの処理を見てみましょう。
static final class Path<T> extends ParameterHandler<T> {
private final Method method;// インターフェース・メソッド
private final int p;// パラメータインデックス
private final String name;// ダイナミックパスのパラメータ名
private final Converter<T, String> valueConverter;// パラメータコンバータ
private final boolean encoded;// path パラメータの値をトランスコードするかどうか、つまり、非
Path(Method method, int p, String name, Converter<T, String> valueConverter, boolean encoded) {
this.method = method;
this.p = p;
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
if (value == null) {
throw Utils.parameterError(method, p,
"Path parameter "" + name + "" value must not be null.");
}
// RequestBuilderに動的なパラメータ名と文字列に変換されたパラメータ値を追加する。
builder.addPathParam(name, valueConverter.convert(value), encoded);
}
}
上記のコード解析は、インターフェイスメソッドのすべてのアノテーション処理を通過します。RequestFactoryのすべてのメンバ変数があります。ServiceMethodに戻って見てください。
HttpServiceMethod の作成方法。
// 3. ServiceMethodの解析と生成
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
HttpServiceMethod
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
.........
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
// 適応された型に関する情報を取得する。これらのパラダイム実パラメータは、実際にはバイトコードのコメント行に格納されているので、コメントを難読化するとパラダイム実パラメータを取得できなくなる。
// にショートカットキーがあるため.5パラダイムを追加する際、javaバージョンはすでに広く使われており、互換性の理由から、コメント内に消去されたパラダイムの実パラメータのシグネチャを置くことしかできない。
adapterType = method.getGenericReturnType();
}
// 上流のOkHttpCallから返されたデータを受け取るcallAdapterを生成する。<ResponseT> インターフェイスのリターン・タイプに変換する
// インターフェイスメソッドの定義にどのようなコールバックが欲しいかを決める。例えば、contributorsインターフェイスメソッドの公式サンプルは、以下のコールバックタイプを返す。<List<Contributor>>
// コールバックインターフェースは、最終的なリクエストを開始し、非同期同期を決定する。
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
if (responseType == okhttp3.Response.class) {
throw methodError(method, "'"
+ getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
if (responseType == Response.class) {
throw methodError(method, "Response must include generic type (e.g., Response<String>)");
}
// TODO support Unit for Kotlin?
if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
throw methodError(method, "HEAD method must use Void as response type.");
}
// 上流のResponseBodyをResponseT(公式の例ではList)に変換する。<Contributor>
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
// 最後にここに行く!
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}
parseAnnotationsの戻り値の型CallAdaptedから始めて、ラウンドアップを後ろから前に読みます。
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
private final CallAdapter<ResponseT, ReturnT> callAdapter;
// これはokhttp3として渡される.Call.Factoryリクエストが戻ってきた後、実際にはOkHttpCallが渡される。<ResponseT>
CallAdapted(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, ReturnT> callAdapter) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
// 汎用レスポンスタイプCallを追加する<ResponseT> callOkHttpCallをインターフェイスメソッド固有のReturnTに変換するのがポイント。<ResponseT> ==> ReturnT
@Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
}
前述のインターフェイスによって生成されたダイナミック・プロキシ・クラスでは、インターフェイス・メソッドの呼び出しは、実際にはInvocationHandlerのinvokeメソッドを呼び出し、最終的に返される結果は次のようになります。
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations);
最後の呼び出しは、Retrofit の nextCallAdapter メソッドです。
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
......
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
// ファクトリーキャッシュから対応する型ファクトリーを探し、callAdapterを生成する
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
........
}
CallAdapter<R, T>
上記のコードでは、createCallAdapter プロセスが表示され、CallAdapter の
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
ここでコントリビューターメソッドによって返されるレスポンスデータは、フレームワークがそれを受け取った後に希望のフォーマットに変換する必要があるレスポンスを定義するCallに含まれており、現在のインターフェースは実際に変換を行わずにCallを使用します。その役割は、上流の Call ==> T .
また、CallがreturnTypeであり、responseTypeが先のとがった括弧の中にあることを明確にすることも重要です。
この結果変換インターフェースの具体的な実装を見てみましょう。
// 呼び出しを追加する<Responsetype> ReturnTに変換して、FactoryでCallAdapterを作成する。
// FactoryもRetrofit経由で使える.Builder#addCallAdapterFactory(Factory)追加する
public interface CallAdapter<R, T> {
// ResponseType型を取得する。
Type responseType();
// CallをRetrunT型に変換する。
T adapt(Call<R> call);
// アダプタ作成用ファクトリー
abstract class Factory {
// このファクトリで処理可能なアダプタを返すか、処理できない場合は null を返す。
public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
// パラダイムインスタンスの上境界型を抽出する。indexは最初のパラダイムインスタンスを表すパラメータ。
// 入力パラメータは1,Map<String,? extends Runnable> , Runnable型の結果を返す。
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
// 入力されたTypeのバイトコードオブジェクトを取得する。
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
}
}
DefaultCallAdapterFactory
Retrofitのビルドプロセスをチェックしてみましょう。Builderパターンを使って作成されており、以下のコードがそのBuilderにあります:
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
デフォルトでは、プラットフォームのデフォルトの callAdapterFactory が使用されます。
@Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
@Nullable Executor callbackExecutor) {
if (callbackExecutor == null) throw new AssertionError();
DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
return Build.VERSION.SDK_INT >= 24
? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
: singletonList(executorFactory);
}
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
private final @Nullable Executor callbackExecutor;
.....................
// 最終的に返される CallAdapter のみを気にする。
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
// 実際には、デフォルトではexecutorはnullなので変換は行われず、callのまま返される。
@Override public Call<Object> adapt(Call<Object> call) {
return executor == null
? call
: new ExecutorCallbackCall<>(executor, call);// 設定時にスレッドを切り替える
}
};
}
.....................
}
上記のファクトリ・クラスは CallAdapter を返しますが、実際には結果の変換は何も行いません。 3 章の一番最初で、生成されたダイナミック・プロキシ・オブジェクトが Call タイプの最終的な戻り値を取得するために使用されたことを思い出してください。
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");
実際には多くの場合、RxJava2 を使ってリクエストを発行します。ここでは、R xJava 用のアダプタを紹介します。
RxJava2CallAdapterFactory
同期ファクトリ、同期ファクトリ、非同期ファクトリを作成するには、以下の静的メソッドを使用します。
public final class RxJava2CallAdapterFactory extends CallAdapter.Factory {
// 現在のスレッドで動作する同期observableを作成する
public static RxJava2CallAdapterFactory create() {
return new RxJava2CallAdapterFactory(null, false);
}
// 非同期オブザーバーオブジェクトを作成する
public static RxJava2CallAdapterFactory createAsync() {
return new RxJava2CallAdapterFactory(null, true);
}
// 指定したスレッドで動作する同期オブザーバーオブジェクトを作成する。
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static RxJava2CallAdapterFactory createWithScheduler(Scheduler scheduler) {
if (scheduler == null) throw new NullPointerException("scheduler == null");
return new RxJava2CallAdapterFactory(scheduler, false);
}
private final @Nullable Scheduler scheduler;// スレッドスケジューラー
private final boolean isAsync;// 同期されているか
private RxJava2CallAdapterFactory(@Nullable Scheduler scheduler, boolean isAsync) {
this.scheduler = scheduler;
this.isAsync = isAsync;
}
@Override public @Nullable CallAdapter<?, ?> get(
Type returnType,
Annotation[] annotations,
Retrofit retrofit
) {
Class<?> rawType = getRawType(returnType);// は、Typeに対応するClassを取得する。
// 戻り値の型がCompletableの場合にCallAdapterを作成する。
if (rawType == Completable.class) {
// Completable is not parameterized (which is what the rest of this method deals with) so it
// can only be created with a single configuration.
return new RxJava2CallAdapter(Void.class, scheduler, isAsync, false, true, false, false,
false, true);
}
// RxJava2CallAdapterFactory がサポートしている他の型かどうかを判定する。
boolean isFlowable = rawType == Flowable.class;
boolean isSingle = rawType == Single.class;
boolean isMaybe = rawType == Maybe.class;
if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) {
return null;
}
boolean isResult = false;// 観測不可能<Response<User>>
boolean isBody = false;//観測不可能<Result<User>>
Type responseType;
//返り値の型はパラメータ化されたパラダイム、つまりパラダイム・インスタンスでなければならない。<String> パラダイムの種類や境界を明示的に指定する。
if (!(returnType instanceof ParameterizedType)) {
String name = isFlowable ? "Flowable"
: isSingle ? "Single"
: isMaybe ? "Maybe" : "Observable";
throw new IllegalStateException(name + " return type must be parameterized"
+ " as " + name + "<Foo> or " + name + "<? extends Foo>");
}
// ResponseType型の上限を取得する。<? extends Foo> Foo型を返す。
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
// クラスタイプを取得する
Class<?> rawObservableType = getRawType(observableType);
// Observableかどうかを判断する<Response<User>>この場合、Responseのパラダイム・インスタンスが内部的にパラダイム・インスタンスであるかどうかも判断する必要がある。
if (rawObservableType == Response.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Response must be parameterized"
+ " as Response<Foo> or Response<? extends Foo>");
}// 実際の戻り値の型がある場合は、最初のパラダイム実パラメータのみを取得する。
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
// Observableかどうかを判断する<Result<User>>この場合も、Result内の型がパラダイム・インスタンスかどうかを判断する必要がある。
} else if (rawObservableType == Result.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Result must be parameterized"
+ " as Result<Foo> or Result<? extends Foo>");
}
responseType = getParameterUpperBound(0, (ParameterizedType) observableType);
isResult = true;
} else {
responseType = observableType;
isBody = true;// を直接本体にラベル付けした。
}
return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
isSingle, isMaybe, false);
}
}
RxJava2を使用して、Observable、Flowable、Single、Completable、Maybeなどをサポートするobservableファクトリを作成します。 Observableは
以下は、コンストラクタのパラメータに基づいて最終的な Observable を決定する RxJava2CallAdapter の具体的な実装です。
final class RxJava2CallAdapter<R> implements CallAdapter<R, Object> {
// コンストラクタとそのメンバ変数を省略する
....
@Override public Type responseType() {
return responseType;
}
@Override public Object adapt(Call<R> call) {
Observable<Response<R>> responseObservable = isAsync
? new CallEnqueueObservable<>(call)
: new CallExecuteObservable<>(call);
Observable<?> observable;
if (isResult) { // Observableに対応する<Result<User>>
observable = new ResultObservable<>(responseObservable);
} else if (isBody) {// Observableに対応する<User>
observable = new BodyObservable<>(responseObservable);
} else {// Observableに対応する<Response<User>>
observable = responseObservable;
}
// isFlowable、isSingle、isCompletableなどを省略する。
.....
return RxJavaPlugins.onAssembly(observable);
}
}
ここで注意しなければならないのは、前述のように BodyObservable と ResultObservable です。
bservableには結果が直接格納され、2XXレスポンスコードではonNext、それ以外ではonErrorが呼び出されます。
final class BodyObservable<T> extends Observable<T> {
......
private static class BodyObserver<R> implements Observer<Response<R>> {
private final Observer<? super R> observer;
.....
@Override public void onNext(Response<R> response) {
// コード範囲を確認したら、あとはonNextするだけだ。
if (response.isSuccessful()) {// return this.code >= 200 && this.code < 300;
observer.onNext(response.body());
} else {
terminated = true;
Throwable t = new HttpException(response);
try {
observer.onError(t);
} catch (Throwable inner) {
Exceptions.throwIfFatal(inner);
RxJavaPlugins.onError(new CompositeException(t, inner));
}
}
}
......
}
}
}
Converter.Factory
上記のコードは、ConverterFactoryを使用する過程でResponseTにResponseBodyの上流になり、ここでConverter.Factoryは、主にカスタムタイプがRequestBodyに変換されるか、またはResponseBodyのカスタムタイプに変換される達成するために、具体的なコードです。具体的なコードは次のとおりです:
jpublic interface Converter<F, T> {
@Nullable T convert(F value) throws IOException;
/** Creates {@link Converter} instances based on a type and target usage. */
abstract class Factory {
// ResponseBodyをカスタムタイプに変換する
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
Annotation[] annotations, Retrofit retrofit) {
return null;
}
// カスタムタイプをRequestBodyに変換する
public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
// カスタムタイプを文字列に変換する
public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
// 型の上限を取得する。{@code Map<String, ? extends Runnable>} returns {@code Runnable}.
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
// 元の型を抽出する{@code List<? extends Runnable>} returns {@code List.class}.
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
}
}
Converterはインターフェースで、以下はOptionalConverterの一般的な実装例です。
@IgnoreJRERequirement // Only added when Optional is available (Java 8+ / Android API 24+).
final class OptionalConverterFactory extends Converter.Factory {
static final Converter.Factory INSTANCE = new OptionalConverterFactory();
// responseBodyConverterメソッドをオーバーライドする。
@Override public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(type) != Optional.class) {
return null;
}
Type innerType = getParameterUpperBound(0, (ParameterizedType) type);
Converter<ResponseBody, Object> delegate =
retrofit.responseBodyConverter(innerType, annotations);
return new OptionalConverter<>(delegate);
}
@IgnoreJRERequirement
static final class OptionalConverter<T> implements Converter<ResponseBody, Optional<T>> {
final Converter<ResponseBody, T> delegate;
OptionalConverter(Converter<ResponseBody, T> delegate) {
this.delegate = delegate;
}
// インターフェイスはconvertを実装すればいい。
@Override public Optional<T> convert(ResponseBody value) throws IOException {
return Optional.ofNullable(delegate.convert(value));
}
}
}
GsonConverterFactory
コンバータは型変換に使われます。Retrofitがリクエストを発行するにはRequestBodyが必要で、コンバータは入力されたパラメータオブジェクトをバイト文字列に変換します。あるいは、RespondBody のバイトストリームからオブジェクトインスタンスにデシリアライズします。
public final class GsonConverterFactory extends Converter.Factory {
// ファクトリーのインスタンスを作成する
public static GsonConverterFactory create() {
return create(new Gson());
}
// ファクトリーのインスタンスを作成する
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static GsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new GsonConverterFactory(gson);
}
private final Gson gson;
private GsonConverterFactory(Gson gson) {
this.gson = gson;
}
// 適応させたい型に合わせて、Gsonで対応するTypeAdapterを探し、GsonResponseBodyConverterを作成する。
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
// 適切なコンバーターを見つける
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
// 同様に、適応させるために渡された型に従って、Gsonで対応するTypeAdapterを探す。
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
}
}
ここでは TypeAdapter が型変換を行います。 Gson はデフォルトで多くの型に対応していますが、 カスタムオブジェクトの場合は通常 ReflectiveTypeAdapterFactory ファクトリクラスで作成したアダプタを使用します。
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// まず、元の型を取得し、それがObject型かどうかを確認する。
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null;
} else {
//Object型の場合は、対応するオブジェクトのコンストラクタを探す。
ObjectConstructor<T> constructor = this.constructorConstructor.get(type);
return new ReflectiveTypeAdapterFactory.Adapter(constructor, this.getBoundFields(gson, type, raw));
}
}
constructorConstructor.get(type)の実行を見てみましょう。
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
final Type type = typeToken.getType();// 変換タイプを取得する
Class<? super T> rawType = typeToken.getRawType();
final InstanceCreator<T> typeCreator = (InstanceCreator)this.instanceCreators.get(type);
// 1.対応するtypeCreatorがあるか判定する。
if (typeCreator != null) {
// ObjectConstructorの匿名内部クラスオブジェクトが存在すればそれを作成し、最終的にはtypeCreatorに依存して具体的なインスタンスオブジェクトを作成する
return new ObjectConstructor<T>() {
public T construct() {
return typeCreator.createInstance(type);
}
};
} else {
//2.ない場合は、元の型のrawTypeCreatorがあるかどうかを調べる。
final InstanceCreator<T> rawTypeCreator = (InstanceCreator)this.instanceCreators.get(rawType);
if (rawTypeCreator != null) {
return new ObjectConstructor<T>() {
public T construct() {
return rawTypeCreator.createInstance(type);
}
};
} else {
// 3.これらのどれもが利用できない場合は、デフォルトの参照なしコンストラクタを呼び出してインスタンスオブジェクトを生成してみよう。
ObjectConstructor<T> defaultConstructor = this.newDefaultConstructor(rawType);
if (defaultConstructor != null) {
return defaultConstructor;
} else {
// 4. 参照なしのコンストラクタでもない場合は、パラダイム型かどうかを判断し、型に基づいてインスタンスオブジェクトを生成する。
ObjectConstructor<T> defaultImplementation = this.newDefaultImplementationConstructor(type, rawType);
//5. それでもオブジェクトを構築できない場合は、大物キラーである
return defaultImplementation != null ? defaultImplementation : this.newUnsafeAllocator(type, rawType);
}
}
}
}
UnsafeAllocatorでは、UnsafeクラスのallocateInstanceメソッドがリフレクションを使用して呼び出され、コンストラクタが方程式から除外され、メモリ内の領域を直接開くことによってインスタンスオブジェクトが作成されます。
public static UnsafeAllocator create() {
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
final Object unsafe = f.get((Object)null);
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
return new UnsafeAllocator() {
public <T> T newInstance(Class<T> c) throws Exception {
assertInstantiable(c);
return allocateInstance.invoke(unsafe, c);
}
};
} catch (Exception var6) {
......
GsonRequestBodyConverter
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
GsonRequestBodyConverter
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
引用
Key promoter プラグイン - ショートカットキー表示のヒント




