思路概括
利用面向切面的代理模式(aop),将 凡是被 自定義注解 标注的方法/類 ,都被代理。在元方法中寫正常的業務邏輯,在代理類中利用反射 寫上注解的功能。
ps: 非spring項目 自定義注解功能 也可由代理來實作。
demo環境
springboot + aop ,使用業務行為記錄功能
aop依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
編碼
自定義注解
package li.ql.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author liql
* @date 2021/4/12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
//自定義注解 相應的功能 利用反應在代理類中書寫
String value() default "";
}
定義切面 +功能
以自定義的注解為切入點
package li.ql.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author liql
* @date 2021/9/11
*/
@Aspect //@Aspect注釋告訴Spring這是個切面類
@Component
@Slf4j
public class ActionAspect3 {
//以注解為切入點
@Pointcut("@annotation(li.ql.annotation.LogAnnotation)")
public void logPointCut(){}
@Around("logPointCut()")
public void beforeMehtod(ProceedingJoinPoint pj) throws Throwable {
log.info("自定義的切面3 上環繞通知");
log.info("在這裡利用反射可以書寫 注解的功能");
pj.proceed();
log.info("在這裡利用反射可以書寫 注解的功能");
log.info("自定義切面3 下環繞通知");
}
}
最後 在需要用到注解的類/方法上注上注解即可使用
自定義一個日志記錄功能注解
package li.ql.aspect;
import li.ql.annotation.LogAnnotation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @author liql
* @date 2021/4/12
*/
@Aspect //@Aspect注釋告訴Spring這是個切面類
@Component
@Slf4j
public class ActionAspect2 {
//@Pointcut 聲明切入點 這個切入點是個注解
//如果注解上沒有其它内容,隻要進入這個切面,及代理成功,就可以寫業務了
//如果注解上還有其它的内容 則需要通過反射 擷取主街上的内容
@Pointcut("@annotation(li.ql.annotation.LogAnnotation)")
public void logPointCut(){}
@Around(value = "logPointCut()") //在logPointCut()這個切入點方法執行前 需要做的事
public void beforeMethod(ProceedingJoinPoint pj) throws Throwable {//ProceedingJoinPoint pj 參數隻支援around 環繞通知
//環繞通知 ,如果不調用 pj.proceed(); 讓被代理的切入點繼續執行就會阻塞
log.info("第二個切面攔截注解 Aronud環繞通知第一次做的事");
//先擷取方法 才能檢查方法上有沒有攜帶注解
Signature signature = pj.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
System.out.println("methodSignature="+methodSignature.toString());
//檢查方法上是否有注解 先要擷取目标類,然後根據目标方法名和類型 擷取類的方法的反射 ,然後才能擷取注解,
// 省略擷取類方法的反射,則拿不到注解
// LogAnnotation annotation = methodSignature.getClass().getDeclaredAnnotation(LogAnnotation.class);// 這個不行
// LogAnnotation annotation = methodSignature.getClass().getDeclaredAnnotation(LogAnnotation.class);// 拿不到
Object targetClass = pj.getTarget();
System.out.println(methodSignature.getName()+","+methodSignature.getParameterTypes());
Method declaredMethod = targetClass.getClass().getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
//這下才能拿到注解
LogAnnotation annotation = declaredMethod.getAnnotation(LogAnnotation.class);
log.info("擷取的注解:{}",annotation);
//上面那麼多都是為了拿到這個value 踩寫的
String value = annotation.value();
// System.out.println("注解上的值:"+value);
log.info("aop獲得的注解上的值:{}",value);
pj.proceed(); //讓被攔截的方法繼續執行下去
log.info(" 第二個切面 Aronud環繞通知第二次做的事");
}
@After(value = "logPointCut()") //在logPointCut()這個切入點方法執行後 需要做的事
public void afterMethod(){
log.info("切入點執行後 做的事");
}
}