AOPパラダイムを研究し、AOPを大規模プロジェクトで使用して開発した経験を共有したいと思います。
アスペクト指向プログラミング(AOP)は、エンドツーエンドの機能を強調し、いわゆるアスペクトまたはアスペクトクラスの形式で分離するパラダイムです。 これは、アプリケーションコードへのアスペクトの「エンジンコンパートメント」インジェクションのためのセマンティックツールとメカニズムの存在を意味します。 したがって、アスペクト自体がアプリケーションのどの部分を処理する必要があるかを決定しますが、アプリケーションは(もちろんコンパイル前に)外部コードがそのセクションに無作法かつ無秩序に注入されることを認識しません。
いくつかの言語(ロシア語、英語、イタリア語、フランス語など)のサポートをアプリケーションに提供するという、ささいなタスクがあるとしましょう。 あなたは私たちがすべてのリソースの言語的および地域的な差別化を持っていると言うでしょう、そしてあなたは正しいでしょう。 アプリケーションが組み込みリソースを使用せず、サーバーからそれらを「プル」する場合を除きます。 一般に、この状況は一般的であり、簡単に解決できます。システムクラスから継承するBaseActivity抽象クラスのハンドラーに数行を追加すると、すべてが機能します。 そして、あなたはこれらの線のペアなしで行うことができます。 そして、基本クラスがなくても。 また、必要に応じて、1つのファイルをアプリケーションにコピーするか、gradleに依存関係を追加するだけで、すべてが自動的に実行されます。
したがって、タスクは明確です、と書いています。
package com.archinamon.example.xpoint; import android.support.v7.app.AppCompatActivity; import android.app.Application; public aspect LocaleMonitor { pointcut saveLocale(): execution(* MyApplication.onCreate()); pointcut checkLocale(AppCompatActivity activity): this(activity) && execution(* AppCompatActivity+.onCreate(..)); after(): saveLocale() { saveCurrentLocale(); } before(AppCompatActivity activity): checkLocale(activity) { if (isLocaleChanged()) { saveCurrentLocale(); restartApplication(activity); } } void saveCurrentLocale() {} void restartApplication(AppCompatActivity context) {} boolean isLocaleChanged() {} }
このクラスをアプリケーションに追加することにより、システム内の言語を変更するときに自動的に再起動することを教えます。
しばらくの間、私は既製のソリューションを探していて、それらを味見しました。 その結果、私は自分のバージョンを書きました。 それはどのように機能しますか、なぜ車輪を再発明しなければならなかったのですか?
アスペクトの一般的なフレームワークのみが上記で説明されていることに注意してください。すべてのメカニズムは、
スライスと
接続ポイントからコンテキストデータを取得するために、
ヒントにいくつかの余分な行を追加する必要があり
ます 。 完全なアスペクトコードは、記事の最後にあるデモアプリケーションにあります。 この例は、注入のコンパクトさと簡潔さを示しています。これにより、アスペクトクラスがアプリケーションに浸透します。
哲学
この免責事項から、アスペクトクラスはオブジェクトクラスのデコレータであることがわかります。 いくつかの機能をアスペクトに実装するタスクに取り組むとき、言語やイデオロギーのオブジェクト指向のメカニズム自体を忘れないことが重要です。 悪い方法-アスペクトが判読不能になり、Officeツールクラスが詰め込まれた場合。 最善の方法-ロジック全体を、可能な限り外界から隔離された独立したオブジェクトモジュールとして記述すること。これは、アスペクトデコレータを介してアプリケーションに接続されます。
一種の装飾メカニズムとしての開発へのアスペクトアプローチについて言えば、その主な機能に注意する必要があります。 プロシージャ、関数、およびメソッドの概念は、
「アドバイス」という用語に置き換えられています。 チップは、
スライスを埋め込んだコードのその部分の
前 (
前 )、
後 (
後 )、または
その代わり (
前後 )に適用でき
ます 。 同様に、
スライス (
pointcut )の概念は、アスペクトクラスが接続されているプログラム内のポイントの説明を隠します。 この説明は、パラメーターの完全なセットです-クラスの名前やメソッドのシグネチャ、またはその呼び出しまたは実行の場所など。 1つのスライスで少なくとも1つの
結合ポイント (
joinPoint )を記述できますが、最大値は制限されず、アプリケーション全体に浸透できます。
テスト中
アスペクトがすべての栄光に現れることができる別の領域は、テストとデバッグです。 メソッドとクラスのユニバーサルプロファイラーを作成するのは、思ったより簡単です。
アスペクトMyProfilerImplはプロファイラーを拡張します package com.archinamon.example.xpoint; aspect MyProfilerImpl extends Profiler { private pointcut strict(): within(com.archinamon.example.*) && !within(*.xpoint.*); pointcut innerExecution(): strict() && execution(!public !static * *(..)); pointcut constructorCall(): strict() && call(*.new(..)); pointcut publicExecution(): strict() && execution(public !static * *(..)); pointcut staticsOnly(): strict() && execution(static * *(..)); private pointcut catchAny(): innerExecution() || constructorCall() || publicExecution() || staticsOnly(); before(): catchAny() { writeEnterTime(thisJoinPointStaticPart); } after(): catchAny() { writeExitTime(thisJoinPointStaticPart); } } abstract aspect Profiler issingleton() { abstract pointcut innerExecution(); abstract pointcut constructorCall(); abstract pointcut publicExecution(); abstract pointcut staticsOnly(); protected static final Map<String, Long> sTimeData = new ConcurrentHashMap<>(); protected void writeEnterTime(JoinPoint.StaticPart jp) {} protected void writeExitTime(JoinPoint.StaticPart jp) {} }
strict()スライスは、アスペクトクラス自体のトラバースを防ぐために、結合ポイントのトラバースを遮断します。 説明されている残りの構造は、非常にシンプルで直感的です。 メソッド、コンストラクター、および静的メソッドの選択を意図的に異なるスライスに分離します。これにより、特定のアプリケーションおよび特定のタスクに対してプロファイラーを柔軟に構成できます。 抽象アスペクトクラスの説明にある
issingleton()マーカーは、各子孫がシングルトンになることを明示的に宣言しています。 実際、記録は不要です。なぜなら デフォルトでは、すべてのアスペクトクラスはシングルトンです。 私たちの場合、このマーカーは、このプロパティについてサードパーティの開発者に通知するためにここで必要です。 私の実践では、暗黙的な機能にラベルを付けることを好みます。そのため、他の人にとってモジュールの理解と読みやすさが向上します。
テストに直接進みます。 アスペクトはどのように効果的ですか?
- まず第一に、その内部機構によって。 特定の関数(関数、プロシージャ、オブジェクト自体など)のアプリケーションのコンテキストをエミュレートするのではなく、 通常の環境でコードのセクションをテストしています。
- 2番目の重要な利点は、コンパイル段階での注入により、ジャンクションで利用可能な豊富なデバッグ情報です。
- 3番目の非常に重要なプラスは、スライスのすべてのパラメーターを記述するいわゆるNamePatternの強度にあります。 ニックパターンを使用して、多数の類似および類似のセクションをカバーできます(たとえば、1行のカットですべてのゲッターセッターをキャプチャします)。
これはすべて、単体テストと機能テストを作成するときに大きな利益をもたらします。 しかし、常に重要な「しかし」があります。 アスペクトのテストは、リアルタイム分析である可能性が高くなります。 従来のテストサイクルを実装するには、テストデコレータが実行される環境またはコンテキストを記述する必要があります。 これは、AOPが使い慣れたフレームワークの代替として適切ではないことを意味します。
上記のすべてを要約します。 アスペクトアプローチは、すでに実行中のアプリケーションのエラーを検出するために、作業成果物をカバーするアナライザーとモニターの古典的なテストに追加するのに適しています。 たとえば、手動テスト中またはアプリケーションのベータ版として。
PS便利なものそして、側面では、指をスワイプするだけで、例外ハンドラーでクラスやメソッドをカバーできます。 abstract aspect NetworkProtector { abstract pointcut myClass(); Response around(): myClass() && execution(* executeRequest(..)) { try { return proceed(); } catch (NetworkException ex) { Response response = new Response(); response.addError(new Error(ex)); return response; } } }
これは最も単純なオプションであり、より複雑で、すべての段階で詳細に段階的に説明できます。 自転車v3.14:理由と方法
AOPはAndroidなしでもマスターできます。 私のプロジェクトでこの技術を使用するために、Gradleビルドシステム用の独自のプラグインを書き始めました。 しかし、すでに既製のソリューションがあります、と知識のある読者は言うでしょう! そして彼は正しいでしょう。 そして、それは完全に正しいとは限りません。 使用可能なものはすべて、狭い範囲の条件にのみ適していますが、必要な組み合わせや可能な組み合わせのすべてを網羅していません。 たとえば、フレーバーで正しく動作したり、ソースコードを分解する独自のソースセットを作成したりするプラグインはありませんでした。 また、Javaアノテーションのスタイルでのみアスペクトを記述できるものもありました。 それで、私は自分のプラグインを実装する途中ですべての熊手を集めました。 その結果、すべてのボトルネックがカバーされました。
さらに、 ハッキングされたセマンティックプラグイン(UltimateバージョンのHello Spring @ AOP!)も手に入れました。Android Studioの将来のバージョンでの公式リリースを待っています題名:
AspectJサポートラベル:タイプ強化
サブコンポーネント-ツール-スタジオサブコンポーネント-ツール-グラドル-ide
優先度-小ターゲット-1.6
開発中に対処しなければならなかったいくつかの明白な、あまりそうではないことに注意します。
コンパイラタスクスタックの構成ここでは、前処理ツールとRetrolambdaのサポートが頭痛の種になりました。 これは最初で、主観的に最も難しいレーキでした。 私は最初にGradleの拡張機能の作成を開始し、開発の最初の段階でタスクスタックを管理する際の落とし穴をすべて集めました。 その結果、プラグインはプロジェクトにRetrolambdaの接続を明示的にチェックし、結果が肯定的である場合、プルされる前にキューに組み込まれます。 それ以外の場合、javaコンパイラの直後にキューに入れられます。
パフォーマンスとインクリメンタルビルドタスクスタックを正しく編成することは、戦いの半分です。 それを最適化し、パフォーマンスにオッズを与えます-異なるレベルのタスク。 AspectJは、独自のコンパイラーajcと接続します。 そして、すべてが処理されます。 率直に言って、まだプラグインを開発する余地があります。 プリプロセッサによるコード生成、Javaソースのアセンブリ、dexファイル(Android環境の実行可能ファイル)のアセンブリのタスクは、使い慣れた最適化された条件で機能します。 ただし、ajcはまだ増分モードで動作していません。
ワークスペースおよびGradleツールの適応最初は、基本的な機能と人気のあるプラグインのサポートを実装しました。 ソースコードがコンパイルされ、アスペクトが埋め込まれ、アプリケーションが構築されています。 テストサイトになったこのプロジェクトは、フレーバーの導入のひどい日付に近づいていました。 これらのツールを使用して友達を作らないと、プラグイン全体が無関係になることがわかりました。 それと並行して、ワークスペースにソースを便利かつ簡潔に装備したかったのです。 すぐに、プラグインはビルドオプションを使用して、タスクに適切に統合することを学びました。 そして最後に、java、groovy、aidlなどと一緒に、独自のリソースフォルダ-aspectjを取得しました。
何を持っていきますか?
アスペクトへのヒープの前に、同じ8からJava 8およびStreamAPI構文を取得します(これは配列、コレクション、およびリストの機能的な作業に必要です)結局のところ、Java APIは既にAndroid APIに含まれており、残念ながら8つの革新を誇っていません。
build.gradleプロジェクトファイルが変換されています。 buildscript { repositories { mavenCentral() maven { url 'https://raw.github.com/Archinamon/GradleAspectJ-Android/master/' } maven { url 'https://raw.github.com/Archinamon/RetroStream/master/' } } dependencies { //retrolambda classpath 'me.tatarka:gradle-retrolambda:3.2.3' //aspectj classpath 'com.archinamon:AspectJ-gradle:1.0.16' } } // Required because retrolambda is on maven central repositories { mavenCentral() } apply plugin: 'com.android.application' //or apply plugin: 'com.android.library' apply plugin: 'me.tatarka.retrolambda' apply plugin: 'com.archinamon.aspectj' dependencies { compile 'com.archinamon:RetroStream:1.0.4' }
それだけです! 詳細な設定はバックグラウンドで行いますが、githubのソースコードですべての詳細を確認できます。
参照資料
デモンストレーション
プロジェクト 。
Android Studio用の
Gradleプラグイン 。