淺析Java自定義註解aop切面的使用介紹
Java自定義註解的簡單介紹就不說了,這裡主要說一下自定義註解 aop 切面的使用。
一、什麼是AOP?
1、AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計。
AOP是一種程式設計正規化,隸屬於軟工範疇,指導開發者如何組織程式結構。AOP最早由AOP聯盟的組織提出的,制定了一套規範。Spring將AOP思想引入到框架中,必須遵守AOP聯盟的規範,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。
AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。
利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率
2、AOP的作用及優勢是什麼?
(1)作用:
AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼(效能監視、事務管理、安全檢查、快取)
在程式執行期間,不修改原始碼對已有方法進行增強
將業務邏輯和系統處理的程式碼(關閉連線、事務管理、操作日誌記錄)解耦
(2)優勢:減少重複程式碼、提高開發效率、維護方便
3、AOP相關術語介紹:
Joinpoint(連線點) -- 所謂連線點是指那些被攔截到的點。在spring中這些點指的是方法,因為spring只支援方法型別的連線點
Pointcut(切入點) -- 所謂切入點是指我們要對哪些Joinpoint進行攔截的定義
Advice(通知/增強) -- 所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。通知分為前置通知、後置通知、異常通知、最終通知、環繞通知(切面要完成的功能)
Introduction(引介) -- 引介是一種特殊的通知在不修改類程式碼的前提下, Introduction可以在執行期為類動態地新增一些方法或Field
Target(目標物件) -- 代理的目標物件
Weaving(織入) -- 是指把增強應用到目標物件來建立新的代理物件的過程
Proxy(代理) -- 一個類被AOP織入增強後,就產生一個結果代理類
Aspect(切面) -- 是切入點和通知的結合,以後咱們自己來編寫和配置的
以上內容摘自Spring AOP文件!
二、編寫自定義註解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public @interface AnnoationCeshi {
public String ceshiValue() default "";
}
三、編寫切面
1、編寫切面
@Component @Aspect public class AnnoationCeshiFine { // @annotation 表示 使用該註解的方法 @Around(value = "@annotation(AnnoationCeshi)") public Object aroundMethod(ProceedingJoinPoint jp) throws Throwable { // String module = getModule(jp); // 邏輯程式碼 // System.out.printf("**********" + module); return jp.proceed(); }
}
@Component 等價於 @Controller @Service ... spring 掃描並被 spring 容器管理
@Aspect 作用是把當前類標識為一個切面供容器讀取
AnnoationCeshi 為自己定義的註解
只要加了 @AnnocationCeshi 註解的 方法 都會執行 上述程式碼
2、定義切面
// 定義切點
// @poincut execution 表示式
//exxcution( * com.xy.xywx.controller.CeshiController.*(..)) CeshiController 類中所有方法 public 可省略
@Pointcut("execution(public * com.xy.xywx.controller..*.*(..))")
public void poincut(){}
@poincut 定義切面,裡面為 excution 表示式,其中:
(1)excution 表示式中 public 可省略不寫
(2)第一個 * 表示 controller 包中的所有類
(3)第二個 * 表示 類中的所有方法
(4).. 表示 不限引數
@Before(value = "poincut()") //通知前增強
@AfterReturning(value = "poincut()") // 通知後 增強
@AfterThrowing(value = "poincut()") // 異常 增強
@After(value = "poincut()") // final 增強
@Around(value = "poincut()")
3、具體使用
@Around(value = "poincut()")
public Object aroundMethod(ProceedingJoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
Arrays.stream(args).forEach(x -> System.out.println(x));
return jp.proceed();
}
return jp.proceed() 重點
四、具體介紹
@Component //加入到spring容器
@Aspect //切面
public class AspectDemo {
// 方法用途(切入點表示式可以用&&,||,!來組合使用)
@Pointcut("@annotation(com.example.demo.annotation.AnnotationDemo)")
public void asAnnotation() {}
// 方法用途:在AnnotationDemo註解之前執行,標識一個前置增強方法,相當於BeforeAdvice的功能
@Before("asAnnotation()")
public void beforeRun() {
System.out.println("在AnnotationDemo註解之前執行");
}
/* 方法用途:
* @Around 環繞增強,相當於MethodInterceptor,對帶@AnnotationDemo註解的方法進行切面,並獲取到註解的屬性值
* ProceedingJoinPoint: 環繞通知
*/
@Around("asAnnotation() && @annotation(annotationDemo)")
public Object around(ProceedingJoinPoint joinPoint,AnnotationDemo annotationDemo) {
Object obj = null;
try {
// AnnotationDemo註解的屬性值
annotationDemo.remark();
Object[] args = joinPoint.getArgs();
String arg = JSON.toJSONString(args);
System.out.println("請求引數:"+ arg);
Signature signature = joinPoint.getSignature();
// 方法package路徑
String methodUrl = signature.getDeclaringTypeName();
// 方法名,不包含package路徑
String method = signature.getName();
System.out.println("正在執行" + method + "方法,路徑:" + methodUrl);
// obj是返回的結果,joinPoint.proceed用於啟動目標方法執行,非常重要
obj = joinPoint.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("切面執行");
return obj;
}
/**
* 方法用途: 在AnnotationDemo註解之後執行
* final增強,不管是丟擲異常或者正常退出都會執行。
* @AfterReturning: 後置增強,似於AfterReturningAdvice, 方法正常退出時執行
* @AfterThrowing: 異常丟擲增強,相當於ThrowsAdvice
*/
@AfterReturning(returning = "obj", Pointcut = "asAnnotation()")
public void after(Object obj) {
System.out.println("在AnnotationDemo註解之後執行");
logger.info("執行成功,Result:{}", JSON.toJsonString(obj));
}
}
五、具體應用
1、比如我們限制下重複點選
(1)先定義註解
(2)再寫註解的 AOP
@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {
// @Autowired 可引入一些類
@Around("execution(* com.enmox.emcs.*.controller.*Controller.*(..)) && @annotation(noRepeatSubmit)")
public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
try {
// 業務處理:通過註解的 time()拿到預設設定的值,然後進行一些業務處理即可
int time = noRepeatSubmit.time()
return pjp.proceed();
} catch (EmcsCustomException e) {
throw new EmcsCustomException(e.getMessage());
} catch (Throwable e) {
log.error("校驗表單重複提交時異常: {}", LogUtil.getStack(e));
throw new EmcsCustomException("校驗表單重複提交時異常");
}
}
}
2、比如我們記錄操作日誌
/**
* 記錄操作日誌
*/
@Aspect
@Component
@Slf4j
public class RecordOperLogAspect {
@Pointcut("@annotation(com.baj.iam.authn.aop.UserOperLog)")
public void userLogpointcut() {}
@Pointcut("@annotation(com.baj.iam.authn.aop.AdminOperLog)")
public void adminLogpointcut() {}
@Pointcut("@annotation(com.baj.iam.authn.aop.AppOperLog)")
public void appLogpointcut() {}
/**
* 使用者日誌
* @param userOperLog
*/
@After("userLogpointcut() && @annotation(userOperLog)")
public void recordUserOperLog(UserOperLog userOperLog) {
// 業務處理
}
@Before("userLogpointcut() && @annotation(userOperLog)")
public void recordUpdatePasswordLog(JoinPoint point, UserOperLog userOperLog) {
try {
// 單獨的before切面記錄修改密碼,因為after拿不到使用者資訊
} catch (Throwable e) {
log.error("記錄操作日誌異常: {}", e.getMessage(), e);
}
}
/**
* 應用操作日誌
* @param appOperLog
*/
@Around("appLogpointcut() && @annotation(appOperLog)")
public Object recordAppOperLog(ProceedingJoinPoint point, AppOperLog appOperLog) throws Throwable {
// 業務處理
}
}