Spring AOP でインタフェースにつけたアノテーションを目印にする (Matches annotations on the interface with Spring AOP)

----

## 問題

Spring AOP(の AspectJ)では、インタフェースにつけたアノテーションを目印としたポイントカットが使用出来ない。このため、MyBatisのMapperインタフェースのメソッドなどに対して、アノテーション目印としたインタセプタを差し込むことができない。

## 原因

メソッドにつけたアノテーションは継承クラス(あるいはインタフェースを実装したクラス)のメソッドに引き継がれない。そして Spring AOP(というか AspectJ)が対象とするのはインスタンス化されたBeanであり、その型はインタフェースを実装したクラスとなるため、インタフェースのメソッドにつけたアノテーションは検査されない。

## 解決方法

インタフェースにつけたアノテーションも、ポイントカット判定時に検査するように`AspectJPointcutAdvisor`をカスタマイズして使用する。

### AspectJPointcutAdvisorの拡張

ポイントカットを取得するメソッドがあるので、これをオーバーライドして、インタフェースのアノテーションも検査します。

```InterfaceMethodAwareAspectJPointcutAdvisor.java
package aop;

import java.lang.reflect.Method;
import java.util.stream.Stream;

import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;

public class InterfaceMethodAwareAspectJPointcutAdvisor extends AspectJPointcutAdvisor {
    public InterfaceMethodAwareAspectJPointcutAdvisor(@NonNull AbstractAspectJAdvice advice) {
        super(advice);
    }

    @Override
    public Pointcut getPointcut() {
        val original = super.getPointcut();
        return new ComposablePointcut(original)
                .union(new InterfaceAwareMethodMatcher(original.getMethodMatcher()));
    }

    @RequiredArgsConstructor
    private static class InterfaceAwareMethodMatcher implements MethodMatcher {
        @NonNull
        private final MethodMatcher underlying;

        @Override
        public boolean isRuntime() {
            return underlying.isRuntime();
        }

        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            return Stream.of(targetClass.getInterfaces())
                         .anyMatch(x -> underlying.matches(method, x));
        }

        @Override
        public boolean matches(Method method, Class<?> targetClass, Object... args) {
            return Stream.of(targetClass.getInterfaces())
                         .anyMatch(x -> underlying.matches(method, x, args));
        }
    }
}

```

### Configurationクラス

上記で作成した、インタフェースのアノテーションも検査する`InterfaceMethodAwareAspectJPointcutAdvisor`を使用して、Advisorを登録する。

```AspectConfiguration.java
@Configuration
@EnableAspectJAutoProxy
public class AspectConfiguraiton implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory factory) {
    	val original = new FooAspect();
    	val advisor = new ReflectiveAspectJAdvisorFactory()
    	        .getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(original, "fooAspect"));
    	        
    	 advisors.stream()
    	         .map(advisor -> new InterfaceMethodAwareAspectJPointcutAdivsor((AbstractAspectJAdvice) advisor))
    	         .forEacth(advisor -> factory.registerSingleton(resolveName(advisor), advisor));

    }
    
    private static String resolveName(AspectJPointcutAdvisor advisor) {
        val info = (AspectJPrecedenceInformation) advisor.getAdvice();
        return info.getAspectName() + '#' + info.getDeclarationOrder();
    }
}
```