AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術.AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
在spring AOP中業務邏輯僅僅只關注業務本身,將日志記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中划分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的代碼。
相關注解介紹:
@Aspect:作用是把當前類標識為一個切面供容器讀取
@Pointcut:Pointcut是植入Advice的觸發條件。每個Pointcut的定義包括2部分,一是表達式,二是方法簽名。方法簽名必須是 public及void型。可以將Pointcut中的方法看作是一個被Advice引用的助記符,因為表達式不直觀,因此我們可以通過方法簽名的方式為 此表達式命名。因此Pointcut中的方法只需要方法簽名,而不需要在方法體內編寫實際代碼。
@Around:環繞增強,相當於MethodInterceptor
@AfterReturning:后置增強,相當於AfterReturningAdvice,方法正常退出時執行
@Before:標識一個前置增強方法,相當於BeforeAdvice的功能,相似功能的還有
@AfterThrowing:異常拋出增強,相當於ThrowsAdvice
@After: final增強,不管是拋出異常或者正常退出都會執行
Spring AOP 中@Pointcut的用法
execution表達式
1)execution(* *(..)) //表示匹配所有方法 2)execution(public * com. xl.service.UserService.*(..)) //表示匹配com.xl.server.UserService中所有的公有方法 3)execution(* com.xl.server..*.*(..)) //表示匹配com.xl.server包及其子包下的所有方法 4)@annotation(com.platform.annotation.SysLog) //表示匹配注解SysLog
在Spring 2.0中,Pointcut的定義包括兩個部分:Pointcut表示式(expression)和Pointcut簽名(signature)
//Pointcut表示式 @Pointcut("execution(* com.xl.aop.MessageSender.*(..))") //Point簽名 private void log(){}
然后要使用所定義的Pointcut時,可以指定Pointcut簽名
如下:
@Before("log()")
這種使用方式等同於以下方式,直接定義execution表達式使用
@Before("execution(* com.xl.aop.MessageSender.*(..))")
請求日志代碼:
package cn.aid.cmsweb.assist.aspect; import cn.aid.cmsweb.entity.UserDetails; import cn.aid.cmsweb.handler.OperationLogHandler; import cn.aid.common.utils.util.HttpUtil; import cn.aid.data.api.resource.entity.OperationLog; import com.alibaba.fastjson.JSONObject; 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.beans.factory.annotation.Autowired; 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.*; @Aspect //使之成為切面類 @Component //把切面類加入到IOC容器中 @Order(1) //控制AOP的加載順序 public class OperationLogAspect { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired OperationLogHandler operationLogHandler; private ThreadLocal<Long> startTime = new ThreadLocal<>(); //定義切入點 @Pointcut("execution(* cn.aid.cmsweb.controller..*.*Controller.*(..))") public void webLog() { } //前置通知:目標方法執行之前執行以下方法體的內容 @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); } //返回通知:目標方法正常執行完畢時執行以下代碼 @AfterReturning(value = "webLog()", returning = "ret") public void doAfterReturning(Object ret) throws Throwable { } //環繞通知:目標方法執行前后分別執行一些代碼,發生異常的時候執行另外一些代碼 @Around("webLog()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { // 接收到請求,記錄請求內容 Object result = null; try { //【環繞通知中的--->前置通知】 //獲取當前請求對象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Map<String, String[]> parameterMap = request.getParameterMap(); //記錄請求信息 OperationLog operationLog = new OperationLog(); result = joinPoint.proceed(); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method.isAnnotationPresent(ApiOperation.class)) { ApiOperation log = method.getAnnotation(ApiOperation.class); operationLog.setDescription(log.value()); } long endTime = System.currentTimeMillis(); UserDetails userDetails = (UserDetails) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT_DETAILS"); if(null != userDetails ){ operationLog.setUsername(userDetails.getUsername()); } operationLog.setBasePath(HttpUtil.getBasePath(request)); operationLog.setIp(HttpUtil.getClientIp(request)); operationLog.setMethod(request.getMethod()); operationLog.setParameter(getParameterMap(parameterMap)); //operationLog.setResult(result); operationLog.setSpendTime((int) (endTime - startTime.get())); operationLog.setOperationTime(startTime.get()); operationLog.setUri(request.getRequestURI()); operationLog.setUrl(request.getRequestURL().toString()); // operationLog.setDescription(getParameterMap(parameterMap)); Map<String,Object> logMap = new HashMap<>(); logMap.put("url",operationLog.getUrl()); logMap.put("method",operationLog.getMethod()); logMap.put("parameter",operationLog.getParameter()); logMap.put("spendTime",operationLog.getSpendTime()); logMap.put("description",operationLog.getDescription()); // LOGGER.info("{}", JsonUtil.objectToJson(webLog)); //logger.info(Markers.appendEntries(logMap),JsonUtil.objectToJson(operationLog)); operationLogHandler.saveLog(operationLog); // 【環繞通知中的--->返回通知】 return result; } catch (Exception e) { //【環繞通知中的--->異常通知】 logger.error("日志記錄出錯!", e); return result; } //【環繞通知中的--->HOUZ通知】 } private String getParameterMap(Map<String,String[]> parameterMap) { if(null == parameterMap){ return null; } String result = null; try{ Map<String,Object> paramsMap = new HashMap<>(); Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet(); entries.forEach( entry -> { paramsMap.put(entry.getKey(),entry.getValue()[0]); } ); Object o = JSONObject.toJSON(paramsMap); result = o.toString(); }catch (Exception e){ logger.error("日志記錄出錯! getParameterMap == {}", e.getMessage()); } return result; } /** * 根據方法和傳入的參數獲取請求參數 */ private String getParameter(Method method, Object[] args) { List<Object> argList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { argList.add(args[i]); } 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); }else{ Map<String, Object> map = new HashMap<>(); String key = parameters[i].getName(); map.put(key, args[i]); argList.add(map); } } if (argList.size() == 0) { return null; } else if (argList.size() == 1) { return argList.toString(); } else { return argList.toString(); } } }