Javassist(Java programming assistant)とか CGLIB(Code Generation Library) とか JDK dynamic proxy とかあんまり聞かないけど

f:id:ts0818:20200523184548p:plain

代理人(だいりにん)とは、自分以外の利益のために、何らかの行為を代わって行う人のこと。または、自分以外の利益のためと称して行う人のこと。代行者代弁者とも。

代理人 - Wikipedia

⇧ 「誰がために鐘は鳴る(著:アーネスト・ヘミングウェイ)」ってことですかね?

代理人と言えば、 

代理人 [DVD]

代理人 [DVD]

  • 発売日: 2008/02/20
  • メディア: DVD
 

⇧ 「代理人(監督:スティーヴン・ギレンホール)」 が有名ですが、

ジェイク・ギレンホール」氏は、

ミッション:8ミニッツ [Blu-ray]

ミッション:8ミニッツ [Blu-ray]

  • 発売日: 2013/01/23
  • メディア: Blu-ray
 

⇧ 「スティーヴン・ギレンホール」の息子さんだったんですね。

ちなみに、

ジェイク・ジレンホーJake Gyllenhaal1980年12月19日 -)は、アメリカ合衆国俳優。身長183cm。スウェーデン系「Gyllenhaal」の発音は「イエレンフーレヘイ」表記が近いが、出身地アメリカでの発音は「ジレンホール」表記が近い。日本では「レンホー」表記が定着している。

ジェイク・ジレンホール - Wikipedia

⇧ 「ジレンホール」「ギレンホール」と、表記ゆれがありますが、英語圏は「ギ」か「ジ」なら「ジ」になるってことかと思いきや、

nlab.itmedia.co.jp

 GIFが「Graphics Interchange Format」の略であることを考えると、グラフィックスに合わせて「ギフ(gif)」と読みたくなりますが、Wilhiteさんによれば正解は「ジフ(jif)」。「ギフではありません」と前者をすっぱり否定しています。

 ただし英語圏ではいまだ「ギフ」派が優勢だったりと、生みの親の見解は世論と微妙にずれている様子。戦いはまだまだ(もしかすると未来永劫)続きそうです。

【7年前の今頃は】「GIF」の正しい読み方は「ジフ」?「ギフ」? 開発者が正解を発表 - ねとらぼ

⇧ 「GIF」だと「ギ」なんね...

 

代理つながりで、

代理ミュンヒハウゼン症候群(だいりミュンヒハウゼンしょうこうぐん、Münchausen syndrome by proxyMSbPMSP)とはミュンヒハウゼン症候群の一形態であって、傷害の対象が自分自身ではなく何か代理のものであるような精神疾患である。

代理ミュンヒハウゼン症候群 - Wikipedia

⇧ 「代理ミュンヒハウゼン症候群」ってな症状があって、

ミュンヒハウゼン症候群(ミュンヒハウゼンしょうこうぐん、Münchausen syndrome)は虚偽性障害に分類される精神疾患の一種。症例として周囲の関心や同情を引くために病気を装ったり、自らの体を傷付けたりするといった行動が見られる。1951年にイギリスの内科医リチャード・アッシャー英語版によって発見され、「ほら吹き男爵」の異名を持ったドイツ貴族、ミュンヒハウゼン男爵にちなんで命名された。

ミュンヒハウゼン症候群 - Wikipedia

⇧ 「ミュンヒハウゼン症候群」が元ネタですって。

代理ミュンヒハウゼン症候群(だいりミュンヒハウゼンしょうこうぐん、Münchausen syndrome by proxyMSbPMSP」ってな感じで「プロキシ」によって代替ですか。

 

By the way、 

Javaフレームワーク知名度がお高いのが、Spring だと思うんだけど、な~んか仕組みがややこしい気がするんよね...

Javaってシンプルなつくりを目指してた割に、どうも複雑な方向に進んでる気がしてならんのだが...というボヤキで開幕するわけですけど、素直な気持ちですのでご容赦ください。

 

というわけで、レッツトライ~。

 

Javassist (Java programming assistant)って何ぞ?

まぁ、Wikipediaさんのお力を借りましょうか。

Javassist (Java programming assistant) is a Java library providing a means to manipulate the Java bytecode of an application.

In this sense Javassist provides the support for structural reflection, i.e. the ability to change the implementation of a class at run time.

Bytecode manipulation is performed at load-time through a provided class loader.

Javassist - Wikipedia

⇧ アプリケーションの「Java bytecode」を操作できるJavaのライブラリですって。

Java bytecode」っていうのは、

Javaバイトコードは、Java仮想マシンが実行する命令形式である。

バイトコードオペコードは長さが1バイトであるが、引数を持つものもあるため、結果として複数バイトの命令となる。256個のオペコードの全てが使われているのではなく、51個が将来のために予約されている。その他について、Javaプラットフォームの開発元であるサン・マイクロシステムズは、3つのコードを永久に実装しないままにした

Javaバイトコード - Wikipedia

⇧ ってあるように、Java仮想マシンJVMJava Virtual Machine)のための「バイトコード」っつうことで、

バイトコード (bytecode) は、バイト指向の、中間表現のコードすなわち中間コードの総称である。バイトコードという名前は命令の構成がバイト指向であること、すなわち命令長がバイト可変長であったり、命令中のフィールドの区切りがバイト区切りに合っているといったことから来ている。

バイトコード - Wikipedia

⇧ 「中間表現」ってやつが、

言語のような形を取るとは限らないが、何らかの中間表現を利用することで、多種類の入力と多種類の出力への処理系の対応の工数が定性的には少なくなる。n種類のプログラミング言語とm種類のターゲットがあるとする。中間表現を用いなければ種類の実装が必要となるが、中間表現を用いれば種類の実装のみを必要とする。

中間表現 - Wikipedia

⇧ ってな感じで、煩わしさを解消しようといった思想性が垣間見えますと。

ですが、

ただし必ずしも容易になるとは限らず、中間表現自体の設計のためにむしろ難しくなるかもしれない(あらゆる言語のあらゆる機能に対応し、あらゆるターゲットのあらゆる機能に対応できなければならないから)。

中間表現 - Wikipedia

⇧ ってな感じで、思ったようにいかない場合があるんですな。

で「バイト指向」の「中間コード」の総称である「バイトコード」で、JVMのためのものが「Java bytecode」ってことらしいんだけど、

特にJavaの場合オペコードが1バイトである(Javaバイトコードの記事を参照)。しかし、仮想マシン機械語バイトコードと呼ぶことがJavaで広く一般的になったことから、前述のようなバイト指向でなくともバイトコードと呼んでいることも多い。

Javaバイトコード - Wikipedia

⇧ 「オペコード」が「1バイト」って言われても、「オペコード」って何のこっちゃ?

オペコード (operation codeopcode) とは、機械語の1個の命令の部分で、実行する操作 (operation) の種類を指定する部分のこと、およびそのコード(符号)のことである。数式における演算子に相当する。命令のもうひとつの主要部分は、操作される対象を指定するオペランド被演算子)である。

オペコード - Wikipedia

機械語の命令の1つなんだそうな。

それはさておき、JVMの「機械語」を「バイトコード」ってJavaでやり出しちゃったから、「バイト指向」の「中間コード」の総称であるはずの「バイトコード」の定義がややこしいことになってるんですかね?

で、「○○指向」の「○○コード」って

バイト単位でなくビット単位の場合は、ビットコードとも呼ばれる。ワード指向のためにワードコードという語を使っている例も見られる。

バイトコード - Wikipedia

⇧ 最早、何でも良いんかい!

そんなこんなで「Java bytecode」ってのは、「JVM」のための「バイトコード」であり、「Java bytecode」は

⇧ ってあるように、Javaソースコードが記載されたファイルが、Javaコンパイラで「Java bytecode」が記載されたファイルに変換されるらしく、

  • .class ファイル
  • .jar ファイル

といったファイルが代表的なものになるようです。

つまり、「JavassistJava programming assistant)」は、このコンパイルされた後に作成される「.class ファイル」や「.jar ファイル」を操作することができるんですと。

Javassist enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors, Javassist provides two levels of API: source level and bytecode level.

Using the source-level API, programmers can edit a class file without knowledge of the specifications of the Java bytecode; the whole API is designed with only the vocabulary of the Java language.

Programmers can even specify inserted bytecode in the form of Java source text; Javassist compiles it on the fly. On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors.

Javassist - Wikipedia

⇧ 「JavassistJava programming assistant)」には、

  • source-level API
  • bytecode-level API

の2つのAPIが用意されてるようなので、「JavassistJava programming assistant)」の仕組みとして、「コンパイル」まで含まれているらしいですね。

Java 11 から「javac」コマンドを経由しなくても「.class ファイル」が作成されるっていうのは、「javac」の代わりに「JavassistJava programming assistant)」みたいな「コンパイル」機能をもった役割の奴がおるってことですかね?

 

CGLIB(Code Generation Library)って?

どうやら、「JavassistJava programming assistant)」と似たようなポジションにおるのが、「CGLIB(Code Generation Library)」ってものらしい。

github.com

cglib is a powerful, high performance and quality Code Generation Library. It is used to extend JAVA classes and implements interfaces at runtime. See samples and API documentation to learn more about features.

This library is free software, freely reusable for personal or commercial purposes.

https://github.com/cglib/cglib/wiki

⇧ う~ん、説明が簡潔すぎるんだけど、って言うか、

cglib - Byte Code Generation Library is high level API to generate and transform Java byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.

https://github.com/cglib

⇧ 最初の紹介文のほうがイメージつかみやすいやん。

Java bytecode」を生成、変換できるAPIを提供しますって言うとりますね。どんなところで使われてるかと言うと、「AOPAspect Oriented Programming)」とかが有名ですかね。

「dynamic proxy objects」を生成することで「intercept fields access」とか実現できるよってことなんですかね。

 

「CGLIB」を利用してるプロジェクトは様々あって、その中でも有名なのが、

Open source projects that use cglib and are used by cglib:

  • "Hibernate is a powerful, ultra-high performance object/relational persistence and query service for Java. Hibernate lets you develop persistent objects following common Java idiom, including association, inheritance, polymorphism, composition and the Java collections framework." Uses cglib to generate proxies for persistent classes.
  • "Spring is a J2EE application framework based on code published in Expert One-on-One J2EE Design and Development http://www.wrox.com/books/1861007841.htm by Rod Johnson."

https://github.com/cglib/cglib/wiki

⇧ 「Hibernate」や「Spring」でしょうか。

 

Oreilly の説明だと、

JDK dynamic proxies and CGLIB proxies

The proxy in Spring AOP can be created in two ways:

  • JDK proxy (dynamic proxy): The JDK proxy creates a new proxy object by implementing interfaces of the target object and delegating method calls
  • CGLIB proxy: The CGLIB proxy creates a new proxy object by extending the target object and delegating method calls

JDK dynamic proxies and CGLIB proxies - Hands-On High Performance with Spring 5 [Book]

⇧ 「Spring AOP」での「プロキシ」は、「JDK proxy(dynamic proxy)」「CGLIB」のどっちでも作れるんだそうな。

って言うか、「JDK dynamic proxies」だったり、「JDK proxy(dynamic proxy)」だったり表記の揺れが凄まじいな、わざと?

わざと、認識齟齬を起こそうとしてるんですかね?

まぁ、それは、さておき、「Spring AOP」での「プロキシ」を実現する際に、「JDK proxy(dynamic proxy)」「CGLIB」の2つの方法があり、その違いはと言うと、

⇧ 上記サイト様の図が分かりやすいかと。「インターフェイス」を基にするか「クラス」を基にするかの違いってことね、この違いが混乱を招くことが多いみたいだけど...。

stackoverflow.com

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.

It is important to grasp the fact that Spring AOP is proxy-based. See Understanding AOP proxies for a thorough examination of exactly what this implementation detail actually means.

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-introduction

https://stackoverflow.com/questions/51795511/when-will-is-cglib-proxy-used-by-spring-aop

⇧ 「ビジネスクラスビジネスロジックを扱う、Service層とかですかね?)」で「インターフェイス」が実装されてることが多いと思うから、

  1. インターフェイス」を実装してない「ビジネスクラス」ではデフォルトで「CGLIB」が使用される。
  2. CGLIBの使用を強制することが可能なケース
    1. インターフェイスで宣言されていないメソッドにアドバイスする必要がある場合
    2. プロキシ化されたオブジェクトを具象型としてメソッドに渡す必要がある場合(まれに)

っていう感じで、「CGLIB」の使いどころは、「2.CGLIBの使用を強制することが可能なケース」のどちらかになってくるんですかね?

qiita.com

ここでtoString()について考えてみると、メソッドの定義はObjectクラスで定義されているわけですが……そのObjectクラスは何もインタフェースを実装していません。
なのでインタフェースベースのJdkDynamicAopProxyでプロキシ化した場合、toString()に処理を割り込めないんですね。

JdkDynamicAopProxyでプロキシ化したInterfaceのtoString()でハマったお話 - Qiita

⇧ 上記サイト様の例は、「2.CGLIBの使用を強制することが可能なケース」>「1.インターフェイスで宣言されていないメソッドにアドバイスする必要がある場合」のケースに該当するってことでしょうかね?

あれ?もしかして「CGLIB」って現場だと必要になってくるケースが多いんかな?

 

 

JDK dynamic proxy って?

「CGLIB」の説明でも「dynamic proxy objects」とか出てきたんだけど、 そも「dynamic proxy objects」って何すか?

「プロキシ」って言葉が出てくるんだけど、

qiita.com

  • 初心者向けの文献では動的プロキシを紹介していない。
  • 「プロキシ」という名称がサーバサイドの「プロキシ」と混同しやすい。
  • 一般的なJavaの開発において動的プロキシが必要な局面は僅少である。(そもそも、動的プロキシでしか実現できない機能はない)

動的プロキシ(DynamicProxy)をできる限りわかりやすく解説してみる - Qiita

⇧  ってあるように、あの許可されたものだけがネットワークに繋がるように制限をかけることで、開発者を苦しめてくれる「プロキシ」とは全く別物らしいんですよね。

そんなサーバでの「プロキシ」ですが、
lanchesters.site

海外プロキシは主にプロキシを経由して規制を回避し、本来アクセスできないWEBサイトにアクセスするための手段として使われます。

https://lanchesters.site/proxy-is/

⇧ 海外だと、真逆の発想というのが衝撃。

何故、このような用途が生まれるかと言うと、

例えば中国では、国策によりFacebookGoogleYoutubeといった海外WEBサイトへのアクセスが禁止されています。これを回避するため、中国国内から国外のプロキシが頻繁に利用されます。

https://lanchesters.site/proxy-is/

⇧ 情報操作が行われてるような環境が世の中には有り得るんですと。

 

すみません、脱線しました。

じゃあ、「dynamic proxy objects」の「プロキシ」ってのは、何のことを言っているんだというと、

qiita.com

静的」なプロキシを解説します。これは、GoFGang of Four)のデザインパターンで紹介されている一般的なプロキシパターンに相当します。

プロキシパターン(Proxy Pattern)をできる限りわかりやすく解説してみる - Qiita

⇧ とあるように、デザインパターンの1つである「プロキシパターン」で言及される「プロキシ」のことを意味してるみたいです。

ただ、機能という面で見ると、

  • Webサーバなんかの「プロキシ」
  • デザインパターンの「プロキシパターン」の「プロキシ」

どちらも「代理」を務めるという意味では似てるんですかね。

で、「プロキシパターン」だと厳しい状況が出てきた時に爆誕したのが、「動的プロキシ」ってことらしいですかね。

で、Oracleさんが説明してくれてるんだけど、

docs.oracle.com

ダイナミック・プロキシ・クラスとは、実行時に指定されたインタフェースのリストを実装するクラスのことです(クラスのインスタンスでのいずれかのインタフェースによるメソッド呼出しがエンコードされ、同じインタフェースを介して別のオブジェクトにディスパッチされる)。このため、インタフェースのリストに対して型保証されたプロキシ・オブジェクトを作成できます。コンパイル時ツールなどでプロキシ・クラスを事前に生成する必要がなくなります。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ クソ分かり辛い。

どうも、次の説明を見た感じでは、 

ダイナミック・プロキシ・クラスのインスタンスでのメソッド呼出しは、インスタンス呼出しハンドラ内の1つのメソッドにディスパッチされ、呼び出されたメソッドを識別するjava.lang.reflect.Methodオブジェクト、および引数を含むObject型の配列を使用してエンコードされます。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ 「リフレクション」と同じパッケージ使ってる、って言うか「リフレクション」と同じようなことやってるように見えるんだけど。

何か、 

ダイナミック・プロキシ・クラス (以下「プロキシ・クラス」)とは、クラスが作成されるときに、実行時に指定されたインタフェースのリストを実装するクラスのことです。

プロキシ・クラスとそれらのインスタンスは、java.lang.reflect.Proxyクラスのstaticメソッドを使用して作成されます。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ 「リフレクション」ということで良いんかね?

nagise.hatenablog.jp

後:「へー。リフレクションってなんか凄いですねー」
先:「まあこれは黒魔術だからな。プロダクトコード(製品のコード)でProxyを使うようなことは滅多に無い。フレームワークでも作らん限りはリフレクション周りはがつがつ使うことはないな」

ProxyパターンとProxyクラスと黒魔術 - プログラマーの脳みそ

⇧ 「リフレクション」と捉えて良いみたい。

何か、Javaで「動的」って言うたら、「リフレクション」が絡んでるって考えて良いんですかね?

 

はい、脱線しましたが、「JDK dynamic proxy」ですが、

Proxy.getProxyClassメソッドは、クラス・ローダーおよびインタフェースの配列が渡されると、プロキシ・クラスのjava.lang.Classオブジェクトを返します。プロキシ・クラスは、指定されたクラス・ローダー内に定義されており、指定されたすべてのインタフェースを実装します。同じ組み合わせのインタフェースのためのプロキシ・クラスがクラス・ローダー内にすでに定義されている場合は、既存のプロキシ・クラスが返されます。そうでない場合は、それらのインタフェースのためのプロキシ・クラスがクラス・ローダー内に動的に生成され、定義されます。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ 複数の「インターフェイス」を実装できるんですと。「Proxy.getProxyClassメソッド」で「dynamic proxy objects」が生成されるんですと、ようやく「dynamic proxy objects」が出てきたな。

で、「Proxy.getProxyClassメソッド」で「dynamic proxy objects」を生成する際には、制約があるんですと。

Proxy.getProxyClassに引き渡されるパラメータには、いくつかの制約があります。

  • interfaces配列のClassオブジェクトはすべて、クラスまたはプリミティブ型ではなくインタフェースを表す必要がある。
  • interfaces配列の2つの要素が同一のClassオブジェクトを参照することはできない。
  • すべての型のインタフェースは、対応するクラス・ローダーから名前で参照できなければならない。つまり、クラス・ローダーがcl、各インタフェースがiの場合は、次の式がtrueでなければならない。
            Class.forName(i.getName(), false, cl) == i
    
  • publicでないインタフェースはすべて同じパッケージになければならない。そうでない場合、プロキシ・クラスがどのパッケージで定義されているかにかかわらず、プロキシ・クラスがインタフェースをすべて実装することは不可能である
  • 指定されたインタフェースが同じ署名を持つ場合、そのようなメソッドの任意の組に対して
    • 任意のメソッドの戻り値の型がプリミティブ型またはvoidの場合、それらのメソッドはすべて戻り値の型が同じでなければならない
    • そうでない場合、いずれかのメソッドによる戻り値の型は、残りのメソッドによる戻り値の型すべてに対して割当て可能でなければならない
  • プロキシ・クラスを作成するときは、Virtual Machineに定義されているクラスの制限を超えてはならない。たとえば、VMが、クラスが実装できるインタフェース数を65535に制限している場合は、interfaces配列のサイズは65535を超えてはならない。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧多いな...そして分かり辛いな...

そして、

これらの制約に対して違反が発生した場合は、Proxy.getProxyClassによってIllegalArgumentExceptionがスローされます。interfaces配列引数またはそのいずれかの要素がnullの場合、NullPointerExceptionがスローされます。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ エラーも分かり辛いな...

「IllegalArgumentException」とか「NullPointerException」なんて、「JDK dynamic proxy」以外でも起こりえるエラーでしょうにね、アンド、制約の数が8つぐらいあるから、どの制約に違反した時のエラーなのか分かるエラーログを出してくれるのかね...

そして、

プロキシ・インタフェースは、順番が区別されます。プロキシ・クラスを2回要求したときに、インタフェースの組み合わせが同じで順番が異なる場合は、2つの異なるプロキシ・クラスが作成されます。プロキシ・クラスは、複数のプロキシ・インタフェースが同じ名前とパラメータ・シグネチャを持つメソッドを共有する場合に、決定論的メソッド呼出しエンコードを提供するために、プロキシ・インタフェースの順番によって区別されます。この説明の詳細は、後続の項「複数のプロキシ・インタフェースで重複するメソッド」を参照してください。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ さらに注意事項が...

「プロキシ・インターフェイスの順番」を考慮する必要があるらしいんだけど、詳細は「複数のプロキシ・インタフェースで重複するメソッド」ってあるんで、一旦保留で。

 

さらにさらに、

同じクラス・ローダーとインタフェースのリストでProxy.getProxyClassが呼び出されるたびに新しいプロキシ・クラスを生成する必要がないように、ダイナミック・プロキシ・クラスAPIの実装が生成されたプロキシ・クラスのキャッシュを保持するべきです(対応するローダーおよびインタフェース・リストでキー付け)。実装は、クラス・ローダーおよびそのすべてのクラスが適切なときにガベージ・コレクトされるのを妨げる方法で、クラス・ローダー、インタフェース、およびプロキシ・クラスを参照しないように注意するべきです。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ 具体的な実現方法は説明してくれない、安定の不親切さ。

そして、

各プロキシ・クラスは、1つの引数(InvocationHandlerインタフェースの実装)を取るpublicコンストラクタを1つ持ちます。

各プロキシ・インスタンスには、呼出しハンドラ・オブジェクト(コンストラクタに渡されたもの)が関連付けられています。プロキシ・インスタンスは、リフレクションAPIを介してpublicコンストラクタにアクセスしなくても、Proxy.newProxyInstanceメソッドを呼び出すことによっても作成できます。このメソッドでは、Proxy.getProxyClassを呼び出すアクションと、呼出しハンドラを使用してコンストラクタを呼び出すアクションが行われます。Proxy.getProxyClassの場合と同じ理由で、Proxy.newProxyInstanceIllegalArgumentExceptionをスローします。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ 「Proxy.newProxyInstanceメソッド」でも「dynamic proxy objects」生成できるんだって...

ドキュメントの作りが何かな~、何だろう、普通、説明するんだったら、

『「JDK dynamic proxy」を実現する方法は、

  • Proxy.getProxyClass メソッド呼び出し
  • Proxy.newProxyInstance メソッド呼び出し

 の2つがあります。』

みたいな感じで、まず、全量を説明しませんかね?

で、その後に、それぞれの実現方法の詳細と注意事項を説明していくようなドキュメントにしたほうが分かりやすいように思うんだけどな~。

すみません、脱線しました。

で、保留してた「複数のプロキシ・インタフェースで重複するメソッド」ですが、

複数のインタフェースに、同じ名前とパラメータ・シグニチャを持つメソッドが含まれる場合は、プロキシ・クラスのインタフェースの順番が区別されます。プロキシ・インスタンス上で重複するメソッドが呼び出された場合、呼出しハンドラに渡されるMethodオブジェクトで、プロキシ・メソッドの呼出しに使用されたインタフェースの参照型から宣言クラスを割り当てることができないことがあります。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ ってあり、どうしてそんな制約が在るのかって言うと、

このような制約が存在するのは、生成されたプロキシ・クラス内の対応するメソッドの実装から、その実装が呼び出されたときに使用されたインタフェースを特定できないためです。このため、プロキシ・インスタンス上で重複するメソッドが呼び出された場合は、メソッド呼出しに使用された参照型にかかわりなく、プロキシ・クラスのインタフェース・リストでそのメソッド(直接またはスーパー・インタフェースから継承)を含むインタフェースのうち、最初のインタフェースのメソッドのMethodオブジェクトが呼出しハンドラのinvokeメソッドに渡されます。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ 「インターフェイスを特定できないためです。」って...して、その理由は?についての説明は一切ないというね、安定の不親切さ。

「重複したメソッドの呼び出される優先順位」については、「インターフェイス配列」の先頭からってことですかね?

ただし、

プロキシ・インタフェースに、java.lang.ObjecthashCodeequals、またはtoStringメソッドと同じ名前およびパラメータ・シグニチャを持つメソッドが含まれる場合は、プロキシ・インスタンス上でそのメソッドが呼び出されると、呼出しハンドラに渡されるMethodオブジェクトの宣言クラスはjava.lang.Objectになります。つまり、publicで非finalであるjava.lang.Objectのメソッドは、呼出しハンドラに渡すMethodオブジェクトを決定するときに、論理的にほかのプロキシ・インタフェースより優先されます。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/reflection/proxy.html

⇧ 「Java.lang.Object」のメソッドは最優先されるんだと。

 

まぁ、 何て言うか、ドキュメントが滅茶苦茶に読み辛いんだけど、ハマりどころが多そうなことだけは伝わってきましたかね。

 

 

AOPAspect Oriented Programming)と Spring AOPAspectJ

Spring AOPで、「CGLIB」「JDK dynamic proxy」あたりが使われてるってのが分かったんだけど、ここで今一度、「AOPAspect Oriented Programming)」って何だっけ?

Wikipediaさんによりますと、 

アスペクト指向プログラミングアスペクトしこうプログラミング、Aspect Oriented Programming、AOP)は、オブジェクト指向ではうまく分離できない特徴(クラス間を横断 (cross-cutting) するような機能)を「アスペクト」とみなし、アスペクト記述言語をもちいて分離して記述することでプログラムに柔軟性をもたせようとする試みで、極端に言えば、あるプログラムFの本質とは無関係だが使うかもしれないプログラムGを、プログラムFを実行したときに割り込ませるような機能である。

このプログラムGこそがアスペクトであり、これを別ファイルに分離し、処理系がディスパッチすることで、プログラムF以外を実行するときにもプログラムGを割り込ませることができる。

アスペクト指向プログラミング - Wikipedia

⇧ 「割り込み機能」ですと。

イメージ的には、

⇧ 上記サイト様のスライドの図のようなことを実現できるってことですかね。

これが、実現できると、10000ぐらいのクラスがあったとしても、ソースコードを修正する際のコストも抑えられるってことですかね。

で、Java だと、

アスペクトの例としては、データ転送帯域の制限や例外の処理などがある。Javaアスペクト指向的要素を追加したAspectJが実験的に実装されている。

アスペクト指向プログラミング - Wikipedia

⇧ 「AspectJ」ってのが魁だったらしいんだけど、「Spring AOP」も台頭してきてるそうな。

で、「Spring AOP」では、

  • CGLIB
  • JDK dynamic proxy

ってもんが関わってきてましたと。

じゃあ、「AspectJ」ってどんな感じなのか? 

AspectJアスペクト ジェイ)は、Javaに対するアスペクト指向プログラミングのための拡張。

初期バージョンはパロアルト研究所で開発され、その後IBMEclipseプロジェクトへ移管された。2005年1月から、AspectWerkzプロジェクトも合流した。

AspectJ - Wikipedia

Eclipseと相性が良いのかね?

って言うか、公式が、eclipse.org のプロジェクトの1つってことになるみたいなんですかね。

www.eclipse.org

clean modularization of crosscutting concerns, such as error checking and handling, synchronization, context-sensitive behavior, performance optimizations, monitoring and logging, debugging support, and multi-object protocols

https://www.eclipse.org/aspectj/

⇧ 「AOPAspect Oriented Programming)」を実現したい処理のモジュール化をしてますってことらしい。

んで「Spring AOP」と「AspectJ」って、どっちも「AOPAspect Oriented Programming)」を実現しようとしてるってことだと思うんだけど、何が違うのよ?

どうやら、

Spring AOP はプロキシベースのシステムであり、プロキシオブジェクト自体(this にバインドされている)とプロキシの背後のターゲットオブジェクト(target にバインドされている)を区別します。

Spring コアテクノロジー - 公式ドキュメントの日本語訳

⇧ ってことらしく、

Spring の AOP フレームワークのプロキシベースの性質により、ターゲットオブジェクト内の呼び出しは、定義上、インターセプトされません。JDK プロキシの場合、プロキシ上のパブリックインターフェースメソッド呼び出しのみをインターセプトできます。CGLIB を使用すると、プロキシでのパブリックおよび protected メソッド呼び出しがインターセプトされます(必要に応じて、パッケージ private メソッドも)。ただし、プロキシを介した一般的な相互作用は、常に公開署名を介して設計する必要があります。

Spring コアテクノロジー - 公式ドキュメントの日本語訳

インターセプトにターゲットクラス内のメソッド呼び出しまたはコンストラクターを含める必要がある場合は、Spring のプロキシベースの AOP フレームワークの代わりに、Spring 駆動のネイティブ AspectJ ウィービングの使用を検討してください。これは、異なる特性を備えた AOP 使用の異なるモードを構成するため、決定を下す前に、ウィービングに精通してください。

Spring コアテクノロジー - 公式ドキュメントの日本語訳

⇧ 『「Spring AOP」は「プロキシベース」のシステム』ってことを強調してるところを見る限り、「AspectJ」は異なるってことになるってことかね?

ややこしいのは、「Spring」で「AspectJ」を使えるらしい...

ロードタイムウィービング(LTW)は、Java 仮想マシンJVM)にロードされるときに、AspectJ アスペクトをアプリケーションのクラスファイルにウィービングするプロセスを指します。

https://spring.pleiades.io/spring/docs/current/spring-framework-reference/core.html#aop-aj-ltw

⇧ って言うか、「ロードタイムウィービング(LTW)」とか知らん単語がまた出てくると言うね...キリが無いな...

 

そして、

www.geeksforgeeks.org

f:id:ts0818:20200523160948j:plain

  • AspectJ: It is an extension for Java programming created at PARC research centre. It uses Java like syntax and included IDE integrations for displaying crosscutting structure. It has its own compiler and weaver, on using it enables the use of full AspectJ language.
  • JBoss: It is an open source Java application server developed by JBoss, used for Java development.
  • Spring: It uses XML based configuration for implementing AOP, also it uses annotations which are interpreted by using a library supplied by AspectJ for parsing and matching.

https://www.geeksforgeeks.org/aspect-oriented-programming-and-aop-in-spring-framework/

⇧ 「JBoss」の存在も交え、三つ巴の様相...

で、上記サイト様の説明によりますと、

Common terminologies in AOP:

  1. Aspect: The class which implements the JEE application cross-cutting concerns(transaction, logger etc) is known as the aspect. It can be normal class configured through XML configuration or through regular classes annotated with @Aspect.
  2. Weaving: The process of linking Aspects with an Advised Object. It can be done at load time, compile time or at runtime time. Spring AOP does weaving at runtime.

    Let’s write our first aspect class but before that have a look at the jars required and the Bean configuration file for AOP.

https://www.geeksforgeeks.org/aspect-oriented-programming-and-aop-in-spring-framework/

⇧ 何か「AOPAspect Oriented Programming)」は、

 って2つの段階を経てから実現されるということらしい。

なので、「ロードタイムウィービング(LTW)」って言うのは、「Weaving」をするってことらしい、多分。

An aspect weaver is a metaprogramming utility for aspect-oriented languages designed to take instructions specified by aspects (isolated representations of significant concepts in a program) and generate the final implementation code. The weaver integrates aspects into the locations specified by the software as a pre-compilation step. By merging aspects and classes (representations of the structure of entities in the program), the weaver generates a woven class.

https://en.wikipedia.org/wiki/Aspect_weaver

⇧ 何かよく分からんけど、「Aspect」を実際に実行できるコードに生成するのが「Weaver」という存在らしい。

「Weaver」の仕組みは、

f:id:ts0818:20200523164243j:plain

An aspect weaver takes information from raw classes and aspects and creates new classes with the aspect code appropriately weaved into the classes.

https://en.wikipedia.org/wiki/Aspect_weaver

⇧ 上図のイメージみたいな感じで、「class Foo」とか「class Bar」といったクラスに「aspect Baz」っていう「Aspect」をマージして、「class Foo」や「class Bar」で「aspect Baz」、つまり「Aspect」の機能を使えるようにするってことみたい。

「Weaver」の具体的な処理は、

Aspect weavers take instructions known as advice specified through the use of pointcuts and join points, special segments of code that indicate what methods should be handled by aspect code. The implementation of the aspect then specifies whether the related code should be added before, after, or throughout the related methods. By doing this, aspect weavers improve modularity, keeping code in one place that would otherwise have been interspersed throughout various, unrelated classes.

https://en.wikipedia.org/wiki/Aspect_weaver

⇧ 「pointcuts」とか「join points」など「Aspect」として振る舞われるべきメソッドを「advice」として受け取って、どこに「Aspect」を差し込むのかを決定するってことみたい、多分。

 

結局、「Spring AOP」が「プロキシベース」ってことは分かったんだけど、「AspectJ」の「ベース」が分からんと言うね...

何て言うか、「AOPAspect Oriented Programming)」もそうなんだけど、Javaってライブラリとの絡みが混沌としてるよね...

今日もモヤモヤしか残らんかったな...

今回はこのへんで。