AspectJをAndroidアプリに埋め込む

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() {/* implementation */} void restartApplication(AppCompatActivity context) {/* implementation */} boolean isLocaleChanged() {/* implementation */} }
      
      





このクラスをアプリケーションに追加することにより、システム内の言語を変更するときに自動的に再起動することを教えます。



しばらくの間、私は既製のソリューションを探していて、それらを味見しました。 その結果、私は自分のバージョンを書きました。 それはどのように機能しますか、なぜ車輪を再発明しなければならなかったのですか?



アスペクトの一般的なフレームワークのみが上記で説明されていることに注意してください。すべてのメカニズムは、 スライス接続ポイントからコンテキストデータを取得するために、 ヒントにいくつかの余分な行を追加する必要があります 。 完全なアスペクトコードは、記事の最後にあるデモアプリケーションにあります。 この例は、注入のコンパクトさと簡潔さを示しています。これにより、アスペクトクラスがアプリケーションに浸透します。



哲学



この免責事項から、アスペクトクラスはオブジェクトクラスのデコレータであることがわかります。 いくつかの機能をアスペクトに実装するタスクに取り組むとき、言語やイデオロギーのオブジェクト指向のメカニズム自体を忘れないことが重要です。 悪い方法-アスペクトが判読不能になり、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) {/* implementation */} protected void writeExitTime(JoinPoint.StaticPart jp) {/* implementation */} }
      
      







strict()スライスは、アスペクトクラス自体のトラバースを防ぐために、結合ポイントのトラバースを遮断します。 説明されている残りの構造は、非常にシンプルで直感的です。 メソッド、コンストラクター、および静的メソッドの選択を意図的に異なるスライスに分離します。これにより、特定のアプリケーションおよび特定のタスクに対してプロファイラーを柔軟に構成できます。 抽象アスペクトクラスの説明にあるissingleton()マーカーは、各子孫がシングルトンになることを明示的に宣言しています。 実際、記録は不要です。なぜなら デフォルトでは、すべてのアスペクトクラスはシングルトンです。 私たちの場合、このマーカーは、このプロパティについてサードパーティの開発者に通知するためにここで必要です。 私の実践では、暗黙的な機能にラベルを付けることを好みます。そのため、他の人にとってモジュールの理解と読みやすさが向上します。



テストに直接進みます。 アスペクトはどのように効果的ですか?





これはすべて、単体テストと機能テストを作成するときに大きな利益をもたらします。 しかし、常に重要な「しかし」があります。 アスペクトのテストは、リアルタイム分析である可能性が高くなります。 従来のテストサイクルを実装するには、テストデコレータが実行される環境またはコンテキストを記述する必要があります。 これは、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



開発中に対処しなければならなかったいくつかの明白な、あまりそうではないことに注意します。





何を持っていきますか?



アスペクトへのヒープの前に、同じ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プラグイン



All Articles