基于SpringBoot 攔截所有接口類及實(shí)現(xiàn)類中方法上面的自定義注解

在我們實(shí)際的開發(fā)過程當(dāng)中,可能會用到一些自定義注解去實(shí)現(xiàn)一些功能,自定義注解可以注解在接口類的方法上,也可以注解在接口實(shí)現(xiàn)類的方法上,這樣這個(gè)自定義注解運(yùn)用起來就會更加的靈活,其實(shí)想要在SpringBoot中達(dá)到這樣的效果是一件非常簡單的事,奈何某度搜索引擎及某某DN搜索出來的文章都沒有一篇是切中我想搜索的內(nèi)容

以下的實(shí)現(xiàn)方式借鑒了keetone大佬的(原創(chuàng))spring aop無法攔截接口上的注解文章,中間做了一些修改,如果想要更為詳細(xì)的了解可以去看看他的這篇文章

好了費(fèi)話不多說,直接上代碼(中間有很多我個(gè)人的理解的描述,可能不正確,勿噴, 但功能是能用的):

pom引入aspectjweaver依賴

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

自定義一個(gè)注解類,例如:

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

Target({ElementType.METHOD})由這里定義只能注解在方法上面,如果有其他的需求,可以去看看 java.lang.annotation.ElementType這個(gè)枚舉類的定義

定義一個(gè)方法匹配切入點(diǎn)顧問類,例如:

public class AxMethodPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {

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

定義這個(gè)類的目的是把所有接口類及接口實(shí)現(xiàn)類中被我們上面定義的Ax注解的所有方法都過濾出來,讓spring給我們自動生成CGLIB代理(其實(shí)是自動生成SpringProxy代理)

定義一個(gè)方法攔截器

這個(gè)是作為過濾出來的方法的切面處理,我們對于自己定義的注解要實(shí)現(xiàn)功能的處理邏輯就寫在這個(gè)里面

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)) {
            // 在這里,攔截的有可能是我們的接口實(shí)現(xiàn)類的方法,也有可能是Spring為我們自動創(chuàng)建的SpringProxy動態(tài)代理的方法
            // 在接口實(shí)現(xiàn)類和動態(tài)代理類中,方法上面的注解源信息都可以拿到
            // 當(dāng)我們在接口類的方法上添加我們自定義的注解時(shí),Spring為我們創(chuàng)建的動態(tài)代理的方法上也會有該注解,且含有我們在接口類中設(shè)置的注解源信息,比如Ax的value值
            
            // 調(diào)用該方法之前的處理邏輯... 這里只是打印了一下信息,自定義的注解想要實(shí)現(xiàn)的功能邏輯就從這里開始寫,調(diào)用方法前后或者不調(diào)用方法大家各自發(fā)揮
            System.out.println("before -- " + ax.value() + " --->> " + name);
            ret = invocation.proceed();
            // 調(diào)用該方法之后的處理邏輯... 這里也只是打印了一下信息
            System.out.println("after -- " + ax.value() + " ---->> " + name);
        } else {
            // 該地方是的接口類中被注解的方法的攔截,但是在這里我們拿不到自定義注解源信息:ax都是null,更別說獲取ax的value
            // 故讓方法調(diào)用繼續(xù)往下,下面就有可能是實(shí)現(xiàn)類的方法調(diào)用,也有可能是Spring為我們創(chuàng)建的動態(tài)代理類的方法調(diào)用
            ret = invocation.proceed();
        }
        return ret;
    }
}

讓Spring把我們上面定義的類粘合起來

@Configuration
public class AxConfig implements BeanPostProcessor {

    /**
     * 切面處理類注冊為Spring的Bean
     * 這個(gè)里面是我們自定義注解需想要實(shí)現(xiàn)功能的核心
     *
     * @return AxInterceptor
     */
    @Bean
    public AxInterceptor axInterceptor() {
        return new AxInterceptor();
    }

    /**
     * 方法匹配切入點(diǎn)顧問
     * (我個(gè)人覺得更像是ApplicationContext中的Bean的掃描過濾器,過濾出需要創(chuàng)建動態(tài)代理的方法)
     *
     * @return AxMethodPointcutAdvisor
     */
    @Bean
    public AxMethodPointcutAdvisor axMethodPointcutAdvisor() {
        AxMethodPointcutAdvisor advisor = new AxMethodPointcutAdvisor();
        // 設(shè)置切面處理
        advisor.setAdvice(axInterceptor());
        return advisor;
    }

    /**
     * 該Bean是讓Spring自動創(chuàng)建代理的核心
     * 可以不添加這個(gè)Bean,如果不添加,那么接口類的方法注解就攔截不到了,只能攔截到接口實(shí)現(xiàn)類中被注解的方法
     * 它和上面申明的AxMethodPointcutAdvisor一起協(xié)同工作,它要創(chuàng)建的代理由AxMethodPointcutAdvisor中matches方法決定
     *
     * @return DefaultAdvisorAutoProxyCreator
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }
}

需要注意的是該類為BeanPostProcessor的實(shí)現(xiàn),也可以不實(shí)現(xiàn)BeanPostProcessor接口,不影響使用,如果不實(shí)現(xiàn)BeanPostProcessor接口,那么在Spring啟動的時(shí)候會出現(xiàn)一行像是錯(cuò)誤的提示(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)

這個(gè)提示出現(xiàn)的原因是我們在這個(gè)類里申明了DefaultAdvisorAutoProxyCreator這個(gè)Bean,如果不創(chuàng)建DefaultAdvisorAutoProxyCreator這個(gè)Bean,就可以不用實(shí)現(xiàn)BeanPostProcessor接口,啟動時(shí)不會出現(xiàn)上面那個(gè)日志

這里需要說明一下:我們在申明DefaultAdvisorAutoProxyCreator這個(gè)Bean之后,可能會產(chǎn)生一些"副使用"比如我自己這個(gè)Demo中的SpringRetry實(shí)例,啟動會報(bào)錯(cuò),需要在Retryable的實(shí)例上添加@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)這個(gè)注解才能正常啟動和工作,這只是我個(gè)人遇到了這個(gè)問題及解決的辦法,所以大家在以這種方式去實(shí)現(xiàn)接口類和實(shí)現(xiàn)類的自定義注解攔截時(shí)需要注意由DefaultAdvisorAutoProxyCreator這個(gè)Bean帶來的副作用

測試一下

測試接口類

public interface AxTestService {

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

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

    String c();

    String d();

    String e();
}

測試實(shí)現(xiàn)類

@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時(shí),打印日志為:

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

通過上面的日志可以看到,不論是在接口類中還是實(shí)現(xiàn)類中的方法上添加了Ax注解的都攔截到了,沒有被Ax注解的方法就不會被攔截,這已經(jīng)達(dá)到了我們想要的效果
必須要注意的是接口類必須要有實(shí)現(xiàn)類
必須要注意的是接口類必須要有實(shí)現(xiàn)類
必須要注意的是接口類必須要有實(shí)現(xiàn)類
重要的事情說三遍,除非你自己為這些接口創(chuàng)建動態(tài)代理類,不然Spring啟動直接報(bào)錯(cuò)!
不會有人還要問:你上面不是由Spring自動創(chuàng)建代理了嗎?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容