LoginSignup
5
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-10-17

問題

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();
    }
}
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3