在開(kāi)發(fā)項(xiàng)目中,我們經(jīng)常會(huì)需要打印日志,這樣方便開(kāi)發(fā)人員了解接口調(diào)用情況及定位錯(cuò)誤問(wèn)題,很多時(shí)候?qū)τ贑ontroller或者是Service的入?yún)?/b>和出參需要打印日志,但是我們又不想重復(fù)的在每個(gè)方法里去使用logger打印,這個(gè)時(shí)候希望有一個(gè)管理者統(tǒng)一來(lái)打印,這時(shí)Spring AOP就派上用場(chǎng)了,利用切面的思想,我們?cè)谶M(jìn)入、出入Controller或Service時(shí)給它切一刀實(shí)現(xiàn)統(tǒng)一日志打印。
SpringAOP不僅可以實(shí)現(xiàn)在不產(chǎn)生新類的情況下打印日志,還可以管理事務(wù)、緩存等。具體可以了解官方文檔。https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-api
基礎(chǔ)概念在使用SpringAOP,這里還是先簡(jiǎn)單講解一些基本的知識(shí)吧,如果說(shuō)的不對(duì)請(qǐng)及時(shí)指正,這里主要是根據(jù)官方文檔來(lái)總結(jié)的。本章內(nèi)容主要涉及的知識(shí)點(diǎn)。
Pointcut: 切入點(diǎn),這里用于定義規(guī)則,進(jìn)行方法的切入(形象的比喻就是一把刀)。
JoinPoint: 連接點(diǎn),用于連接定義的切面。
Before: 在之前,在切入點(diǎn)方法執(zhí)行之前。
AfterReturning: 在切入點(diǎn)方法結(jié)束并返回時(shí)執(zhí)行。
這里除了SpringAOP相關(guān)的知識(shí),還涉及到了線程相關(guān)的知識(shí)點(diǎn),因?yàn)槲覀冃枰紤]多線程中它們各自需要保存自己的變量,所以就用到了ThreadLocal。
依賴引入這里主要是用到aop和mongodb,在pom.xml文件中加入以下依賴即可:
相關(guān)實(shí)體類org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-data-mongodb
/** * 請(qǐng)求日志實(shí)體,用于保存請(qǐng)求日志 */ @Document class WebLog { var id: String = "" var request: String? = null var response: String? = null var time: Long? = null var requestUrl: String? = null var requestIp: String? = null var startTime: Long? = null var endTime: Long? = null var method: String? = null override fun toString(): String { return ObjectMapper().writeValueAsString(this) } } /** * 業(yè)務(wù)對(duì)象,上一章講JPA中有定義 */ @Document class Student { @Id var id :String? = null var name :String? = null var age :Int? = 0 var gender :String? = null var sclass :String ?= null override fun toString(): String { return ObjectMapper().writeValueAsString(this) } }定義切面 定義切入點(diǎn)
/** * 定義一個(gè)切入,只要是為io.intodream..web下public修飾的方法都要切入 */ @Pointcut(value = "execution(public * io.intodream..web.*.*(..))") fun webLog() {}
定義切入點(diǎn)的表達(dá)式還可以使用within、如:
/** * 表示在io.intodream.web包下的方法都會(huì)被切入 */ @Pointcut(value = "within(io.intodream.web..*")定義一個(gè)連接點(diǎn)
/** * 切面的連接點(diǎn),并聲明在該連接點(diǎn)進(jìn)入之前需要做的一些事情 */ @Before(value = "webLog()") @Throws(Throwable::class) fun doBefore(joinPoint: JoinPoint) { val webLog = WebLog() webLog.startTime = System.currentTimeMillis() val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes? val request = attributes!!.request val args = joinPoint.args val paramNames = (joinPoint.signature as CodeSignature).parameterNames val params = HashMap方法結(jié)束后執(zhí)行(args.size) for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } webLog.id = UUID.randomUUID().toString() webLog.request = params.toString() webLog.requestUrl = request.requestURI.toString() webLog.requestIp = request.remoteAddr webLog.method = request.method webRequestLog.set(webLog) logger.info("REQUEST={} {}; SOURCE IP={}; ARGS={}", request.method, request.requestURL.toString(), request.remoteAddr, params) }
@AfterReturning(returning = "ret", pointcut = "webLog()") @Throws(Throwable::class) fun doAfterReturning(ret: Any) { val webLog = webRequestLog.get() webLog.response = ret.toString() webLog.endTime = System.currentTimeMillis() webLog.time = webLog.endTime!! - webLog.startTime!! logger.info("RESPONSE={}; SPEND TIME={}MS", ObjectMapper().writeValueAsString(ret), webLog.time) logger.info("webLog:{}", webLog) webLogRepository.save(webLog) webRequestLog.remove() }
這里的主要思路是,在方法執(zhí)行前,先記錄詳情的請(qǐng)求參數(shù),請(qǐng)求方法,請(qǐng)求ip, 請(qǐng)求方式及進(jìn)入時(shí)間,然后將對(duì)象放入到ThreadLocal中,在方法結(jié)束后并取到對(duì)應(yīng)的返回對(duì)象且計(jì)算出請(qǐng)求耗時(shí),然后將請(qǐng)求日志保存到mongodb中。
完成的代碼
package io.intodream.kotlin07.aspect import com.fasterxml.jackson.databind.ObjectMapper import io.intodream.kotlin07.dao.WebLogRepository import io.intodream.kotlin07.entity.WebLog import org.aspectj.lang.JoinPoint import org.aspectj.lang.annotation.AfterReturning import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Before import org.aspectj.lang.annotation.Pointcut import org.aspectj.lang.reflect.CodeSignature import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.annotation.Order import org.springframework.stereotype.Component import org.springframework.validation.BindingResult import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.context.request.ServletRequestAttributes import java.util.* /** * {描述} * * @author yangxianxi@gogpay.cn * @date 2019/4/10 19:06 * */ @Aspect @Order(5) @Component class WebLogAspect { private val logger:Logger = LoggerFactory.getLogger(WebLogAspect::class.java) private val webRequestLog: ThreadLocal= ThreadLocal() @Autowired lateinit var webLogRepository: WebLogRepository /** * 定義一個(gè)切入,只要是為io.intodream..web下public修飾的方法都要切入 */ @Pointcut(value = "execution(public * io.intodream..web.*.*(..))") fun webLog() {} /** * 切面的連接點(diǎn),并聲明在該連接點(diǎn)進(jìn)入之前需要做的一些事情 */ @Before(value = "webLog()") @Throws(Throwable::class) fun doBefore(joinPoint: JoinPoint) { val webLog = WebLog() webLog.startTime = System.currentTimeMillis() val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes? val request = attributes!!.request val args = joinPoint.args val paramNames = (joinPoint.signature as CodeSignature).parameterNames val params = HashMap (args.size) for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } webLog.id = UUID.randomUUID().toString() webLog.request = params.toString() webLog.requestUrl = request.requestURI.toString() webLog.requestIp = request.remoteAddr webLog.method = request.method webRequestLog.set(webLog) logger.info("REQUEST={} {}; SOURCE IP={}; ARGS={}", request.method, request.requestURL.toString(), request.remoteAddr, params) } @AfterReturning(returning = "ret", pointcut = "webLog()") @Throws(Throwable::class) fun doAfterReturning(ret: Any) { val webLog = webRequestLog.get() webLog.response = ret.toString() webLog.endTime = System.currentTimeMillis() webLog.time = webLog.endTime!! - webLog.startTime!! logger.info("RESPONSE={}; SPEND TIME={}MS", ObjectMapper().writeValueAsString(ret), webLog.time) logger.info("webLog:{}", webLog) webLogRepository.save(webLog) webRequestLog.remove() } }
這里定義的是Web層的切面,對(duì)于Service層我也可以定義一個(gè)切面,但是對(duì)于Service層的進(jìn)入和返回的日志我們可以把級(jí)別稍等調(diào)低一點(diǎn),這里改debug,具體實(shí)現(xiàn)如下:
package io.intodream.kotlin07.aspect import com.fasterxml.jackson.databind.ObjectMapper import org.aspectj.lang.JoinPoint import org.aspectj.lang.annotation.AfterReturning import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Before import org.aspectj.lang.annotation.Pointcut import org.aspectj.lang.reflect.CodeSignature import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.core.annotation.Order import org.springframework.stereotype.Component import org.springframework.validation.BindingResult /** * service層所有public修飾的方法調(diào)用返回日志 * * @author yangxianxi@gogpay.cn * @date 2019/4/10 17:33 * */ @Aspect @Order(2) @Component class ServiceLogAspect { private val logger: Logger = LoggerFactory.getLogger(ServiceLogAspect::class.java) /** * */ @Pointcut(value = "execution(public * io.intodream..service.*.*(..))") private fun serviceLog(){} @Before(value = "serviceLog()") fun deBefore(joinPoint: JoinPoint) { val args = joinPoint.args val codeSignature = joinPoint.signature as CodeSignature val paramNames = codeSignature.parameterNames val params = HashMap接口測(cè)試(args.size).toMutableMap() for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } logger.debug("CALL={}; ARGS={}", joinPoint.signature.name, params) } @AfterReturning(returning = "ret", pointcut = "serviceLog()") @Throws(Throwable::class) fun doAfterReturning(ret: Any) { logger.debug("RESPONSE={}", ObjectMapper().writeValueAsString(ret)) } }
這里就不在貼出Service層和web的代碼實(shí)現(xiàn)了,因?yàn)槲沂强截愔皩PA那一章的代碼,唯一不同的就是加入了切面,切面的加入并不影響原來(lái)的業(yè)務(wù)流程。
執(zhí)行如下請(qǐng)求:
我們會(huì)在控制臺(tái)看到如下日志
2019-04-14 19:32:27.208 INFO 4914 --- [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : REQUEST=POST http://localhost:9000/api/student/; SOURCE IP=0:0:0:0:0:0:0:1; ARGS={student={"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}} 2019-04-14 19:32:27.415 INFO 4914 --- [nio-9000-exec-1] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:4}] to localhost:27017 2019-04-14 19:32:27.431 INFO 4914 --- [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : RESPONSE={"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}; SPEND TIME=239MS 2019-04-14 19:32:27.431 INFO 4914 --- [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : webLog:{"id":"e7b0ca1b-0a71-4fa0-9f5f-95a29d4d54a1","request":"{student={"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}}","response":"{"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}","time":239,"requestUrl":"/api/student/","requestIp":"0:0:0:0:0:0:0:1","startTime":1555241547191,"endTime":1555241547430,"method":"POST"}
查看數(shù)據(jù)庫(kù)會(huì)看到我們的請(qǐng)求日志已經(jīng)寫入了:
這里有一個(gè)地方需要注意,在Service層的實(shí)現(xiàn),具體如下:
return studentRepository.findById(id).get()
這里的findById會(huì)返回一個(gè)Optional
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/74105.html
摘要:一本節(jié)目標(biāo)前兩章主要講了的基本操作,這一章我們將學(xué)習(xí)使用訪問(wèn),并通過(guò)完成簡(jiǎn)單操作。這里有一個(gè)問(wèn)題什么不選用數(shù)據(jù)庫(kù)呢答案是目前支持。突出點(diǎn)是,即非阻塞的。二構(gòu)建項(xiàng)目及配置本章不在講解如何構(gòu)建項(xiàng)目了,大家可以參考第一章。 showImg(https://segmentfault.com/img/remote/1460000018819338?w=1024&h=500); 一、本節(jié)目標(biāo) 前兩...
摘要:一本節(jié)目標(biāo)前兩章主要講了的基本操作,這一章我們將學(xué)習(xí)使用訪問(wèn),并通過(guò)完成簡(jiǎn)單操作。這里有一個(gè)問(wèn)題什么不選用數(shù)據(jù)庫(kù)呢答案是目前支持。突出點(diǎn)是,即非阻塞的。二構(gòu)建項(xiàng)目及配置本章不在講解如何構(gòu)建項(xiàng)目了,大家可以參考第一章。 showImg(https://segmentfault.com/img/remote/1460000018819338?w=1024&h=500); 一、本節(jié)目標(biāo) 前兩...
摘要:是一門最近比較流行的靜態(tài)類型編程語(yǔ)言,而且和一樣同屬系。這個(gè)生成的構(gòu)造函數(shù)是合成的,因此不能從或中直接調(diào)用,但可以使用反射調(diào)用。 showImg(https://segmentfault.com/img/remote/1460000012958496); Kotlin是一門最近比較流行的靜態(tài)類型編程語(yǔ)言,而且和Groovy、Scala一樣同屬Java系。Kotlin具有的很多靜態(tài)語(yǔ)言...
摘要:二教程環(huán)境三創(chuàng)建項(xiàng)目創(chuàng)建項(xiàng)目有兩種方式一種是在官網(wǎng)上創(chuàng)建二是在上創(chuàng)建如圖所示勾選然后點(diǎn),然后一直默認(rèn)最后點(diǎn)擊完成即可。我們這里看到和普通的接口沒(méi)有異同,除了返回類型是用包裝之外。與之對(duì)應(yīng)的還有,這個(gè)后面我們會(huì)講到。 showImg(https://segmentfault.com/img/remote/1460000018819338?w=1024&h=500); 從去年開(kāi)始就開(kāi)始學(xué)習(xí)...
摘要:下一代服務(wù)端開(kāi)發(fā)下一代服務(wù)端開(kāi)發(fā)第部門快速開(kāi)始第章快速開(kāi)始環(huán)境準(zhǔn)備,,快速上手實(shí)現(xiàn)一個(gè)第章企業(yè)級(jí)服務(wù)開(kāi)發(fā)從到語(yǔ)言的缺點(diǎn)發(fā)展歷程的缺點(diǎn)為什么是產(chǎn)生的背景解決了哪些問(wèn)題為什么是的發(fā)展歷程容器的配置地獄是什么從到下一代企業(yè)級(jí)服務(wù)開(kāi)發(fā)在移動(dòng)開(kāi)發(fā)領(lǐng)域 《 Kotlin + Spring Boot : 下一代 Java 服務(wù)端開(kāi)發(fā) 》 Kotlin + Spring Boot : 下一代 Java...
閱讀 2044·2021-09-07 10:14
閱讀 1478·2019-08-30 15:53
閱讀 2270·2019-08-30 12:43
閱讀 2861·2019-08-29 16:37
閱讀 754·2019-08-26 13:29
閱讀 2000·2019-08-26 13:28
閱讀 437·2019-08-23 18:33
閱讀 3500·2019-08-23 16:09