spring的AOP瞭解以及應用
一、spring AOP的應用場景
1、spring AOP s是什麼? (what)
AOP 面向切面程式設計,其通過預編譯方式和執行期動態代理實現在不修改原始碼的情況下給程式動態統一新增功能的一種技術。其核心使用了java的動態代理來實現的,一定程度上做到了業務和系統級別服務的解耦
2、spring AOP 可以用來做什麼 ()
日誌記錄,效能統計,安全控制,事務處理,異常處理等通用服務 可以通過AOP橫切進入業務邏輯,這樣做在實際的開發中將功能性業務和一些非功能的通用服務分離,解耦,需求變更的時候,如果是通用服務邏輯變更,只需要開發日誌等通用服務模組的開發人員進行更改(通用服務程式碼不會和業務程式碼耦合在一起),業務上的需求變更只需更改業務邏輯即可。
二、spring AOP的相關術語
1、 AOP中的相關術語
- Aspect,切面,一個關注點的模組。包含有具體實現的切面類。
- JoinPoint, 連線點,程式執行中的某個點,某個位置。
- PointCut,切點,切面匹配連線點的點,一般與切點表示式相關,就是切面如何切點。 例子中,@PointCut註解就是切點表示式,匹配對應的連線點後連線點即變為了切點。
- Advice,通知,指在切面的某個特定的連線點上執行的動作。 例子中,before()與after()方法中的程式碼。
- TargetObject,目標物件,指被切入的物件。 例子中,從ctx中取出的testBean則是目標物件。
- Weave,織入,將Advice作用在JoinPoint的過程。
2、spring AOP的五種通知
- @Before,前置通知,執行方法前執行
- @AfterReturn,返回通知,正常返回方法後執行
- @After,後置通知,方法最終結束後執行,相當於finaly
- @Around,環繞通知,圍繞整個方法
- @AfterThrowing,異常通知,丟擲異常後執行
三、spring AOP的相關demo
使用兩種形式實現Spring的AOP 第一個是使用切入點表示式,第二種使用註解的形式,個人喜歡使用註解的形式因為註解靈活且便於理解
1、使用切入點表示式來實現
1.1、切面類aspect
/**
* 切面列印服務層日誌資訊
*/
@Aspect
@Component
public class LogInfoAspect {
private static final Logger logger = LoggerFactory.getLogger(LogInfoAspect.class);
//定義一個切入點表示式
@Pointcut("execution(* com.xiu.sb.aopTest.service.*.*(..))")
public void logInfo() {
}
//在進入切點業務程式碼未執行之前執行
@Before("logInfo()")
public void logBefore(JoinPoint pjp){
MethodSignature methodSignature = ( MethodSignature) pjp.getSignature();
String methodName = methodSignature.getMethod().getName();
String className = pjp.getTarget().getClass().getName();
Object[] argsInfo = pjp.getArgs();
logger.info("日誌列印開始 class info : {}, method info : {}, args info: {}",className,methodName, JsonUtil.obj2str(argsInfo));
}
//在執行完切點後執行
@After("logInfo()")
public void logAfter(JoinPoint pjp){
MethodSignature methodSignature = ( MethodSignature) pjp.getSignature();
String methodName = methodSignature.getMethod().getName();
String className = pjp.getTarget().getClass().getName();
Object[] argsInfo = pjp.getArgs();
logger.info("日誌列印結束 class info : {}, method info : {}, args info: {}",className,methodName, JsonUtil.obj2str(argsInfo));
}
}
切入點表示式為com.xiu.sb.aopTest.service 該包下的所有類的所有方法都會被織入日誌列印的功能增強,無法靈活可控的進行處理。
2、使用註解來實現
2.1、切面類的程式碼和上面的程式碼比較相似
/**
* 切面列印服務層日誌資訊 使用註解的形式
*/
@Aspect
@Component
public class LogInfoAnnoAspect {
private static final Logger logger = LoggerFactory.getLogger(LogInfoAnnoAspect.class);
// 後期 切入點 可以為指定的註解(比如時間花費註解,日誌註解等)
//需要實現一個相關的註解
@Pointcut("@annotation(com.xiu.sb.aopTest.annonation.LogInfoAnnotation)")
public void logAnonation() {}
@Before("logAnonation()")
public void logBefore(JoinPoint pjp){
MethodSignature methodSignature = ( MethodSignature) pjp.getSignature();
String methodName = methodSignature.getMethod().getName();
String className = pjp.getTarget().getClass().getName();
Object[] argsInfo = pjp.getArgs();
logger.info("日誌列印開始 class info : {}, method info : {}, args info: {}",className,methodName, JsonUtil.obj2str(argsInfo));
}
@After("logAnonation()")
public void logAfter(JoinPoint pjp){
MethodSignature methodSignature = ( MethodSignature) pjp.getSignature();
String methodName = methodSignature.getMethod().getName();
String className = pjp.getTarget().getClass().getName();
Object[] argsInfo = pjp.getArgs();
logger.info("日誌列印結束 class info : {}, method info : {}, args info: {}",className,methodName, JsonUtil.obj2str(argsInfo));
}
}
2.2、註解形式實現的註解:定義一個列印日誌的註解 LogInfoAnnotation類
/**
* author Administrator
* date 2018/9/17
*/
@RestController
//修飾類,則類中的所有方法均會織入列印日誌的功能
@LogInfoAnnotation
public class StudentService extends BaseController{
@Autowired
private StudentService sudentService;
@RequestMapping("/demo")
//不放置到類上僅僅放置到方法上,只有當前被註解修飾的方法會織入日誌列印功能
@LogInfoAnnotation
public ResponseDto<Student> studentList(){
List<Student> students = new ArrayList<>();
for(int i =0;i <5; i++){
Student student = new Student("student"+i,"man",24);
students.add(student);
}
Map<String,Object> data = new HashMap();
data.put("studentList",students);
return success(data);
}
@RequestMapping("/logInfo")
public ResponseDto<Student> studentAll(){
List<Student> students = sudentService.getStuList();
Map<String,Object> data = new HashMap();
data.put("studentList",students);
return success(data);
}
@RequestMapping("/logInfo2")
public ResponseDto<Student> findAloneStudent(){
Student student = sudentService.findStudent(0);
Map<String,Object> data = new HashMap();
data.put("studentList",student);
return success(data);
}
}
/**
* author xieqx
* createTime 2018/7/20
* desc 記錄花費時間註解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogInfoAnnotation {
String value() default "";
}
2.3、註解形式的使用
註解之所以比切入點表示式靈活的原因在於,例子中的切入點表示式會對指定包下的所有方法起作用,如果需要精確配置,需要設定特定的切入點表示式來進行精確匹配,不夠靈活,使用註解則不然,我們可以將註解放置到我們需要起作用的方法上,如果需要在類的所有方法上起作用,只需使用註解來修飾類,例子如下所示:
@Service
public class StudentService {
private static List<Student> students = new ArrayList<>();
static {
students.add(new Student("xieqx","man",24));
students.add(new Student("xieqx2","man",25));
students.add(new Student("xieqx3","man",26));
}
//只有當前被註解修飾的方法會織入日誌列印功能
@LogInfoAnnotation
public List<Student> getStuList(){
return students;
}
public Student findStudent(Integer index){
return students.get(index);
}
}
四、spring的5種通知
1、前面的例子中已經提到了兩種通知,一種是前置通知,另一種是後置通知,所謂前置通知是指在執行切入點方法之前被執行所謂後置通知是執行切入點方法之後被執行,這兩種通知都比較簡單,此處不造敘述。這裡主要著重指出後三種通知
@Around 環繞通知,@AfterReturn,返回通知,@AfterThrow 異常通知
2、@Around 使用環繞通知實現一個輸出每一個介面的執行時間
首先定義切面類(這裡直接使用註解的形式)
/**
* 使用切面類來檢視controller介面的執行時間
*/
@Aspect
//@Order(4)
@Component
public class TimeCostAspect {
private static final Logger logger = LoggerFactory.getLogger(TimeCostAspect.class);
//後期 切入點 可以為指定的註解(比如時間花費註解,日誌註解等)
@Pointcut("@annotation(com.xiu.sb.aopTest.annonation.TimeCostAnnotation)")
public void timecostAnonation() {
}
@Around("timecostAnonation()")
public Object aroundAnnotation(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
Object o;
try {
//執行當前切入點方法
o = pjp.proceed(pjp.getArgs());
} catch (Throwable var7) {
String message = var7.getClass().getName() + " / " + var7.getMessage() + " / " + var7.getStackTrace()[0];
logger.error("error", message);
throw var7;
}
//獲取當前執行方法名稱
Signature s = pjp.getSignature();
MethodSignature ms = (MethodSignature)s;
String methodName = ms.getMethod().getName();
logger.info("time cost " + (System.currentTimeMillis() - startTime) + " ms path is " + pjp.getTarget().getClass().getName() +"."+methodName);
//logger.info("[time cost] " + (System.currentTimeMillis() - startTime) + " ms [path is " +ms.toString()+ "]");
return o;
}
}
2、@AfterReturning 返回通知 會獲取到切入點的返回值在該方法中,可以對其返回值進行處理
該註解提供了一個returning 變數,這裡用來宣告切入點方法的返回值的形參名稱,必須和方法中形參名稱保持一致,即returning = "result", 則該返回通知的方法中對應的返回值引數名稱也應該為result。
@Aspect
@Component
public class AfterReturnAspect {
private static final Logger logger = LoggerFactory.getLogger(AfterReturnAspect.class);
//後期 切入點 可以為指定的註解(比如時間花費註解,日誌註解等)
@Pointcut("@annotation(com.xiu.sb.aopTest.annonation.AfterReturnAnnotation)")
public void afterReturn() {
}
@AfterReturning(value = "afterReturn()",returning = "result")
public void aroundAnnotation(Object result) throws Throwable {
logger.info("方法的返回值:{}", JsonUtil.obj2str(result) );
}
}
3、 @AfterThrowing 返回異常 用來處理對應的方法丟擲異常
該註解提供了一個throwing 引數 用來宣告捕獲的異常變數名稱,也必須保持註解中宣告的名稱和通知方法中宣告的變數名稱一致。
@Aspect
@Component
public class AfterThrowAspect {
private static final Logger logger = LoggerFactory.getLogger(AfterThrowAspect.class);
//後期 切入點 可以為指定的註解(比如時間花費註解,日誌註解等)
@Pointcut("@annotation(com.xiu.sb.aopTest.annonation.AfterThrowAnnotation)")
public void afterThrow() {
}
//該方法即為返回異常通知
@AfterThrowing(value = "afterThrow()",throwing = "ex")
public void aroundAnnotation(NullPointerException ex) throws Throwable {
logger.info("列印日誌資訊:");
ex.printStackTrace();
}
}
到此為止,有關spring AOP 的基本只是以及相關程式碼應用就介紹到這裡,相關aop的原理 動態代理的內容會以後奉上。