前言
創(chuàng)新互聯(lián)建站專(zhuān)業(yè)為企業(yè)提供卡若網(wǎng)站建設(shè)、卡若做網(wǎng)站、卡若網(wǎng)站設(shè)計(jì)、卡若網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、卡若企業(yè)網(wǎng)站模板建站服務(wù),十多年卡若做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
AOP 是 Aspect Oriented Program (面向切面)的編程的縮寫(xiě)。他是和面向?qū)ο缶幊滔鄬?duì)的一個(gè)概念。在面向?qū)ο蟮木幊讨?,我們傾向于采用封裝、繼承、多態(tài)等概念,將一個(gè)個(gè)的功能在對(duì)象中來(lái)實(shí)現(xiàn)。但是,我們?cè)趯?shí)際情況中也發(fā)現(xiàn),會(huì)有另外一種需求就是一類(lèi)功能在很多對(duì)象的很多方法中都有需要。例如有一些對(duì)數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)的方法有事務(wù)管理的需求,有很多方法中要求打印日志。按照面向?qū)ο蟮姆绞?,那么這些相同的功能要在很多地方來(lái)實(shí)現(xiàn)或者在很多地方來(lái)調(diào)用。這就非常繁瑣并且和這些和業(yè)務(wù)不相關(guān)的需求耦合太緊密了。所以后來(lái)就出現(xiàn)了面向切面的編程來(lái)解決這一類(lèi)問(wèn)題,并對(duì)面向?qū)ο蟮木幊套隽撕芎玫难a(bǔ)充
概念
要很好的理解面向切面的編程,先要理解 AOP 的一些概念。在 Java 中 AspectJ 比較完整的實(shí)現(xiàn)了 AOP 的功能,但是使用起來(lái)也比較復(fù),所以這里主要是討論 Spring 的 AOP 。Spring AOP 采用簡(jiǎn)單夠用的原則,實(shí)現(xiàn)了 AOP 的核心功能。下面先說(shuō)說(shuō) AOP 中的具體概念
SprinBoot AOP 實(shí)現(xiàn)
前面我們已經(jīng)用好幾章講述了 SpringBoot 的基本使用。那么這里我們就用 SpringBoot 和 AOP 結(jié)合來(lái)實(shí)現(xiàn)一個(gè)輸出所有 Rest 接口輸入?yún)?shù)和返回參數(shù)的日志的功能。
實(shí)現(xiàn) rest 服務(wù)功能。
根據(jù)前面的文章,我們先建立一個(gè) SpingBoot 的工程如下圖所示
demo 工程
SpringBoot 項(xiàng)目配置
我們對(duì) SpringBoot 項(xiàng)目配置如下
server: port: 3030 servlet: context-path: /aop-demo spring: jackson: date-format: yyyy-MM-dd HH:mm:ss serialization: indent-output: true logging: level: com.yanggch: debug
其中 jackson 相關(guān)配置是為了將對(duì)象輸出成 json 字符串后能夠格式化輸出
實(shí)現(xiàn)一個(gè) rest 接口的 Controller 類(lèi)
在這里,我們實(shí)現(xiàn)兩個(gè) rest 接口。一個(gè)是返回 hello 信息。一個(gè)是根據(jù)輸入返回登錄信息。
package com.yanggch.demo.aop.web; import com.yanggch.demo.aop.domain.LoginEntity; import com.yanggch.demo.aop.domain.SecurityEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.Date; /** * 安全相關(guān) rest 服務(wù) * * @author : 楊高超 * @since : 2018-05-27 */ @RestController @RequestMapping("/api/v1/security") public class SecurityApi { @RequestMapping(value = "/login/{shopId}", method = RequestMethod.POST) public SecurityEntity login(@RequestBody LoginEntity loginEntity, @PathVariable Long shopId) { SecurityEntity securityEntity = new SecurityEntity(); securityEntity.setShopId(shopId); securityEntity.setAccount(loginEntity.getAccount()); securityEntity.setPwd(loginEntity.getPwd()); securityEntity.setLoginTime(new Date()); return securityEntity; } @RequestMapping(value = "/echo/{name}", method = RequestMethod.GET) public String login(@PathVariable String name) { return "hello," + name; } }
先在我們要通過(guò) AOP 功能將所有 Rest 接口的輸入?yún)?shù)和返回結(jié)果輸出到日志中。
實(shí)現(xiàn) Web Aop 功能。
package com.yanggch.demo.aop.comment; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * web 接口日志 * * @author : 楊高超 * @since : 2018-05-27 */ @Aspect @Component public class WebLogAspect { private static Logger log = LoggerFactory.getLogger(WebLogAspect.class); private final ObjectMapper mapper; @Autowired public WebLogAspect(ObjectMapper mapper) { this.mapper = mapper; } @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") public void webLog() { } @Before("webLog()") public void doBefore(JoinPoint joinPoint) { for (Object object : joinPoint.getArgs()) { if ( object instanceof MultipartFile || object instanceof HttpServletRequest || object instanceof HttpServletResponse ) { continue; } try { if (log.isDebugEnabled()) { log.debug( joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + " : request parameter : " + mapper.writeValueAsString(object) ); } } catch (Exception e) { e.printStackTrace(); } } } @AfterReturning(returning = "response", pointcut = "webLog()") public void doAfterReturning(Object response) throws Throwable { if (response != null) { log.debug("response parameter : " + mapper.writeValueAsString(response)); } } }
這里有幾個(gè)需要注意的地方,
測(cè)試
在前臺(tái)通過(guò) postman 發(fā)起請(qǐng)求,后臺(tái)日志輸入結(jié)果如下
2018-05-27 19:58:42.941 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect : com.yanggch.demo.aop.web.SecurityApi.login : request parameter : {
"account" : "yanggch",
"pwd" : "123456"
}
2018-05-27 19:58:42.941 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect : com.yanggch.demo.aop.web.SecurityApi.login : request parameter : 2001
2018-05-27 19:58:42.942 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect : response parameter : {
"shopId" : 2001,
"account" : "yanggch",
"pwd" : "123456",
"loginTime" : "2018-05-27 11:58:42"
}
2018-05-27 19:58:45.796 DEBUG 86072 --- [nio-3030-exec-5] c.yanggch.demo.aop.comment.WebLogAspect : com.yanggch.demo.aop.web.SecurityApi.echo : request parameter : "yanggch"
2018-05-27 19:58:45.796 DEBUG 86072 --- [nio-3030-exec-5] c.yanggch.demo.aop.comment.WebLogAspect : response parameter : "hello,yanggch"
由此可見(jiàn),我們雖然沒(méi)有在 rest 接口方法中寫(xiě)輸出日志的代碼,但是通過(guò) AOP 的方式可以自動(dòng)的給各個(gè) rest 入口方法中添加上輸出入口參數(shù)和返回參數(shù)的代碼并正確執(zhí)行。
其他說(shuō)明
前面提到了 Advice 的類(lèi)型和 Pointcut 的 AOP 表達(dá)式語(yǔ)言。具體參考如下。
Advice 類(lèi)型
AOP 表達(dá)式語(yǔ)言
1、方法參數(shù)匹配
@args()
2、方法描述匹配
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
其中 returning type pattern,name pattern, and parameters pattern是必須的.
. ret-type-pattern:可以為表示任何返回值,全路徑的類(lèi)名等.
*. name-pattern:指定方法名, *代表所有
.set代表以set開(kāi)頭的所有方法.
. parameters pattern:指定方法參數(shù)(聲明的類(lèi)型),(..)代表所有參數(shù),()代表一個(gè)參數(shù)
. (,String)代表第一個(gè)參數(shù)為任何值,第二個(gè)為String類(lèi)型.
3、當(dāng)前AOP代理對(duì)象類(lèi)型匹配
4、目標(biāo)類(lèi)匹配
@target()
@within()
5、標(biāo)有此注解的方法匹配
@annotation()
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。