前言
AOP 是Aspect Oriented Program (面向切面)的編程的縮寫。他是和麵向對象編程相對的一個概念。在面向對象的編程中,我們傾向於採用封裝、繼承、多態等概念,將一個個的功能在對像中來實現。但是,我們在實際情況中也發現,會有另外一種需求就是一類功能在很多對象的很多方法中都有需要。例如有一些對數據庫訪問的方法有事務管理的需求,有很多方法中要求打印日誌。按照面向對象的方式,那麼這些相同的功能要在很多地方來實現或者在很多地方來調用。這就非常繁瑣並且和這些和業務不相關的需求耦合太緊密了。所以後來就出現了面向切面的編程來解決這一類問題,並對面向對象的編程做了很好的補充
概念
要很好的理解面向切面的編程,先要理解AOP 的一些概念。在Java 中AspectJ 比較完整的實現了AOP 的功能,但是使用起來也比較复,所以這裡主要是討論Spring 的AOP 。 Spring AOP 採用簡單夠用的原則,實現了AOP 的核心功能。下面先說說AOP 中的具體概念
SprinBoot AOP 實現
前面我們已經用好幾章講述了SpringBoot 的基本使用。那麼這裡我們就用SpringBoot 和AOP 結合來實現一個輸出所有Rest 接口輸入參數和返回參數的日誌的功能。
實現rest 服務功能。
根據前面的文章,我們先建立一個SpingBoot 的工程如下圖所示
demo 工程
SpringBoot 項目配置
我們對SpringBoot 項目配置如下
server: port: 3030 servlet: context-path: /aop-demospring: jackson: date-format: yyyy-MM-dd HH:mm:ss serialization: indent-output: truelogging: level: com.yanggch: debug
其中jackson 相關配置是為了將對象輸出成json 字符串後能夠格式化輸出
實現一個rest 接口的Controller 類
在這裡,我們實現兩個rest 接口。一個是返回hello 信息。一個是根據輸入返回登錄信息。
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;/** * 安全相關rest 服務* * @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; }}先在我們要通過AOP 功能將所有Rest 接口的輸入參數和返回結果輸出到日誌中。
實現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@Componentpublic 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)); } }}這裡有幾個需要注意的地方,
測試
在前台通過postman 發起請求,後台日誌輸入結果如下
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"
由此可見,我們雖然沒有在rest 接口方法中寫輸出日誌的代碼,但是通過AOP 的方式可以自動的給各個rest 入口方法中添加上輸出入口參數和返回參數的代碼並正確執行。
其他說明
前面提到了Advice 的類型和Pointcut 的AOP 表達式語言。具體參考如下。
Advice 類型
AOP 表達式語言
1、方法參數匹配
@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:可以為表示任何返回值,全路徑的類名等.
*. name-pattern:指定方法名, *代表所有
.set代表以set開頭的所有方法.
. parameters pattern:指定方法參數(聲明的類型),(..)代表所有參數,()代表一個參數
. (,String)代表第一個參數為任何值,第二個為String類型.
3、當前AOP代理對像類型匹配
4、目標類匹配
@target()
@within()
5、標有此註解的方法匹配
@annotation()
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。