您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)Spring AOP如何實(shí)現(xiàn)簡(jiǎn)單的日志切面,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話(huà)不多說(shuō),跟著小編一起來(lái)看看吧。
AOP 的全稱(chēng)為 Aspect Oriented Programming,譯為面向切面編程,是通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)核心業(yè)務(wù)邏輯之外的橫切行為的統(tǒng)一維護(hù)的一種技術(shù)。AOP 是面向?qū)ο缶幊蹋∣OP)的補(bǔ)充和擴(kuò)展。 利用 AOP 可以對(duì)業(yè)務(wù)邏輯各部分進(jìn)行隔離,從而達(dá)到降低模塊之間的耦合度,并將那些影響多個(gè)類(lèi)的公共行為封裝到一個(gè)可重用模塊,從而到達(dá)提高程序的復(fù)用性,同時(shí)提高了開(kāi)發(fā)效率,提高了系統(tǒng)的可操作性和可維護(hù)性。
在實(shí)際的 Web 項(xiàng)目開(kāi)發(fā)中,我們常常需要對(duì)各個(gè)層面實(shí)現(xiàn)日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等功能。如果我們對(duì)每個(gè)層面的每個(gè)類(lèi)都獨(dú)立編寫(xiě)這部分代碼,那久而久之代碼將變得很難維護(hù),所以我們把這些功能從業(yè)務(wù)邏輯代碼中分離出來(lái),聚合在一起維護(hù),而且我們能靈活地選擇何處需要使用這些代碼。
名詞 | 概念 | 理解 |
---|---|---|
通知(Advice) | 攔截到連接點(diǎn)之后所要執(zhí)行的代碼,通知分為前置、后置、異常、最終、環(huán)繞通知五類(lèi) | 我們要實(shí)現(xiàn)的功能,如日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等,說(shuō)明什么時(shí)候要干什么 |
連接點(diǎn)(Joint Point) | 被攔截到的點(diǎn),如被攔截的方法、對(duì)類(lèi)成員的訪(fǎng)問(wèn)以及異常處理程序塊的執(zhí)行等等,自身還能嵌套其他的 Joint Point | Spring 允許你用通知的地方,方法有關(guān)的前前后后(包括拋出異常) |
切入點(diǎn)(Pointcut) | 對(duì)連接點(diǎn)進(jìn)行攔截的定義 | 指定通知到哪個(gè)方法,說(shuō)明在哪干 |
切面(Aspect) | 切面類(lèi)的定義,里面包含了切入點(diǎn)(Pointcut)和通知(Advice)的定義 | 切面就是通知和切入點(diǎn)的結(jié)合 |
目標(biāo)對(duì)象(Target Object) | 切入點(diǎn)選擇的對(duì)象,也就是需要被通知的對(duì)象;由于 Spring AOP 通過(guò)代理模式實(shí)現(xiàn),所以該對(duì)象永遠(yuǎn)是被代理對(duì)象 | 業(yè)務(wù)邏輯本身 |
織入(Weaving) | 把切面應(yīng)用到目標(biāo)對(duì)象從而創(chuàng)建出 AOP 代理對(duì)象的過(guò)程??椚肟梢栽诰幾g期、類(lèi)裝載期、運(yùn)行期進(jìn)行,而 Spring 采用在運(yùn)行期完成 | 切點(diǎn)定義了哪些連接點(diǎn)會(huì)得到通知 |
引入(Introduction ) | 可以在運(yùn)行期為類(lèi)動(dòng)態(tài)添加方法和字段,Spring 允許引入新的接口到所有目標(biāo)對(duì)象 | 引入就是在一個(gè)接口/類(lèi)的基礎(chǔ)上引入新的接口增強(qiáng)功能 |
AOP 代理(AOP Proxy ) | Spring AOP 可以使用 JDK 動(dòng)態(tài)代理或者 CGLIB 代理,前者基于接口,后者基于類(lèi) | 通過(guò)代理來(lái)對(duì)目標(biāo)對(duì)象應(yīng)用切面 |
AOP 是 Spring 框架中的一個(gè)核心內(nèi)容。在 Spring 中,AOP 代理可以用 JDK 動(dòng)態(tài)代理或者 CGLIB 代理 CglibAopProxy 實(shí)現(xiàn)。Spring 中 AOP 代理由 Spring 的 IOC 容器負(fù)責(zé)生成和管理,其依賴(lài)關(guān)系也由 IOC 容器負(fù)責(zé)管理。
注解 | 說(shuō)明 |
---|---|
@Aspect | 將一個(gè) java 類(lèi)定義為切面類(lèi) |
@Pointcut | 定義一個(gè)切入點(diǎn),可以是一個(gè)規(guī)則表達(dá)式,比如下例中某個(gè) package 下的所有函數(shù),也可以是一個(gè)注解等 |
@Before | 在切入點(diǎn)開(kāi)始處切入內(nèi)容 |
@After | 在切入點(diǎn)結(jié)尾處切入內(nèi)容 |
@AfterReturning | 在切入點(diǎn) return 內(nèi)容之后處理邏輯 |
@Around | 在切入點(diǎn)前后切入內(nèi)容,并自己控制何時(shí)執(zhí)行切入點(diǎn)自身的內(nèi)容 |
@AfterThrowing | 用來(lái)處理當(dāng)切入內(nèi)容部分拋出異常之后的處理邏輯 |
@Order(100) | AOP 切面執(zhí)行順序, @Before 數(shù)值越小越先執(zhí)行,@After 和 @AfterReturning 數(shù)值越大越先執(zhí)行 |
其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都屬于通知(Advice)。 |
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 熱部署模塊 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 這個(gè)需要為 true 熱部署才有效 --> </dependency> <!-- Spring AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies>
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ControllerWebLog { String name();//所調(diào)用接口的名稱(chēng) boolean intoDb() default false;//標(biāo)識(shí)該條操作日志是否需要持久化存儲(chǔ) }
@Aspect @Component @Order(100) public class WebLogAspect { private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class); private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>(); /** * 橫切點(diǎn) */ @Pointcut("execution(public * cn.zwqh.springboot.controller..*.*(..))") public void webLog() { } /** * 接收請(qǐng)求,并記錄數(shù)據(jù) * @param joinPoint * @param controllerWebLog */ @Before(value = "webLog()&& @annotation(controllerWebLog)") public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) { // 接收到請(qǐng)求 RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); // 記錄請(qǐng)求內(nèi)容,threadInfo存儲(chǔ)所有內(nèi)容 Map<String, Object> threadInfo = new HashMap<>(); logger.info("URL : " + request.getRequestURL()); threadInfo.put("url", request.getRequestURL()); logger.info("URI : " + request.getRequestURI()); threadInfo.put("uri", request.getRequestURI()); logger.info("HTTP_METHOD : " + request.getMethod()); threadInfo.put("httpMethod", request.getMethod()); logger.info("REMOTE_ADDR : " + request.getRemoteAddr()); threadInfo.put("ip", request.getRemoteAddr()); logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); threadInfo.put("classMethod", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); threadInfo.put("args", Arrays.toString(joinPoint.getArgs())); logger.info("USER_AGENT"+request.getHeader("User-Agent")); threadInfo.put("userAgent", request.getHeader("User-Agent")); logger.info("執(zhí)行方法:" + controllerWebLog.name()); threadInfo.put("methodName", controllerWebLog.name()); threadLocal.set(threadInfo); } /** * 執(zhí)行成功后處理 * @param controllerWebLog * @param ret * @throws Throwable */ @AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "ret") public void doAfterReturning(ControllerWebLog controllerWebLog, Object ret) throws Throwable { Map<String, Object> threadInfo = threadLocal.get(); threadInfo.put("result", ret); if (controllerWebLog.intoDb()) { //插入數(shù)據(jù)庫(kù)操作 //insertResult(threadInfo); } // 處理完請(qǐng)求,返回內(nèi)容 logger.info("RESPONSE : " + ret); } /** * 獲取執(zhí)行時(shí)間 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around(value = "webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object ob = proceedingJoinPoint.proceed(); Map<String, Object> threadInfo = threadLocal.get(); Long takeTime = System.currentTimeMillis() - startTime; threadInfo.put("takeTime", takeTime); logger.info("耗時(shí):" + takeTime); threadLocal.set(threadInfo); return ob; } /** * 異常處理 * @param throwable */ @AfterThrowing(value = "webLog()", throwing = "throwable") public void doAfterThrowing(Throwable throwable) { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); // 異常信息 logger.error("{}接口調(diào)用異常,異常信息{}", request.getRequestURI(), throwable); } }
@RestController @RequestMapping("/user") public class UserController { @GetMapping("/getOne") @ControllerWebLog(name = "查詢(xún)", intoDb = true) public String getOne(Long id, String name) { return "1234"; } }
瀏覽器請(qǐng)求:http://127.0.0.1:8080/user/getOne?id=1&name=zwqh ,可以看到后臺(tái)日志輸出:
日志記錄只是一個(gè)簡(jiǎn)單的示例,而 Spring AOP 的應(yīng)用讓整個(gè)系統(tǒng)變的更加有條不紊,在其他場(chǎng)景應(yīng)用也很強(qiáng)大。 它幫助我們降低模塊間耦合度,提高程序復(fù)用性,提高開(kāi)發(fā)效率,提高系統(tǒng)可做性和可維護(hù)性。
以上就是Spring AOP如何實(shí)現(xiàn)簡(jiǎn)單的日志切面,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。