AOP切面編程
簡介
就是在一端邏輯執行前後 加入對應的邏輯。常見的用法如 用註解動態切換數據源,或者日誌記錄。
利用註解做日誌記錄的功能
- 寫一個用於註解的枚舉
/**
* @Auther: lots
* @Date: 2020/4/14 10:06
* @Description:
*/
public enum LogTypeEnum {
SAVE, //新增
SELECT,//查詢
DELETE,//刪除
UPDATE;//修改
}
- 先寫一個註解類log
import com.lots.inputrabbit.entity.LogTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Auther: lots
* @Date: 2020/4/13 14:33
* @Description:
*/
/** ElementType的取值
* 1.CONSTRUCTOR:用於描述構造器
* 2.FIELD:用於描述域
* 3.LOCAL_VARIABLE:用於描述局部變量
* 4.METHOD:用於描述方法
* 5.PACKAGE:用於描述包
* 6.PARAMETER:用於描述參數
* 7.TYPE:用於描述類、接口(包括註解類型) 或enum聲明
*/
@Target({ElementType.METHOD,ElementType.TYPE})
/**
* RetentionPolicy的取值
* 1、SOURCE:註解只保留在源文件,當Java文件編譯成class文件的時候,註解被遺棄;
* 2、CLASS:註解被保留到class文件,但jvm加載class文件時候被遺棄,這是默認的生命週期;
* 3、RUNTIME:註解不僅被保存到class文件中,jvm加載class文件之後,仍然存在;
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
//請求類型
LogTypeEnum logType() default LogTypeEnum.SELECT;
//接口名稱
String name() default "";
}
- Log切面的實現
import cn.hutool.json.JSONUtil;
import com.lots.inputrabbit.annotation.Log;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* @Auther: lots
* @Date: 2020/4/13 14:41
* @Description: Log切面類實現
*/
@Aspect //定義這是一個切面
@Order(100) //加載的順序 值越小,優先級越高
@Component //實例化到spring容器中
public class LogAspect {
/**
* 切入點 定義切入的範圍 下面的spring的規範
* 格式: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
* 修飾符匹配(modifier-pattern?)
* 返回值匹配(ret-type-pattern)可以爲*表示任何返回值,全路徑的類名等
* 類路徑匹配(declaring-type-pattern?)
* 方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, set* 代表以set開頭的所有方法
* 參數匹配((param-pattern))可以指定具體的參數類型,多個參數間用“,”隔開,各個參數也可以用“*”來表示匹配任意類型的參數,如(String)表示匹配一個String參數的方法;(*,String) 表示匹配有兩個參數的方法,第一個參數可以是任意類型,而第二個參數是String類型;可以用(..)表示零個或多個任意參數
* 異常類型匹配(throws-pattern?)
* 其中後面跟着“?”的是可選項
* <p>
* 例如:
* 1)execution(* *(..)) //表示匹配所有方法
* <p>
* 2)execution(public * com. savage.service.UserService.*(..))//表示匹配com.savage.server.UserService中所有的公有方法
* <p>
* 3)execution(* com.savage.server..*.*(..)) //表示匹配com.savage.server包及其子包下的所有方法
*
*
*
* @Pointcut("@annotation(com.lots.inputrabbit.annotation.Log)") 這種可以直接根據標籤切入
*/
// @Pointcut("execution( * com.lots.inputrabbit..*(..))")
@Pointcut("@annotation(com.lots.inputrabbit.annotation.Log)")
public void serviceLog() {
}
/**
* @Before是在所攔截方法執行之前執行一段邏輯。
* @After 是在所攔截方法執行之後執行一段邏輯。
* @Around是可以同時在所攔截方法的前後執行一段邏輯。 ProceedingJoinPoint point point.proceed()-調用方法
*/
@Around("serviceLog()")
public Object log(ProceedingJoinPoint point) throws Throwable {
try {
// 獲取方法簽名
MethodSignature signature = (MethodSignature) point.getSignature();
//java reflect相關類,通過反射得到註解
Method method = signature.getMethod();
HttpServletRequest request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
//獲取用戶請求方法的參數並序列化爲JSON格式字符串
String params = JSONUtil.parse(point.getArgs()).toStringPretty();
Class<?> targetClass = method.getDeclaringClass();
StringBuffer classAndMethod = new StringBuffer();
//獲取類註解Log
Log classAnnotation = targetClass.getAnnotation(Log.class);
//獲取方法註解Log
Log methodAnnotation = method.getAnnotation(Log.class);
//下面是各類參數的對應
System.out.println("---------- 請求路徑:" + request.getServletPath());
System.out.println("---------- 請求參數:" + params.toString());
System.out.println("---------- ip:" + request.getRemoteAddr());
System.out.println("---------- 調用方法:" + method.getName());
System.out.println("---------- 調用類:" + point.getSignature().getDeclaringTypeName());
System.out.println("---------- 調用類名:" + point.getSignature().getDeclaringType().getSimpleName());
System.out.println("---------- 註解定義的名稱:" + methodAnnotation.name());
System.out.println("---------- 註解定義的類型:" + methodAnnotation.logType());
Object proceed = point.proceed();//調用目標方法
System.out.println("---------- 返回值爲:"+proceed);
} catch (Exception e) {
}
return null;
}
}
- 測試 Controller與Service的表寫
import com.biz.primus.base.util.JSONResult;
import com.biz.primus.ms.base.api.BaseApiController;
import com.lots.inputrabbit.annotation.Log;
import com.lots.inputrabbit.entity.LogTypeEnum;
import com.lots.inputrabbit.service.TestLogService;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("soa/testLog/")
@Api("測試切面")
public class TestLogApiController extends BaseApiController {
@Resource
private TestLogService testLogService;
@GetMapping("la")
@Log(name = "測試接口la",logType = LogTypeEnum.SELECT)
public JSONResult la(@RequestParam("name") String name){
return testLogService.la(name);
}
@GetMapping("la2")
@Log(name = "測試接口la2",logType = LogTypeEnum.SELECT)
public JSONResult la2(@RequestParam("name") String name){
return testLogService.la2(name);
}
}
import com.biz.primus.base.util.JSONResult;
import org.springframework.stereotype.Service;
/**
* @Auther: lots
* @Date: 2020/4/13 17:39
* @Description:
*/
@Service
public class TestLogService {
public JSONResult la(String name){
return new JSONResult(name);
}
public JSONResult la2(String name){
return new JSONResult(name);
}
}
- 數據輸出結果
---------- 請求路徑:/soa/testLog/la
---------- 請求參數:[
“lots”
]
---------- ip:127.0.0.1
---------- 調用方法:la
---------- 調用類:com.lots.inputrabbit.api.TestLogApiController
---------- 調用類名:TestLogApiController
---------- 註解定義的名稱:測試接口la
---------- 註解定義的類型:SELECT
---------- 返回值爲:{code:0, msg:‘success’, data:lots, ts:1586933801930}
---------- 請求路徑:/soa/testLog/la2
---------- 請求參數:[
“lots”
]
---------- ip:127.0.0.1
---------- 調用方法:la2
---------- 調用類:com.lots.inputrabbit.api.TestLogApiController
---------- 調用類名:TestLogApiController
---------- 註解定義的名稱:測試接口la2
---------- 註解定義的類型:SELECT
---------- 返回值爲:{code:0, msg:‘success’, data:lots, ts:1586933841411}