文章來源:https://macrozheng.github.io/mall-learning/#/technology/aop_logjava
AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。git
通知描述了切面要完成的工做以及什麼時候執行。好比咱們的日誌切面須要記錄每一個接口調用時長,就須要在接口調用先後分別記錄當前時間,再取差值。github
通知功能被應用的時機。好比接口方法被調用的時候就是日誌切面的鏈接點。web
切點定義了通知功能被應用的範圍。好比日誌切面的應用範圍就是全部接口,即全部controller層的接口方法。spring
切面是通知和切點的結合,定義了什麼時候、何地應用通知功能。編程
在無需修改現有類的狀況下,向現有的類添加新方法或屬性。json
把切面應用到目標對象並建立新的代理對象的過程。api
指定了通知被應用的範圍,表達式格式:測試
execution(方法修飾符 返回類型 方法所屬的包.類名.方法名稱(方法參數)
//com.macro.mall.tiny.controller包中全部類的public方法都應用切面裏的通知 execution(public * com.macro.mall.tiny.controller.*.*(..)) //com.macro.mall.tiny.service包及其子包下全部類中的全部方法都應用切面裏的通知 execution(* com.macro.mall.tiny.service..*.*(..)) //com.macro.mall.tiny.service.PmsBrandService類中的全部方法都應用切面裏的通知 execution(* com.macro.mall.tiny.service.PmsBrandService.*(..))
用於封裝須要記錄的日誌信息,包括操做的描述、時間、消耗時間、url、請求參數和返回結果等信息。url
package com.xc.mall2.dto; import lombok.Getter; import lombok.Setter; import lombok.ToString; /** * Controller層的日誌封裝類 * Created by xc on 190903. */ @Getter @Setter @ToString public class WebLog { /** * 操做描述 */ private String description; /** * 操做用戶 */ private String username; /** * 操做時間 */ private Long startTime; /** * 消耗時間 */ private Integer spendTime; /** * 根路徑 */ private String basePath; /** * URI */ private String uri; /** * URL */ private String url; /** * 請求類型 */ private String method; /** * IP地址 */ private String ip; /** * 請求參數 */ private Object parameter; /** * 請求返回的結果 */ private Object result; }
定義了一個日誌切面,在環繞通知中獲取日誌須要的信息,並應用到controller層中全部的public方法中去。
package com.xc.mall2.component; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.json.JSONUtil; import com.xc.mall2.dto.WebLog; import io.swagger.annotations.ApiOperation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 統一日誌處理切面 * Created by xc on 190903. */ @Aspect @Component @Order(1) public class WebLogAspect { private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class); @Pointcut("execution(public * com.xc.mall2.controller.*.*(..))") public void webLog() { } @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { } @AfterReturning(value = "webLog()", returning = "ret") public void doAfterReturning(Object ret) throws Throwable { } @Around("webLog()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); //獲取當前請求對象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //記錄請求信息 WebLog webLog = new WebLog(); Object result = joinPoint.proceed(); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method.isAnnotationPresent(ApiOperation.class)) { ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); webLog.setDescription(apiOperation.value()); } long endTime = System.currentTimeMillis(); String urlStr = request.getRequestURL().toString(); webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath())); webLog.setIp(request.getRemoteUser()); webLog.setMethod(request.getMethod()); webLog.setParameter(getParameter(method, joinPoint.getArgs())); webLog.setResult(result); webLog.setSpendTime((int) (endTime - startTime)); webLog.setStartTime(startTime); webLog.setUri(request.getRequestURI()); webLog.setUrl(request.getRequestURL().toString()); LOGGER.info("{}", JSONUtil.parse(webLog)); return result; } /** * 根據方法和傳入的參數獲取請求參數 */ private Object getParameter(Method method, Object[] args) { List<Object> argList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //將RequestBody註解修飾的參數做爲請求參數 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { argList.add(args[i]); } //將RequestParam註解修飾的參數做爲請求參數 RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); if (requestParam != null) { Map<String, Object> map = new HashMap<>(); String key = parameters[i].getName(); if (!StringUtils.isEmpty(requestParam.value())) { key = requestParam.value(); } map.put(key, args[i]); argList.add(map); } } if (argList.size() == 0) { return null; } else if (argList.size() == 1) { return argList.get(0); } else { return argList; } } }
接口測試:
2019-09-03 16:48:31.866 INFO 14208 --- [nio-8080-exec-2] com.xc.mall2.component.WebLogAspect : {"result":{"code":200,"data":{"total":11,"totalPage":11,"pageSize":1,"list":[{"productCommentCount":100,"name":"萬和","bigPic":"","logo":"http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/images/20180607/timg(5).jpg","showStatus":1,"id":1,"sort":0,"productCount":100,"firstLetter":"W","factoryStatus":1}],"pageNum":1},"message":"操做成功"},"basePath":"http://localhost:8080","method":"GET","ip":"test","parameter":[{"pageNum":1},{"pageSize":1}],"description":"分頁查詢品牌列表","startTime":1567500511861,"uri":"/brand/list","url":"http://localhost:8080/brand/list","spendTime":5}