AOP稱爲面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,好比日誌,事務,權限等等。java
Spring AOP模塊提供截取攔截應用程序的攔截器,例如,當執行方法時,能夠在執行方法以前或以後添加額外的功能.web
(1)Aspect(切面):一般是一個類,裏面能夠定義切入點和通知spring
(2)JointPoint(鏈接點):程序執行過程當中明確的點,通常是方法的調用express
(3)Advice(通知):AOP在特定的切入點上執行的加強處理,有before,after,afterReturning,afterThrowing,aroundapache
(4)Pointcut(切入點):就是帶有通知的鏈接點,在程序中主要體現爲書寫切入點表達式編程
(5)AOP代理:AOP框架建立的對象,代理就是目標對象的增強。Spring中的AOP代理可使JDK動態代理,也能夠是CGLIB代理,前者基於接口,後者基於子類json
@JoinPoint數組
切入點(PointCut
)是一組一個或多個鏈接點,在其中應該執行的通知。 您可使用表達式或模式指定切入點,咱們將在AOP示例中看到。 在Spring中切入點有助於使用特定的鏈接點來應用通知。請考慮如下示例:app
@Pointcut("execution(* com.demo.controller.*.*(..))")
@Pointcut("execution(* com.demo.StudentServiceImpl.getName(..))")
語法框架
@Aspectpublic class Logging {
//切全部的controller包下表全部方法
@Pointcut("execution(* com.demo.controller.*.*(..))")
private void controllerPoint(){}
@Aspect - 將類標記爲包含通知方法的類。
@Pointcut - 將函數標記爲切入點
execution( expression ) - 涵蓋應用通知的方法的表達式。
@Before Advice
@Before
是一種通知類型(前置),能夠確保在方法執行以前運行通知。 如下是@Before
通知(advice
)的語法:
@Pointcut("execution(* com.demo.controller.*.*(..))")
private void controllerPoint(){}
@Before("controllerPoint()")
public void beforeAdvice(){
System.out.println("在方法執行以前輸出");
}
@After Advice
@After是一種通知類型(後置),可確保在方法執行後運行通知。 如下是@After通知類的語法:
@Pointcut("execution(* com.demo.controller.*.*(..))")p
rivate void controllerPoint(){}
@After("controllerPoint()")
public void afterAdvice(){
System.out.println("方法執行後輸出。");
}
@After Returning Advice
@AfterReturning是一種通知類型,可確保方法執行成功後運行通知。 如下是@AfterReturning通知的語法:
@AfterReturning(pointcut="execution(* com.demo.controller.*.*(..))", returning="retVal")
public void afterReturningAdvice(JoinPoint jp, Object retVal){
System.out.println("Method Signature: " + jp.getSignature());
System.out.println("Returning:" + retVal.toString() );
}
- 要返回的變量的名稱。ps:returning
@AfterThrowing
@AfterThrowing是一種通知類型,能夠確保在方法拋出異常時運行一個通知
@AfterThrowing(pointcut="execution(* com.demo.controller.*.*(..))", throwing= "error")
public void afterThrowingAdvice(JoinPoint jp, Throwable error){
System.out.println("Method Signature: " + jp.getSignature());
System.out.println("Exception: "+error);
}
ps: - 返回的異常名稱throwing
@Around
@Around是一種環繞通知,經過環繞通知,咱們能夠在一個方法內完成前置、後置、異常(@AfterThrowing)等通知所實現的功能。
@Around("controllerPoint()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("方法開始");
//執行方法
Object result=jp.proceed(args);
System.out.println("方法結束");
return result.toString();
}
package com.example.dubbo.demo.service.aop; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.Modifier; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; import javassist.bytecode.MethodInfo; @Component @Aspect public class LogAspect { private final Logger logger = LoggerFactory.getLogger(LogAspect.class); //定義切點 @Pointcut("execution(public * com.example.dubbo.demo.service.impl..*.*(..))") public void serviceLog(){} @Around("serviceLog()") public Object logBefore(ProceedingJoinPoint pj) throws Throwable{ //接口請求的開始時間 Long startTimeMillis = System.currentTimeMillis(); JSONObject paramJson = this.printMethodParams(pj,String.valueOf(startTimeMillis)); logger.info("請求前:{}",paramJson.toString()); Object retVal = pj.proceed(); JSONObject returnJson = new JSONObject(); returnJson.put("class_name",paramJson.get("class_name")); returnJson.put("method_name",paramJson.get("method_name")); returnJson.put("class_name_method",paramJson.get("class_name_method")); returnJson.put("return_name",retVal); Long endTimeMillis = System.currentTimeMillis(); returnJson.put("endTimeMillis",endTimeMillis); returnJson.put("times",endTimeMillis - startTimeMillis); logger.info("請求後:"+returnJson.toString()); return retVal; } @AfterThrowing(pointcut = "serviceLog()", throwing = "e")//切點在webpointCut() public void handleThrowing(JoinPoint joinPoint, Exception e) throws IOException { Long startTimeMillis = System.currentTimeMillis(); JSONObject paramJson = this.printMethodParams(joinPoint,String.valueOf(startTimeMillis)); //獲取錯誤詳細信息 StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); paramJson.put("errorMsg", e.getMessage()); paramJson.put("StackTrace", sw.toString()); logger.info("請求錯誤:"+paramJson.toString()); pw.flush(); sw.flush(); } /** * 打印類method的名稱以及參數 * @param point 切面 */ public JSONObject printMethodParams(JoinPoint point,String startTimeMillis){ if(point == null){ return new JSONObject(); } /** * Signature 包含了方法名、申明類型以及地址等信息 */ String class_name = point.getTarget().getClass().getName(); String method_name = point.getSignature().getName(); logger.info("class_name = {},startTimeMillis:"+startTimeMillis,class_name); logger.info("method_name = {},startTimeMillis:"+startTimeMillis,method_name); JSONObject paramJson = new JSONObject(); paramJson.put("class_name",class_name); paramJson.put("method_name",method_name); paramJson.put("startTimeMillis",startTimeMillis); paramJson.put("class_name_method", String.format("%s.%s", class_name,method_name)); /** * 獲取方法的參數值數組。 */ Object[] method_args = point.getArgs(); try { //獲取方法參數名稱 String[] paramNames = getFieldsName(class_name, method_name); //打印方法的參數名和參數值 String param_name = logParam(paramNames,method_args); paramJson.put("param_name",JSONObject.parse(param_name)); } catch (Exception e) { e.printStackTrace(); } return paramJson; } /** * 使用javassist來獲取方法參數名稱 * @param class_name 類名 * @param method_name 方法名 * @return * @throws Exception */ private String[] getFieldsName(String class_name, String method_name) throws Exception { Class<?> clazz = Class.forName(class_name); String clazz_name = clazz.getName(); ClassPool pool = ClassPool.getDefault(); ClassClassPath classPath = new ClassClassPath(clazz); pool.insertClassPath(classPath); CtClass ctClass = pool.get(clazz_name); CtMethod ctMethod = ctClass.getDeclaredMethod(method_name); MethodInfo methodInfo = ctMethod.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); if(attr == null){ return null; } String[] paramsArgsName = new String[ctMethod.getParameterTypes().length]; // 若是是靜態方法,則第一就是參數 // 若是不是靜態方法,則第一個是"this",而後纔是方法的參數 // 我接口中沒有寫public修飾詞,致使個人數組少一位參數,因此再日後一位,本來應該是 XX ? 0 : 1 int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1; for (int i=0;i<paramsArgsName.length;i++){ paramsArgsName[i] = attr.variableName(i+pos); } return paramsArgsName; } /** * 判斷是否爲基本類型:包括String * @param clazz clazz * @return true:是; false:不是 */ private boolean isPrimite(Class<?> clazz){ if (clazz.isPrimitive() || clazz == String.class){ return true; }else { return false; } } /** * 打印方法參數值 基本類型直接打印,非基本類型須要重寫toString方法 * @param paramsArgsName 方法參數名數組 * @param paramsArgsValue 方法參數值數組 */ private String logParam(String[] paramsArgsName,Object[] paramsArgsValue){ StringBuffer buffer = new StringBuffer(); if(ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)){ buffer.append("{\"noargs\":\"該方法沒有參數\"}"); return buffer.toString(); } for (int i=0;i<paramsArgsName.length;i++){ //參數名 String name = paramsArgsName[i]; //參數值 Object value = paramsArgsValue[i]; buffer.append("\""+name+"\":"); if(isPrimite(value.getClass())){ buffer.append("\""+value+"\","); }else { buffer.append(JSON.toJSONString(value)+","); } } return "{"+buffer.toString().substring(0,buffer.toString().length()-1)+"}"; } }
最後須要再 application.properties文件中添加開啓aop的配資
spring.aop.auto=true