Spring Boot 2.x 教程系列

文章首發於個人網站

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

本節中,您將學習如何在 Spring Boot 中使用 AOP 切面統一處理請求日誌,打印進出參相關參數。

一、先看看日誌輸出效果

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

可以看到,每個對於每個請求,開始與結束一目瞭然,並且打印了以下參數:

  • URL: 請求接口地址;
  • HTTP Method: 請求的方法,是 POST, GET, 還是 DELETE 等;
  • Class Method: 對應 Controller 的全路徑以及調用的哪個方法;
  • IP: 請求 IP 地址;
  • Request Args: 請求入參,以 JSON 格式輸出;
  • Response Args: 響應出參,以 JSON 格式輸出;
  • Time-Consuming: 請求耗時;

效果應該還不錯吧!接下來就讓我們一步一步去實現該功能, 首先, 。

二、添加 Maven 依賴

在項目 pom.xml 文件中添加依賴:


<dependency>

<groupid>
org.springframework.boot
/<groupid>

<artifactid>
spring-boot-starter-aop
/<artifactid>
/<dependency>

<dependency>

<groupid>
com.google.code.gson
/<groupid>

<artifactid>
gson
/<artifactid>

<version>
2.8.5
/<version>
/<dependency>

三、配置 AOP 切面

在配置 AOP 切面之前,我們需要了解下 aspectj 相關注解的作用:

  • @Aspect:聲明該類為一個註解類;
  • @Pointcut:定義一個切點,後面跟隨一個表達式,表達式可以定義為某個 package 下的方法,也可以是自定義註解等;
  • 切點定義好後,就是圍繞這個切點做文章了:
  • @Before: 在切點之前,織入相關代碼;
  • @After: 在切點之後,織入相關代碼;
  • @AfterReturning: 在切點返回內容後,織入相關代碼,一般用於對返回值做些加工處理的場景;
  • @AfterThrowing: 用來處理當織入的代碼拋出異常後的邏輯處理;
  • @Around: 在切入點前後織入代碼,並且可以自由的控制何時執行切點;

接下來,定義一個 WebLogAspect.java 切面類,代碼如下:

package
site

.
exception
.
springbootaopwebrequest
.
aspect
;
import
com
.
google
.
gson
.
Gson
;
import
org
.
aspectj
.
lang
.
JoinPoint
;
import
org
.
aspectj
.
lang
.
ProceedingJoinPoint
;
import
org
.
aspectj
.
lang
.
annotation
.*;
import
org
.
slf4j
.
Logger
;

import
org
.
slf4j
.
LoggerFactory
;
import
org
.
springframework
.
stereotype
.
Component
;
import
org
.
springframework
.
web
.
context
.
request
.
RequestContextHolder
;
import
org
.
springframework
.
web
.
context
.
request
.
ServletRequestAttributes
;
import
javax
.
servlet
.
http
.
HttpServletRequest

;
/**
* @author www.exception.site (exception 教程網)
* @date 2019/2/12
* @time 14:03
* @discription
**/
@Aspect
@Component
public

class

WebLogAspect

{

private

final

static

Logger
logger
=

LoggerFactory
.
getLogger
(
WebLogAspect
.
class
);

/** 以 controller 包下定義的所有請求為切入點 */

@Pointcut
(
"execution(public * site.exception.springbootaopwebrequest.controller..*.*(..))"
)

public

void
webLog
()

{}

/**
* 在切點之前織入
* @param joinPoint
* @throws Throwable
*/

@Before
(
"webLog()"
)

public

void
doBefore
(
JoinPoint
joinPoint
)

throws

Throwable

{

// 開始打印請求日誌

ServletRequestAttributes
attributes
=

(
ServletRequestAttributes
)

RequestContextHolder
.
getRequestAttributes
();

HttpServletRequest
request
=
attributes
.
getRequest

();

// 打印請求相關參數
logger
.
info
(
"========================================== Start =========================================="
);

// 打印請求 url
logger
.
info
(
"URL : {}"
,
request
.
getRequestURL
().
toString
());

// 打印 Http method
logger
.
info
(
"HTTP Method : {}"
,
request
.
getMethod
());

// 打印調用 controller 的全路徑以及執行方法
logger
.
info
(
"Class Method : {}.{}"
,
joinPoint
.
getSignature
().
getDeclaringTypeName

(),
joinPoint
.
getSignature
().
getName
());

// 打印請求的 IP
logger
.
info
(
"IP : {}"
,
request
.
getRemoteAddr
());

// 打印請求入參
logger
.
info
(
"Request Args : {}"
,

new

Gson
().
toJson
(
joinPoint
.
getArgs
()));

}

/**
* 在切點之後織入
* @throws Throwable
*/

@After
(
"webLog()"

)

public

void
doAfter
()

throws

Throwable

{
logger
.
info
(
"=========================================== End ==========================================="
);

// 每個請求之間空一行
logger
.
info
(
""
);

}

/**
* 環繞
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/

@Around
(
"webLog()"
)

public

Object
doAround
(
ProceedingJoinPoint
proceedingJoinPoint

)

throws

Throwable

{

long
startTime
=

System
.
currentTimeMillis
();

Object
result
=
proceedingJoinPoint
.
proceed
();

// 打印出參
logger
.
info
(
"Response Args : {}"
,

new

Gson
().
toJson
(
result
));

// 執行耗時
logger
.
info
(
"Time-Consuming : {} ms"
,

System
.
currentTimeMillis
()

-
startTime
);

return
result
;

}
}

我們通過 @Aspect 聲明瞭 WebLogAspect.java 為切面類,之後,通過 @Pointcut 定義了打印請求日誌的切點,切點為 site.exception.springbootaopwebrequest.controller 包下所有的請求接口。

切點定義好後,我們通過 @Before 在切點之前打印請求的相關參數,通過 @Around 打印了請求接口的耗時時間,最後通過 @After 做了請求的收尾工作。

到這裡,切面相關的代碼就完成了!

三、測試

我們針對 GET, POST, 文件提交,以及多文件提交四種接口分別測試其效果, 接口定義如下:

package
site
.
exception
.
springbootaopwebrequest
.
controller
;
import
org
.
slf4j
.
Logger
;
import
org
.
slf4j
.
LoggerFactory
;
import
org
.
springframework
.
web
.
bind
.
annotation
.*;
import
org
.
springframework
.
web
.
multipart
.
MultipartFile
;
import
site
.
exception
.

springbootaopwebrequest
.
entity
.
User
;
/**
* @author www.exception.site (exception 教程網)
* @date 2019/2/16
* @time 21:03
* @discription
**/
@RestController
public

class

TestController

{

private

final

static

Logger
logger
=

LoggerFactory
.
getLogger
(
TestController
.
class
);

/**
* POST 方式接口測試
* @param user
* @return
*/

@PostMapping
(
"/user"
)


public

User
testPost
(
@RequestBody

User
user
)

{
logger
.
info
(
"testPost ..."
);

return
user
;

}

/**
* GET 方式接口測試
* @return
*/

@GetMapping
(
"/user"
)

public

String
testGet
(
@RequestParam
(
"username"
)

String
username
,

@RequestParam
(
"password"
)

String
password
)

{
logger
.
info
(
"testGet ..."
);

return

"success"
;

}

/**
* 單文件上傳接口測試
* @return
*/

@PostMapping
(
"/file/upload"
)

public

String
testFileUpload
(
@RequestParam
(
"file"
)

MultipartFile
file
)

{
logger

.
info
(
"testFileUpload ..."
);

return

"success"
;

}

/**
* 多文件上傳接口測試
* @return
*/

@PostMapping
(
"/multiFile/upload"
)

public

String
testMultiFileUpload
(
@RequestParam
(
"file"
)

MultipartFile
[]
file
)

{
logger
.
info
(
"testMultiFileUpload ..."
);

return

"success"
;


}
}

User.java:

package
site
.
exception
.
springbootaopwebrequest
.
entity
;
import
java
.
io
.
Serializable
;
import
java
.
util
.
Date
;
/**
* @author www.exception.site (exception 教程網)
* @date 2019/2/16
* @time 21:00
* @discription
**/
public

class

User

implements

Serializable

{

/**
* 用戶名

*/

private

String
username
;

/**
* 密碼
*/

private

String
password
;

/**
* 創建時間
*/

private

Date
createTime
;

public

String
getUsername
()

{

return
username
;

}

public

void
setUsername
(
String
username
)


{

this
.
username
=
username
;

}

public

String
getPassword
()

{

return
password
;

}

public

void
setPassword
(
String
password
)

{

this
.
password
=
password
;

}

public

Date
getCreateTime

()

{

return
createTime
;

}

public

void
setCreateTime
(
Date
createTime
)

{

this
.
createTime
=
createTime
;

}
}

3.1 GET 接口測試

請求 http://localhost:8080/user?username=張三&password=123456接口,觀察日誌打印情況:

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

GET 接口正常打印日誌!

3.2 POST 接口測試

通過 Postman 請求 http://localhost:8080/user POST 接口:

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

看看控制檯輸出效果:

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

POST 接口也是 OK 的!

3.3 單文件提交接口測試

請求單文件提交接口: http://localhost:8080/file/upload :

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

日誌輸出如下:

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

3.4 多文件提交接口測試

請求單文件提交接口: http://localhost:8080/multiFile/upload :

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

切面日誌輸出:

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

為何不用 FASTJSON

筆者在開始階段的確使用的是阿里 FASTJSON 來做出入參的打印,但是對於文件上傳接口,FASTJSON 轉換會出錯,故改用谷歌的 Gson。

Spring Boot 2.x 教程系列 | AOP 切面統一打印請求日誌

GitHub 源碼地址


分享到:


相關文章: