SpringBoot中利用AOP和攔截器實(shí)現(xiàn)自定義注解
前言
最近遇到了這樣一個(gè)工作場(chǎng)景,需要寫一批dubbo接口,再將dubbo接口注冊(cè)到網(wǎng)關(guān)中,但是當(dāng)dubbo接口異常的時(shí)候會(huì)給前端返回非常不友好的異常。所以就想要對(duì)異常進(jìn)行統(tǒng)一捕獲處理,但是對(duì)于這種service接口使用@ExceptionHandler注解進(jìn)行異常捕獲也是捕獲不到的,應(yīng)為他不是Controller的接口。這時(shí)就想到了自定義一個(gè)注解去實(shí)現(xiàn)異常捕獲的功能。
Spring實(shí)現(xiàn)自定義注解
通過攔截器+AOP實(shí)現(xiàn)自定義注解的實(shí)現(xiàn),在這里攔截器充當(dāng)在指定注解處要執(zhí)行的方法,aop負(fù)責(zé)將攔截器的方法和要注解生效的地方做一個(gè)織入(通過動(dòng)態(tài)注解生成代理類實(shí)現(xiàn))。
1.引入相關(guān)依賴
spring-boot-starter:spring的一些核心基礎(chǔ)依賴
spring-boot-starter-aop:spring實(shí)現(xiàn)Aop的一些相關(guān)依賴
<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.相關(guān)類
1.自定義注解類
@Target({ElementType.TYPE}) //說明了Annotation所修飾的對(duì)象范圍,這里,的作用范圍是類、接口(包括注解類型) 或enum @Retention(RetentionPolicy.RUNTIME) //自定義注解的有效期,Runtime:注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在 @Documented //標(biāo)注生成javadoc的時(shí)候是否會(huì)被記錄 public @interface EasyExceptionResult { }
2.攔截器類
/** * MethodInterceptor是AOP項(xiàng)目中的攔截器(注:不是動(dòng)態(tài)代理攔截器), * 區(qū)別與HandlerInterceptor攔截目標(biāo)時(shí)請(qǐng)求,它攔截的目標(biāo)是方法。 */ 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) { //不同環(huán)境下的一個(gè)異常處理 System.out.println("發(fā)生異常了"); return null; } } }
3.切點(diǎn)切面類
MethodInterceptor的實(shí)現(xiàn)類能作為切面的執(zhí)行方式是應(yīng)為Interceptor的父類是Advice。
@Configuration public class EasyExceptionAdvisor { /** * 放在最后執(zhí)行 * 等待ump/日志等記錄結(jié)束 * * @return {@link DefaultPointcutAdvisor}對(duì)象 */ @Bean @Order(Integer.MIN_VALUE) public DefaultPointcutAdvisor easyExceptionResultAdvisor() { DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); //針對(duì)EasyExceptionResult注解創(chuàng)建切點(diǎn) AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(EasyExceptionResult.class, true); EasyExceptionIntercepter interceptor = new EasyExceptionIntercepter(); advisor.setPointcut(annotationMatchingPointcut); //在切點(diǎn)執(zhí)行interceptor中的invoke方法 advisor.setAdvice(interceptor); return advisor; } }
4.自定義注解的使用
@Service @EasyExceptionResult //自定義異常捕獲注解 public class EasyServiceImpl { public void testEasyResult(){ throw new NullPointerException("測(cè)試自定義注解"); } }
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(); } }
至此就實(shí)現(xiàn)了通過spring實(shí)現(xiàn)自定義注解。
Java實(shí)現(xiàn)自定義注解
雖然通過Spring實(shí)現(xiàn)了自定義注解但是還有辦法讓我們不通過Spring也能實(shí)現(xiàn)自定義注解,畢竟注解是早于Spring的。
JDK中有一些元注解,主要有@Target,@Retention,@Document,@Inherited用來修飾注解,如下為一個(gè)自定義注解。
@Target({ElementType.TYPE}) //說明了Annotation所修飾的對(duì)象范圍,這里,的作用范圍是類、接口(包括注解類型) 或enum @Retention(RetentionPolicy.RUNTIME) //自定義注解的有效期,Runtime:注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在 @Documented //標(biāo)注生成javadoc的時(shí)候是否會(huì)被記錄 public @interface EasyExceptionResult { }
@Target
表明該注解可以應(yīng)用的java元素類型
Target類型 | 描述 |
---|---|
ElementType.TYPE | 應(yīng)用于類、接口(包括注解類型)、枚舉 |
ElementType.FIELD | 應(yīng)用于屬性(包括枚舉中的常量) |
ElementType.METHOD | 應(yīng)用于方法 |
ElementType.PARAMETER | 應(yīng)用于方法的形參 |
ElementType.CONSTRUCTOR | 應(yīng)用于構(gòu)造函數(shù) |
ElementType.LOCAL_VARIABLE | 應(yīng)用于局部變量 |
ElementType.ANNOTATION_TYPE | 應(yīng)用于注解類型 |
ElementType.PACKAGE | 應(yīng)用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,應(yīng)用于類型變量) |
ElementType.TYPE_USE | 1.8版本新增,應(yīng)用于任何使用類型的語句中(例如聲明語句、泛型和強(qiáng)制轉(zhuǎn)換語句中的類型) |
@Retention
表明該注解的生命周期
生命周期類型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 編譯時(shí)被丟棄,不包含在類文件中 |
RetentionPolicy.CLASS | JVM加載時(shí)被丟棄,包含在類文件中,默認(rèn)值 |
RetentionPolicy.RUNTIME | 由JVM 加載,包含在類文件中,在運(yùn)行時(shí)可以被獲取到 |
@Document
表明該注解標(biāo)記的元素可以被Javadoc 或類似的工具文檔化
@Inherited
表明使用了@Inherited注解的注解,所標(biāo)記的類的子類也會(huì)擁有這個(gè)注解
通過Cglib實(shí)現(xiàn)
在我們定義好注解之后就需要考慮如何將注解和類綁定到一起,在運(yùn)行期間達(dá)到我們想要的效果,這里就可以引入動(dòng)態(tài)代理的機(jī)制,將注解想要做的操作在方法執(zhí)行前,類編譯時(shí)就進(jìn)行一個(gè)織入的操作如下。
public static void main(String[] args) { Class easyServiceImplClass=EasyServiceImpl.class; //判斷該對(duì)象是否有我們自定義的@EasyExceptionResult注解 if(easyServiceImplClass.isAnnotationPresent(EasyExceptionResult.class)){ final EasyServiceImpl easyService=new EasyServiceImpl(); //cglib的字節(jié)碼加強(qiáng)器 Enhancer enhancer=new Enhancer(); 將目標(biāo)對(duì)象所在的類作為Enhaner類的父類 enhancer.setSuperclass(EasyServiceImpl.class); 通過實(shí)現(xiàn)MethodInterceptor實(shí)現(xiàn)方法回調(diào),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("事務(wù)結(jié)束..."); }catch (Exception e){ System.out.println("發(fā)生異常了"); } return proxy; } }); Object obj= enhancer.create();; EasyServiceImpl easyServiceProxy=(EasyServiceImpl)obj; easyServiceProxy.testEasyResult(); } }
運(yùn)行效果:
通過JDk動(dòng)態(tài)代理實(shí)現(xiàn)
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 { // 這里可以做增強(qiáng) System.out.println("已經(jīng)是代理類啦"); try{ return method.invoke(proxy, args); }catch (Exception e){ System.out.println("發(fā)生異常了"); return null; } } /** * 生成代理類 * @return 代理類 */ public Object CreatProxyedObj() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
Cglib和JDK動(dòng)態(tài)代理的區(qū)別
java動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來處理。
而cglib動(dòng)態(tài)代理是利用asm開源包,對(duì)代理對(duì)象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。
1、如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP
2、如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP
3、如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)了接口,必須采用CGLIB庫(kù),spring會(huì)自動(dòng)在JDK動(dòng)態(tài)代理和CGLIB之間轉(zhuǎn)換
如何強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP?
(1)添加CGLIB庫(kù),SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK動(dòng)態(tài)代理和CGLIB字節(jié)碼生成的區(qū)別?
(1)JDK動(dòng)態(tài)代理只能對(duì)實(shí)現(xiàn)了接口的類生成代理,而不能針對(duì)類
(2)CGLIB是針對(duì)類實(shí)現(xiàn)代理,主要是對(duì)指定的類生成一個(gè)子類,覆蓋其中的方法
因?yàn)槭抢^承,所以該類或方法最好不要聲明成final
寫在最后
@ExceptionHandler注解的使用可參考文章Java實(shí)現(xiàn)優(yōu)雅的參數(shù)校驗(yàn)方法詳解
到此這篇關(guān)于SpringBoot中利用AOP和攔截器實(shí)現(xiàn)自定義注解的文章就介紹到這了,更多相關(guān)SpringBoot自定義注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)簡(jiǎn)單的學(xué)生信息管理系統(tǒng)代碼實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)簡(jiǎn)單的學(xué)生信息管理系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04關(guān)于springboot中的自定義配置項(xiàng)
這篇文章主要介紹了關(guān)于springboot中的自定義配置項(xiàng),在項(xiàng)目開發(fā)的過程中,經(jīng)常需要自定義系統(tǒng)業(yè)務(wù)方面的配置文件及配置項(xiàng),Spring Boot提供了@value注解、@ConfigurationProperties注解和Environment接口等3種方式自定義配置項(xiàng),需要的朋友可以參考下2023-07-07spring security結(jié)合jwt實(shí)現(xiàn)用戶重復(fù)登錄處理
本文主要介紹了spring security結(jié)合jwt實(shí)現(xiàn)用戶重復(fù)登錄處理,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Java多線程 ReentrantReadWriteLock原理及實(shí)例詳解
這篇文章主要介紹了Java多線程 ReentrantReadWriteLock原理及實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java的Socket網(wǎng)絡(luò)編程基礎(chǔ)知識(shí)入門教程
這篇文章主要介紹了Java的Socket網(wǎng)絡(luò)編程基礎(chǔ)知識(shí)入門教程,包括基于TCP/IP和UDP協(xié)議的簡(jiǎn)單實(shí)例程序講解,需要的朋友可以參考下2016-01-01Redis結(jié)合AOP與自定義注解實(shí)現(xiàn)分布式緩存流程詳解
項(xiàng)目中如果查詢數(shù)據(jù)是直接到MySQL數(shù)據(jù)庫(kù)中查詢的話,會(huì)查磁盤走IO,效率會(huì)比較低,所以現(xiàn)在一般項(xiàng)目中都會(huì)使用緩存,目的就是提高查詢數(shù)據(jù)的速度,將數(shù)據(jù)存入緩存中,也就是內(nèi)存中,這樣查詢效率大大提高2022-11-11關(guān)于MyBatis plus條件構(gòu)造器的逐條詳解
什么是條件構(gòu)造器呢?簡(jiǎn)單來說,條件構(gòu)造器就是用來生成我們查數(shù)據(jù)庫(kù)的sql。它可以簡(jiǎn)化sql代碼的編寫,靈活、方便且易于維護(hù)2021-09-09