首頁 > 軟體

SpringBoot中利用AOP和攔截器實現自定義註解

2022-06-27 14:00:21

前言

最近遇到了這樣一個工作場景,需要寫一批dubbo介面,再將dubbo介面註冊到閘道器中,但是當dubbo介面異常的時候會給前端返回非常不友好的異常。所以就想要對異常進行統一捕獲處理,但是對於這種service介面使用@ExceptionHandler註解進行異常捕獲也是捕獲不到的,應為他不是Controller的介面。這時就想到了自定義一個註解去實現異常捕獲的功能。

Spring實現自定義註解

通過攔截器+AOP實現自定義註解的實現,在這裡攔截器充當在指定註解處要執行的方法,aop負責將攔截器的方法和要註解生效的地方做一個織入(通過動態註解生成代理類實現)。

1.引入相關依賴

spring-boot-starter:spring的一些核心基礎依賴

spring-boot-starter-aop:spring實現Aop的一些相關依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.相關類

1.自定義註解類

@Target({ElementType.TYPE})  //說明了Annotation所修飾的物件範圍,這裡,的作用範圍是類、介面(包括註解型別) 或enum
@Retention(RetentionPolicy.RUNTIME)  //自定義註解的有效期,Runtime:註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在
@Documented //標註生成javadoc的時候是否會被記錄
public @interface EasyExceptionResult {
}

2.攔截器類

/**
 * MethodInterceptor是AOP專案中的攔截器(注:不是動態代理攔截器),
 * 區別與HandlerInterceptor攔截目標時請求,它攔截的目標是方法。
 */
public class EasyExceptionIntercepter implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        AnnotatedElement element=invocation.getThis().getClass();
        EasyExceptionResult easyExceptionResult=element.getAnnotation(EasyExceptionResult.class);
        if (easyExceptionResult == null) {
            return invocation.proceed();
        }
        try {
            return invocation.proceed();
        } catch (Exception rpcException) {
            //不同環境下的一個例外處理
            System.out.println("發生異常了");
            return null;
        }
    }
}

3.切點切面類

MethodInterceptor的實現類能作為切面的執行方式是應為Interceptor的父類別是Advice。

@Configuration
public class EasyExceptionAdvisor {
 
    /**
     * 放在最後執行
     * 等待ump/紀錄檔等記錄結束
     *
     * @return {@link DefaultPointcutAdvisor}物件
     */
    @Bean
    @Order(Integer.MIN_VALUE)
    public DefaultPointcutAdvisor easyExceptionResultAdvisor() {
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        //針對EasyExceptionResult註解建立切點
        AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(EasyExceptionResult.class, true);
        EasyExceptionIntercepter interceptor = new EasyExceptionIntercepter();
        advisor.setPointcut(annotationMatchingPointcut);
        //在切點執行interceptor中的invoke方法
        advisor.setAdvice(interceptor);
        return advisor;
    }
 
}

4.自定義註解的使用

@Service
@EasyExceptionResult  //自定義異常捕獲註解
public class EasyServiceImpl {
 
    public void testEasyResult(){
        throw new NullPointerException("測試自定義註解");
    }
 
}

5.效果

@SpringBootApplication
public class JdStudyApplication {
 
    public static void main(String[] args) {
        ConfigurableApplicationContext context=SpringApplication.run(JdStudyApplication.class, args);
        EasyServiceImpl easyService=context.getBean(EasyServiceImpl.class);
        easyService.testEasyResult();
    }
 
}

至此就實現了通過spring實現自定義註解。

Java實現自定義註解

雖然通過Spring實現了自定義註解但是還有辦法讓我們不通過Spring也能實現自定義註解,畢竟註解是早於Spring的。

JDK中有一些元註解,主要有@Target,@Retention,@Document,@Inherited用來修飾註解,如下為一個自定義註解。

@Target({ElementType.TYPE})  //說明了Annotation所修飾的物件範圍,這裡,的作用範圍是類、介面(包括註解型別) 或enum
@Retention(RetentionPolicy.RUNTIME)  //自定義註解的有效期,Runtime:註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在
@Documented //標註生成javadoc的時候是否會被記錄
public @interface EasyExceptionResult {
}

@Target

表明該註解可以應用的java元素型別

Target型別描述
ElementType.TYPE應用於類、介面(包括註解型別)、列舉
ElementType.FIELD應用於屬性(包括列舉中的常數)
ElementType.METHOD應用於方法
ElementType.PARAMETER應用於方法的形參
ElementType.CONSTRUCTOR應用於建構函式
ElementType.LOCAL_VARIABLE應用於區域性變數
ElementType.ANNOTATION_TYPE應用於註解型別
ElementType.PACKAGE應用於包
ElementType.TYPE_PARAMETER1.8版本新增,應用於型別變數)
ElementType.TYPE_USE1.8版本新增,應用於任何使用型別的語句中(例如宣告語句、泛型和強制轉換語句中的型別)

@Retention

表明該註解的生命週期

生命週期型別描述
RetentionPolicy.SOURCE編譯時被丟棄,不包含在類檔案中
RetentionPolicy.CLASSJVM載入時被丟棄,包含在類檔案中,預設值
RetentionPolicy.RUNTIME由JVM 載入,包含在類檔案中,在執行時可以被獲取到

@Document

表明該註解標記的元素可以被Javadoc 或類似的工具檔案化

@Inherited

表明使用了@Inherited註解的註解,所標記的類的子類也會擁有這個註解

通過Cglib實現

在我們定義好註解之後就需要考慮如何將註解和類繫結到一起,在執行期間達到我們想要的效果,這裡就可以引入動態代理的機制,將註解想要做的操作在方法執行前,類編譯時就進行一個織入的操作如下。  

public static void main(String[] args) {
        Class easyServiceImplClass=EasyServiceImpl.class;
        //判斷該物件是否有我們自定義的@EasyExceptionResult註解
        if(easyServiceImplClass.isAnnotationPresent(EasyExceptionResult.class)){
            final EasyServiceImpl easyService=new EasyServiceImpl();
            //cglib的位元組碼加強器
            Enhancer enhancer=new Enhancer();
            將目標物件所在的類作為Enhaner類的父類別
            enhancer.setSuperclass(EasyServiceImpl.class);
            通過實現MethodInterceptor實現方法回撥,MethodInterceptor繼承了Callback
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    try{
                        method.invoke(easyService, args);
                        System.out.println("事務結束...");
                    }catch (Exception e){
                        System.out.println("發生異常了");
                    }
                    return proxy;
                }
            });
 
            Object obj= enhancer.create();;
            EasyServiceImpl easyServiceProxy=(EasyServiceImpl)obj;
            easyServiceProxy.testEasyResult();
        }
 
    }

執行效果:

通過JDk動態代理實現

public class EasyServiceImplProxy implements InvocationHandler {
 
    private EasyServiceImpl target;
 
    public void setTarget(EasyServiceImpl target)
    {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 這裡可以做增強
        System.out.println("已經是代理類啦");
        try{
            return  method.invoke(proxy, args);
        }catch (Exception e){
            System.out.println("發生異常了");
            return null;
        }
    }
 
 
    /**
     * 生成代理類
     * @return 代理類
     */
    public Object CreatProxyedObj()
    {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}

Cglib和JDK動態代理的區別

java動態代理是利用反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。

而cglib動態代理是利用asm開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。

1、如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP 

2、如果目標物件實現了介面,可以強制使用CGLIB實現AOP 

3、如果目標物件沒有實現了介面,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

如何強制使用CGLIB實現AOP?

(1)新增CGLIB庫,SPRING_HOME/cglib/*.jar

(2)在spring組態檔中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

JDK動態代理和CGLIB位元組碼生成的區別?

(1)JDK動態代理只能對實現了介面的類生成代理,而不能針對類

(2)CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法

因為是繼承,所以該類或方法最好不要宣告成final 

寫在最後

@ExceptionHandler註解的使用可參考文章Java實現優雅的引數校驗方法詳解

到此這篇關於SpringBoot中利用AOP和攔截器實現自定義註解的文章就介紹到這了,更多相關SpringBoot自定義註解內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com