基于SpringBoot 拦截所有接口类及实现类中方法上面的自定义注解

在我们实际的开发过程当中,可能会用到一些自定义注解去实现一些功能,自定义注解可以注解在接口类的方法上,也可以注解在接口实现类的方法上,这样这个自定义注解运用起来就会更加的灵活,其实想要在SpringBoot中达到这样的效果是一件非常简单的事,奈何某度搜索引擎及某某DN搜索出来的文章都没有一篇是切中我想搜索的内容

以下的实现方式借鉴了keetone大佬的(原创)spring aop无法拦截接口上的注解文章,中间做了一些修改,如果想要更为详细的了解可以去看看他的这篇文章

好了费话不多说,直接上代码(中间有很多我个人的理解的描述,可能不正确,勿喷, 但功能是能用的):

pom引入aspectjweaver依赖

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

自定义一个注解类,例如:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Ax {
    String value() default "";
}

Target({ElementType.METHOD})由这里定义只能注解在方法上面,如果有其他的需求,可以去看看 java.lang.annotation.ElementType这个枚举类的定义

定义一个方法匹配切入点顾问类,例如:

public class AxMethodPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {

    @Override
    public boolean matches(@NotNull Method method, @NotNull Class<?> targetClass) {
        return AnnotatedElementUtils.hasAnnotation(method, Ax.class);
    }
}

定义这个类的目的是把所有接口类及接口实现类中被我们上面定义的Ax注解的所有方法都过滤出来,让spring给我们自动生成CGLIB代理(其实是自动生成SpringProxy代理)

定义一个方法拦截器

这个是作为过滤出来的方法的切面处理,我们对于自己定义的注解要实现功能的处理逻辑就写在这个里面

public class AxInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
        String name = invocation.getMethod().getName();
        Ax ax = invocation.getMethod().getAnnotation(Ax.class);
        Object ret;
        if (Objects.nonNull(ax)) {
            // 在这里,拦截的有可能是我们的接口实现类的方法,也有可能是Spring为我们自动创建的SpringProxy动态代理的方法
            // 在接口实现类和动态代理类中,方法上面的注解源信息都可以拿到
            // 当我们在接口类的方法上添加我们自定义的注解时,Spring为我们创建的动态代理的方法上也会有该注解,且含有我们在接口类中设置的注解源信息,比如Ax的value值
            
            // 调用该方法之前的处理逻辑... 这里只是打印了一下信息,自定义的注解想要实现的功能逻辑就从这里开始写,调用方法前后或者不调用方法大家各自发挥
            System.out.println("before -- " + ax.value() + " --->> " + name);
            ret = invocation.proceed();
            // 调用该方法之后的处理逻辑... 这里也只是打印了一下信息
            System.out.println("after -- " + ax.value() + " ---->> " + name);
        } else {
            // 该地方是的接口类中被注解的方法的拦截,但是在这里我们拿不到自定义注解源信息:ax都是null,更别说获取ax的value
            // 故让方法调用继续往下,下面就有可能是实现类的方法调用,也有可能是Spring为我们创建的动态代理类的方法调用
            ret = invocation.proceed();
        }
        return ret;
    }
}

让Spring把我们上面定义的类粘合起来

@Configuration
public class AxConfig implements BeanPostProcessor {

    /**
     * 切面处理类注册为Spring的Bean
     * 这个里面是我们自定义注解需想要实现功能的核心
     *
     * @return AxInterceptor
     */
    @Bean
    public AxInterceptor axInterceptor() {
        return new AxInterceptor();
    }

    /**
     * 方法匹配切入点顾问
     * (我个人觉得更像是ApplicationContext中的Bean的扫描过滤器,过滤出需要创建动态代理的方法)
     *
     * @return AxMethodPointcutAdvisor
     */
    @Bean
    public AxMethodPointcutAdvisor axMethodPointcutAdvisor() {
        AxMethodPointcutAdvisor advisor = new AxMethodPointcutAdvisor();
        // 设置切面处理
        advisor.setAdvice(axInterceptor());
        return advisor;
    }

    /**
     * 该Bean是让Spring自动创建代理的核心
     * 可以不添加这个Bean,如果不添加,那么接口类的方法注解就拦截不到了,只能拦截到接口实现类中被注解的方法
     * 它和上面申明的AxMethodPointcutAdvisor一起协同工作,它要创建的代理由AxMethodPointcutAdvisor中matches方法决定
     *
     * @return DefaultAdvisorAutoProxyCreator
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }
}

需要注意的是该类为BeanPostProcessor的实现,也可以不实现BeanPostProcessor接口,不影响使用,如果不实现BeanPostProcessor接口,那么在Spring启动的时候会出现一行像是错误的提示(Info级别的日志):

Bean 'axConfig' of type [com.proxyclient.advisor.AxConfig$$EnhancerBySpringCGLIB$$60a28775] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

这个提示出现的原因是我们在这个类里申明了DefaultAdvisorAutoProxyCreator这个Bean,如果不创建DefaultAdvisorAutoProxyCreator这个Bean,就可以不用实现BeanPostProcessor接口,启动时不会出现上面那个日志

这里需要说明一下:我们在申明DefaultAdvisorAutoProxyCreator这个Bean之后,可能会产生一些"副使用"比如我自己这个Demo中的SpringRetry实例,启动会报错,需要在Retryable的实例上添加@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)这个注解才能正常启动和工作,这只是我个人遇到了这个问题及解决的办法,所以大家在以这种方式去实现接口类和实现类的自定义注解拦截时需要注意由DefaultAdvisorAutoProxyCreator这个Bean带来的副作用

测试一下

测试接口类

public interface AxTestService {

    @Ax(value = "interface a method")
    String a();

    @Ax(value = "interface b method")
    String b();

    String c();

    String d();

    String e();
}

测试实现类

@Service
public class AxTestServiceImpl implements AxTestService {

    @Override
    public String a() {
        System.out.println("impl a method");
        return "a method\n";
    }

    @Override
    public String b() {
        System.out.println("impl b method");
        return "b method\n";
    }

    @Ax(value = "impl c method")
    @Override
    public String c() {
        System.out.println("impl c method");
        return "c method\n";
    }

    @Ax(value = "impl d method")
    @Override
    public String d() {
        System.out.println("impl d method");
        return "d method\n";
    }

    @Override
    public String e() {
        System.out.println("impl e method");
        return "e method\n";
    }
}

测试WEB入口类

@RestController
@RequestMapping("/ax")
public class AxTestController {

    @Resource
    private AxTestService axTestService;

    @GetMapping
    public void a() {
        System.out.println("controller-->> " + axTestService.a());
        System.out.println("controller-->> " + axTestService.b());
        System.out.println("controller-->> " + axTestService.c());
        System.out.println("controller-->> " + axTestService.d());
        System.out.println("controller-->> " + axTestService.e());
    }
}

直接浏览器请求:http://host:port/ax时,打印日志为:

before -- interface a method --->> a
impl a method
after -- interface a method ---->> a
controller-->> a method

before -- interface b method --->> b
impl b method
after -- interface b method ---->> b
controller-->> b method

before -- impl c method --->> c
impl c method
after -- impl c method ---->> c
controller-->> c method

before -- impl d method --->> d
impl d method
after -- impl d method ---->> d
controller-->> d method

impl e method
controller-->> e method

通过上面的日志可以看到,不论是在接口类中还是实现类中的方法上添加了Ax注解的都拦截到了,没有被Ax注解的方法就不会被拦截,这已经达到了我们想要的效果
必须要注意的是接口类必须要有实现类
必须要注意的是接口类必须要有实现类
必须要注意的是接口类必须要有实现类
重要的事情说三遍,除非你自己为这些接口创建动态代理类,不然Spring启动直接报错!
不会有人还要问:你上面不是由Spring自动创建代理了吗?

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容