SpringBoot中利用AOP和攔截器實現(xiàn)自定義注解
前言
最近遇到了這樣一個工作場景,需要寫一批dubbo接口,再將dubbo接口注冊到網(wǎng)關中,但是當dubbo接口異常的時候會給前端返回非常不友好的異常。所以就想要對異常進行統(tǒng)一捕獲處理,但是對于這種service接口使用@ExceptionHandler注解進行異常捕獲也是捕獲不到的,應為他不是Controller的接口。這時就想到了自定義一個注解去實現(xiàn)異常捕獲的功能。
Spring實現(xiàn)自定義注解
通過攔截器+AOP實現(xiàn)自定義注解的實現(xiàn),在這里攔截器充當在指定注解處要執(zhí)行的方法,aop負責將攔截器的方法和要注解生效的地方做一個織入(通過動態(tài)注解生成代理類實現(xiàn))。
1.引入相關依賴
spring-boot-starter:spring的一些核心基礎依賴
spring-boot-starter-aop:spring實現(xiàn)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項目中的攔截器(注:不是動態(tài)代理攔截器),
* 區(qū)別與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) {
//不同環(huán)境下的一個異常處理
System.out.println("發(fā)生異常了");
return null;
}
}
}
3.切點切面類
MethodInterceptor的實現(xiàn)類能作為切面的執(zhí)行方式是應為Interceptor的父類是Advice。
@Configuration
public class EasyExceptionAdvisor {
/**
* 放在最后執(zhí)行
* 等待ump/日志等記錄結束
*
* @return {@link DefaultPointcutAdvisor}對象
*/
@Bean
@Order(Integer.MIN_VALUE)
public DefaultPointcutAdvisor easyExceptionResultAdvisor() {
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
//針對EasyExceptionResult注解創(chuàng)建切點
AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(EasyExceptionResult.class, true);
EasyExceptionIntercepter interceptor = new EasyExceptionIntercepter();
advisor.setPointcut(annotationMatchingPointcut);
//在切點執(zhí)行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();
}
}
至此就實現(xiàn)了通過spring實現(xiàn)自定義注解。
Java實現(xiàn)自定義注解
雖然通過Spring實現(xiàn)了自定義注解但是還有辦法讓我們不通過Spring也能實現(xiàn)自定義注解,畢竟注解是早于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 | 應用于構造函數(shù) |
| ElementType.LOCAL_VARIABLE | 應用于局部變量 |
| ElementType.ANNOTATION_TYPE | 應用于注解類型 |
| ElementType.PACKAGE | 應用于包 |
| ElementType.TYPE_PARAMETER | 1.8版本新增,應用于類型變量) |
| ElementType.TYPE_USE | 1.8版本新增,應用于任何使用類型的語句中(例如聲明語句、泛型和強制轉換語句中的類型) |
@Retention
表明該注解的生命周期
| 生命周期類型 | 描述 |
|---|---|
| RetentionPolicy.SOURCE | 編譯時被丟棄,不包含在類文件中 |
| RetentionPolicy.CLASS | JVM加載時被丟棄,包含在類文件中,默認值 |
| RetentionPolicy.RUNTIME | 由JVM 加載,包含在類文件中,在運行時可以被獲取到 |
@Document
表明該注解標記的元素可以被Javadoc 或類似的工具文檔化
@Inherited
表明使用了@Inherited注解的注解,所標記的類的子類也會擁有這個注解
通過Cglib實現(xiàn)
在我們定義好注解之后就需要考慮如何將注解和類綁定到一起,在運行期間達到我們想要的效果,這里就可以引入動態(tài)代理的機制,將注解想要做的操作在方法執(zhí)行前,類編譯時就進行一個織入的操作如下。
public static void main(String[] args) {
Class easyServiceImplClass=EasyServiceImpl.class;
//判斷該對象是否有我們自定義的@EasyExceptionResult注解
if(easyServiceImplClass.isAnnotationPresent(EasyExceptionResult.class)){
final EasyServiceImpl easyService=new EasyServiceImpl();
//cglib的字節(jié)碼加強器
Enhancer enhancer=new Enhancer();
將目標對象所在的類作為Enhaner類的父類
enhancer.setSuperclass(EasyServiceImpl.class);
通過實現(xiàn)MethodInterceptor實現(xiàn)方法回調,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("發(fā)生異常了");
}
return proxy;
}
});
Object obj= enhancer.create();;
EasyServiceImpl easyServiceProxy=(EasyServiceImpl)obj;
easyServiceProxy.testEasyResult();
}
}
運行效果:

通過JDk動態(tài)代理實現(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 {
// 這里可以做增強
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動態(tài)代理的區(qū)別
java動態(tài)代理是利用反射機制生成一個實現(xiàn)代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。
而cglib動態(tài)代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節(jié)碼生成子類來處理。
1、如果目標對象實現(xiàn)了接口,默認情況下會采用JDK的動態(tài)代理實現(xiàn)AOP
2、如果目標對象實現(xiàn)了接口,可以強制使用CGLIB實現(xiàn)AOP
3、如果目標對象沒有實現(xiàn)了接口,必須采用CGLIB庫,spring會自動在JDK動態(tài)代理和CGLIB之間轉換
如何強制使用CGLIB實現(xiàn)AOP?
(1)添加CGLIB庫,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK動態(tài)代理和CGLIB字節(jié)碼生成的區(qū)別?
(1)JDK動態(tài)代理只能對實現(xiàn)了接口的類生成代理,而不能針對類
(2)CGLIB是針對類實現(xiàn)代理,主要是對指定的類生成一個子類,覆蓋其中的方法
因為是繼承,所以該類或方法最好不要聲明成final
寫在最后
@ExceptionHandler注解的使用可參考文章Java實現(xiàn)優(yōu)雅的參數(shù)校驗方法詳解
到此這篇關于SpringBoot中利用AOP和攔截器實現(xiàn)自定義注解的文章就介紹到這了,更多相關SpringBoot自定義注解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
IDEA MyBatis Plugins自動生成實體類和mapper.xml
這篇文章主要介紹了IDEA MyBatis Plugins自動生成實體類和mapper.xml,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07
詳解Java實現(xiàn)批量壓縮圖片裁剪壓縮多種尺寸縮略圖一鍵批量上傳圖片
這篇文章主要介紹了Java實現(xiàn)批量壓縮圖片裁剪壓縮多種尺寸縮略圖一鍵批量上傳圖片,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-03-03
教你1秒將本地SpringBoot項目jar包部署到Linux環(huán)境(超詳細!)
spring Boot簡化了Spring應用的開發(fā)過程,遵循約定優(yōu)先配置的原則提供了各類開箱即用(out-of-the-box)的框架配置,下面這篇文章主要給大家介紹了關于1秒將本地SpringBoot項目jar包部署到Linux環(huán)境的相關資料,超級詳細,需要的朋友可以參考下2023-04-04
Java String類詳解_動力節(jié)點Java學院整理
這篇文章主要介紹了Java String類詳解,本文經(jīng)多方資料的收集整理和歸納,最終撰寫成文,非常不錯,值得收藏,需要的的朋友參考下2017-04-04
詳解如何在spring boot中使用spring security防止CSRF攻擊
這篇文章主要介紹了詳解如何在spring boot中使用spring security防止CSRF攻擊,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
SpringBoot后端進行數(shù)據(jù)校驗JSR303的使用詳解
這篇文章主要介紹了SpringBoot后端進行數(shù)據(jù)校驗JSR303的使用詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03

