web-dev-qa-db-ja.com

Javaアノテーションを使用したアスペクト指向プログラミング

"AOP Fundamentals" というタイトルの投稿で、私はKing's English AOPとは何か、そしてそれが何をするかの説明を求めました。私は、すべての理論を説明するのに役立つ非常に役立つ回答と記事へのリンクを受け取りました。

しかし今、AOPは完全に注目されており、これらのすべての記事と章の抜粋は素晴らしいですが、every single caseでは、高尚な理論、曖昧なUMLモデル、私の好みにはあまりにも高すぎる抽象化。

ここに、AOP理論についての私の理解があります。明確にするために、間違っているように見えるものがあれば、お知らせください。

  1. ロギング、認証、同期、検証、例外処理などの横断的な関心事は、コードベースのほぼすべてのコンポーネント/モジュールで広く使用されているため、非AOPシステムで高度に結合されます。

  2. AOPは、aspects(クラス/メソッド)を定義し、join pointsを使用してこれらの横断的関心事を抽象化します。advice、およびpointcuts

    a。 アドバイス-横断的関心事を実装する実際のコード(アスペクトの方法、おそらく?)(つまり、実際のロギング、検証、認証などを行う)

    b。 Join Point-特定のアスペクトのアドバイスを実行させる非AOPコードでトリガーされるイベント(非AOPコードに「織り込まれる」)

    c。 Pointcut-基本的に、アドバイス実行への結合ポイント(トリガーイベント)のマッピング

  3. すべてのアスペクトはモジュール化され(LoggingAspect、AuthenticationAspect、ValidationAspectなど)コンポーネントになり、AspectWeaverで登録されます。非AOP/POJOコードが結合ポイントに出くわすと、AspectWeaverは非AOPコードの周りにマッピングされたアドバイスを「織り」ます(統合します):

 public class LoggingAspect 
 {
 // ... 
 
 public void log(String msg){...} 
} 
 
 public class ExceptionHandlingAspect 
 {
 // .. 
 
 public void handle(Exception exc){.. 。} 
} 
 
 public class NonAOPCode 
 {
 // ... 
 
 @LoggingAspect @ ExceptionHandlingAspect 
 public void foo()
 {
 //いくつかのことを行う... 
} 
} 
 
 //ドライバー
 public static int main void(String [] args)
 {
 NonAOPCode nonAOP = new NonAOPCode(); 
 nonAOP。 foo(); 
} 
 
 // AspectWeaverは*魔法のように*メソッド呼び出しで織り込まれるため、mainは次のようになります。
 {
 NonAOPCode nonAOP = new NonAOPCode(); 
 
 log(someMsg); 
 nonAOP.foo(); 
 handle(someExc); 
} 
 

$ 64,000の質問:JavaベースのAOPがターゲットにあるのか、それとも先にあるのか、そしてその理由は何ですか?correctlyアノテーションを使用して、アスペクト、アドバイス、結合ポイント、ポイントカット、およびこのいわゆるアスペクトウィーバーを実装するにはどうすればよいですか?

62
Eugie

@LogExecTime注釈を使用して、いくつかの注釈付きメソッドにかかった時間をログに記録したいと想像してください。

最初に注釈LogExecTimeを作成します:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecTime {

}

次に、アスペクトを定義します。

@Component  // For Spring AOP
@Aspect
public class LogTimeAspect {
    @Around(value = "@annotation(annotation)")
    public Object LogExecutionTime(final ProceedingJoinPoint joinPoint, final LogExecTime annotation) throws Throwable {
        final long startMillis = System.currentTimeMillis();
        try {
            System.out.println("Starting timed operation");
            final Object retVal = joinPoint.proceed();
            return retVal;
        } finally {
            final long duration = System.currentTimeMillis() - startMillis;
            System.out.println("Call to " + joinPoint.getSignature() + " took " + duration + " ms");
        }

    }
}

LogExecTimeアノテーションが付けられたクラスを作成します。

@Component
public class Operator {

    @LogExecTime
    public void operate() throws InterruptedException {
        System.out.println("Performing operation");
        Thread.sleep(1000);
    }
}

そして、Spring AOPを使用するメイン:

public class SpringMain {

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
        final Operator bean = context.getBean(Operator.class);
        bean.operate();
    }
}

このクラスを実行すると、stdoutで次の出力が得られます。

Starting timed operation
Performing operation
Call to void testaop.Operator.Operate() took 1044 ms

magicが追加されました。私はAspectJウィーバーではなくSpring AOPを使用したので、実行時にプロキシのようなメカニズムを使用して魔法が発生します。したがって、.classファイルは変更されません。たとえば、このプログラムをデバッグし、operateにブレークポイントを配置すると、Springがどのように魔法を実行したかがわかります。

Debug screen shot

Spring AOP実装はnon-intrusiveであり、Springメカニズムを使用するため、@Componentアノテーションを追加し、プレーンnewではなくSpringコンテキストを使用してオブジェクトを作成する必要があります。

反対側のAspectJは、.classファイルを変更します。 AspectJでこのプロジェクトを試し、JadでOperatorクラスを逆コンパイルしました。につながる:

public void operate()
    throws InterruptedException
{
    JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, this);
    operate_aroundBody1$advice(this, joinpoint, LogTimeAspect.aspectOf(), (ProceedingJoinPoint)joinpoint, (LogExecTime)(ajc$anno$0 == null && (ajc$anno$0 = testaop/Operator.getDeclaredMethod("operate", new Class[0]).getAnnotation(testaop/LogExecTime)) == null ? ajc$anno$0 : ajc$anno$0));
}

private static final void operate_aroundBody0(Operator ajc$this, JoinPoint joinpoint)
{
    System.out.println("Performing operation");
    Thread.sleep(1000L);
}

private static final Object operate_aroundBody1$advice(Operator ajc$this, JoinPoint thisJoinPoint, LogTimeAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint, LogExecTime annotation)
{
    long startMillis = System.currentTimeMillis();
    Object obj;
    System.out.println("Starting timed operation");
    ProceedingJoinPoint proceedingjoinpoint = joinPoint;
    operate_aroundBody0(ajc$this, proceedingjoinpoint);
    Object retVal = null;
    obj = retVal;
    long duration = System.currentTimeMillis() - startMillis;
    System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
    return obj;
    Exception exception;
    exception;
    long duration = System.currentTimeMillis() - startMillis;
    System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
    throw exception;
}

private static void ajc$preClinit()
{
    Factory factory = new Factory("Operator.Java", testaop/Operator);
    ajc$tjp_0 = factory.makeSJP("method-execution", factory.makeMethodSig("1", "operate", "testaop.Operator", "", "", "Java.lang.InterruptedException", "void"), 5);
}

private static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0; /* synthetic field */
private static Annotation ajc$anno$0; /* synthetic field */

static 
{
    ajc$preClinit();
}
63
gabuzo

数か月前に、Aspect/JのアスペクトとJavaアノテーションを組み合わせた実用的なケースを実装した方法の例を紹介した記事を作成しました。

http://technomilk.wordpress.com/2010/11/06/combining-annotations-and-aspects-part-1/

アノテーションに適用されるアスペクトは、コード内でアスペクトをより明確に、しかしクリーンな方法で作成し、さらに柔軟性を高めるためにアノテーションでパラメーターを使用できるため、良い組み合わせになると思います。

ところで、Aspect/Jの動作方法は、実行時ではなくコンパイル時にクラスを変更することです。 Aspect/Jコンパイラを介してソースとアスペクトを実行すると、変更されたクラスファイルが作成されます。

私が理解する限り、Spring AOPは、プロキシオブジェクトを作成することにより、異なる方法で織り方(クラスファイルを操作してアスペクト処理を含める)を行い、インスタンス化時にそれを信じます(しかし、私のWordをそれに取ってはいけません) 。

12

多くの掘り出しと肘のグリースの後、自分で答えを見つけました...

はい、AOPはJavaの世界では注釈ベースである必要がありますが、通常の(メタデータ)注釈のようなアスペクト関連の注釈を処理することはできません。タグ付きメソッド呼び出しを傍受し、その前後にアドバイスメソッドを「織り込む」には、AspectJなどの非常に気の利いたAOP中心のエンジンの助けが必要です。 @Christopher McCannは、アノテーション関連の別のスレッドで本当に素晴らしいソリューションを提供し、Google Guiceと組み合わせてAOP Allianceを使用することを提案しました。 AOPサポートに関するGuiceのドキュメントを読んだ後、これはまさに私が探しているものです。ロギング、検証、キャッシングなどの横断的な関心事の「アドバイス」(メソッド呼び出し)を織り込むためのわかりやすいフレームワーク、等.

これはダウジーでした。

5
Eugie

コメントを変更する

// The AspectWeaver *magically* might weave in method calls so main now becomes

// The AspectWeaver *magically* might weave in method calls so main now
// becomes effectively (the .class file is not changed)

私はAOPの春の記事が好きです。チェックアウト 第7章

0
DwB

この非常に便利な投稿への私の貢献は次のとおりです。

非常に簡単な例を取り上げます。いくつかのメソッドの処理でアクションを起こす必要があります。これらには、処理するデータを含むカスタム注釈が付けられています。このデータが与えられた場合、例外が発生するか、メソッドに注釈が付けられていないようにプロセスを続行します。

アスペクトを定義するためのJavaコード:

package com.example;

public class AccessDeniedForCustomAnnotatedMethodsAspect {

public Object checkAuthorizedAccess(ProceedingJoinPoint proceedingJointPoint)
throws Throwable {

    final MethodSignature methodSignature = (MethodSignature) proceedingJointPoint
                                            .getSignature();

    // how to get the method name
    final String methodName = methodSignature
                                            .getMethod()
                                            .getName();

    // how to get the parameter types
    final Class<?>[] parameterTypes = methodSignature
                                            .getMethod()
                                            .getParameterTypes();

    // how to get the annotations setted on the method
    Annotation[] declaredAnnotations = proceedingJointPoint
                                            .getTarget()
                                            .getClass()
                                            .getMethod(methodName, parameterTypes)
                                            .getDeclaredAnnotations();

    if (declaredAnnotations.length > 0) {

        for (Annotation declaredAnnotation : Arrays.asList(declaredAnnotations)) {

            // I just want to deal with the one that interests me
            if(declaredAnnotation instanceof CustomAnnotation) {

                // how to get the value contained in this annotation 
                (CustomAnnotation) declaredAnnotation).value()

                if(test not OK) {
                    throw new YourException("your exception message");
                }

                // triggers the rest of the method process
                return proceedingJointPoint.proceed();
           }
        }
    }
}

Xml構成:

<aop:config>
    <aop:aspect id="accessDeniedForCustomAnnotatedMethods"
               ref="accessDeniedForCustomAnnotatedMethodsAspect">
        <aop:around pointcut="execution(@xxx.zzz.CustomAnnotation * *(..))"
               method="checkAuthorizedAccess" />
    </aop:aspect>
</aop:config>

<bean id="accessDeniedForCustomAnnotatedMethodsAspect"
   class="xxx.yyy.AccessDeniedForCustomAnnotatedMethodsAspect" />

それが役に立てば幸い !

0
lboix