亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

SpringBoot 2.X Kotlin系列之AOP統(tǒng)一打印日志

Nino / 3648人閱讀

在開發(fā)項目中,我們經常會需要打印日志,這樣方便開發(fā)人員了解接口調用情況及定位錯誤問題,很多時候對于Controller或者是Service的入參出參需要打印日志,但是我們又不想重復的在每個方法里去使用logger打印,這個時候希望有一個管理者統(tǒng)一來打印,這時Spring AOP就派上用場了,利用切面的思想,我們在進入、出入Controller或Service時給它切一刀實現統(tǒng)一日志打印。

SpringAOP不僅可以實現在不產生新類的情況下打印日志,還可以管理事務、緩存等。具體可以了解官方文檔。https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-api

基礎概念

在使用SpringAOP,這里還是先簡單講解一些基本的知識吧,如果說的不對請及時指正,這里主要是根據官方文檔來總結的。本章內容主要涉及的知識點。

Pointcut: 切入點,這里用于定義規(guī)則,進行方法的切入(形象的比喻就是一把刀)。

JoinPoint: 連接點,用于連接定義的切面。

Before: 在之前,在切入點方法執(zhí)行之前。

AfterReturning: 在切入點方法結束并返回時執(zhí)行。

這里除了SpringAOP相關的知識,還涉及到了線程相關的知識點,因為我們需要考慮多線程中它們各自需要保存自己的變量,所以就用到了ThreadLocal

依賴引入

這里主要是用到aopmongodb,在pom.xml文件中加入以下依賴即可:


    org.springframework.boot
    spring-boot-starter-aop


    org.springframework.boot
    spring-boot-starter-data-mongodb
相關實體類
/**
 * 請求日志實體,用于保存請求日志
 */
@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è)務對象,上一章講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)
    }
}
定義切面 定義切入點
/**
 * 定義一個切入,只要是為io.intodream..web下public修飾的方法都要切入
 */
@Pointcut(value = "execution(public * io.intodream..web.*.*(..))")
fun webLog() {}

定義切入點的表達式還可以使用within、如:

/**
 * 表示在io.intodream.web包下的方法都會被切入
 */
@Pointcut(value = "within(io.intodream.web..*")
定義一個連接點
/**
 * 切面的連接點,并聲明在該連接點進入之前需要做的一些事情
 */
@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)
}
方法結束后執(zhí)行
@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í)行前,先記錄詳情的請求參數,請求方法,請求ip, 請求方式及進入時間,然后將對象放入到ThreadLocal中,在方法結束后并取到對應的返回對象且計算出請求耗時,然后將請求日志保存到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

    /**
     * 定義一個切入,只要是為io.intodream..web下public修飾的方法都要切入
     */
    @Pointcut(value = "execution(public * io.intodream..web.*.*(..))")
    fun webLog() {}

    /**
     * 切面的連接點,并聲明在該連接點進入之前需要做的一些事情
     */
    @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層的切面,對于Service層我也可以定義一個切面,但是對于Service層的進入和返回的日志我們可以把級別稍等調低一點,這里改debug,具體實現如下:

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修飾的方法調用返回日志
 *
 * @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(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的代碼實現了,因為我是拷貝之前將JPA那一章的代碼,唯一不同的就是加入了切面,切面的加入并不影響原來的業(yè)務流程。

執(zhí)行如下請求:

我們會在控制臺看到如下日志

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"}

查看數據庫會看到我們的請求日志已經寫入了:

這里有一個地方需要注意,在Service層的實現,具體如下:

return studentRepository.findById(id).get()

這里的findById會返回一個Optional對象,如果沒有查到數據,我們使用get獲取數據會出現異常java.util.NoSuchElementException: No value present,可以改為返回對象可以為空只要在返回類型后面加一個?即可,同時調用OptionalifPresent進行安全操作。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://www.ezyhdfw.cn/yun/74105.html

相關文章

  • SpringBoot 2.X Kotlin 系列Reactive Mongodb 與 JPA

    摘要:一本節(jié)目標前兩章主要講了的基本操作,這一章我們將學習使用訪問,并通過完成簡單操作。這里有一個問題什么不選用數據庫呢答案是目前支持。突出點是,即非阻塞的。二構建項目及配置本章不在講解如何構建項目了,大家可以參考第一章。 showImg(https://segmentfault.com/img/remote/1460000018819338?w=1024&h=500); 一、本節(jié)目標 前兩...

    MSchumi 評論0 收藏0
  • SpringBoot 2.X Kotlin 系列Reactive Mongodb 與 JPA

    摘要:一本節(jié)目標前兩章主要講了的基本操作,這一章我們將學習使用訪問,并通過完成簡單操作。這里有一個問題什么不選用數據庫呢答案是目前支持。突出點是,即非阻塞的。二構建項目及配置本章不在講解如何構建項目了,大家可以參考第一章。 showImg(https://segmentfault.com/img/remote/1460000018819338?w=1024&h=500); 一、本節(jié)目標 前兩...

    ?。琛?/span> 評論0 收藏0
  • 初探Kotlin+SpringBoot聯合編程

    摘要:是一門最近比較流行的靜態(tài)類型編程語言,而且和一樣同屬系。這個生成的構造函數是合成的,因此不能從或中直接調用,但可以使用反射調用。 showImg(https://segmentfault.com/img/remote/1460000012958496); Kotlin是一門最近比較流行的靜態(tài)類型編程語言,而且和Groovy、Scala一樣同屬Java系。Kotlin具有的很多靜態(tài)語言...

    xiaokai 評論0 收藏0
  • SpringBoot 2.X Kotlin 系列Hello World

    摘要:二教程環(huán)境三創(chuàng)建項目創(chuàng)建項目有兩種方式一種是在官網上創(chuàng)建二是在上創(chuàng)建如圖所示勾選然后點,然后一直默認最后點擊完成即可。我們這里看到和普通的接口沒有異同,除了返回類型是用包裝之外。與之對應的還有,這個后面我們會講到。 showImg(https://segmentfault.com/img/remote/1460000018819338?w=1024&h=500); 從去年開始就開始學習...

    warkiz 評論0 收藏0
  • Kotlin + Spring Boot : 下一代 Java 服務端開發(fā) 》

    摘要:下一代服務端開發(fā)下一代服務端開發(fā)第部門快速開始第章快速開始環(huán)境準備,,快速上手實現一個第章企業(yè)級服務開發(fā)從到語言的缺點發(fā)展歷程的缺點為什么是產生的背景解決了哪些問題為什么是的發(fā)展歷程容器的配置地獄是什么從到下一代企業(yè)級服務開發(fā)在移動開發(fā)領域 《 Kotlin + Spring Boot : 下一代 Java 服務端開發(fā) 》 Kotlin + Spring Boot : 下一代 Java...

    springDevBird 評論0 收藏0

發(fā)表評論

0條評論

Nino

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<