1. 程式人生 > 實用技巧 >UOJ32【UR #2】跳蚤公路

UOJ32【UR #2】跳蚤公路

技術標籤:Aopjava

基於aop思想實現日誌列印

基於切點表示式實現:

  1. execution詳解
    execution的語法表示式如下:execution(<修飾符> <返回型別> <類路徑> <方法名>(<引數列表>) <異常模式> )
    其中,修飾符和異常是可選的,如果不加類路徑,則預設對所有的類生效。它常用例項如下:
  2. 通過方法簽名、返回值定義切點:
  • execution(public * *Service(..)):定位於所有類下返回值任意、方法入參型別、數量任意,public型別的方法
  • execution(public String *Service(..))
    :定位於所有類下返回值為String、方法入參型別、數量任意,public型別的方法
  1. 通過類包定義切點:
  • execution(* com.yc.controller.BaseController+.*(..)):匹配任意返回型別,對應包下BaseController類及其子類等任意方法。
  • execution(* com.*.(..)):匹配任意返回型別,com包下所有類的所有方法
  • execution(* com..*.(..)):匹配任意返回型別,com包、子包下所有類的所有方法
    注意.表示該包下所有類,…則涵括其子包。
  1. 通過方法入參定義切點
  • 這裡“*”表示任意型別的一個引數,“…”表示任意型別任意數量的引數
  • execution(* speak(Integer,*)):匹配任意返回型別,所有類中只有兩個入參,第一個入參為Integer,第二個入參任意的方法
  • execution(* speak(..,Integer,..)):匹配任意返回型別,所有類中至少有一個Integer入參,但位置任意的方法。
  1. 常用切點表示式
  • execution(* com.yc.service..*.*(..))在配置service層的事務管理時常用,定位於任意返回型別(第一個”*”) 在com.yc.service包以及(“..”)子包下的(第一個“*”)所有類(第二個”*”)下的所有方法(第三個”*”),且這個方法的入參為任意型別、數量(體現在 “(..)“
    )
/**
 * Created by yangmin on 2020/10/26
 */
@Aspect
@Component
public class LoggerAop {
    private static Logger log = LoggerFactory.getLogger(LoggerAop.class);

    /**
     * 此處的切點是註解的方式
     * 只要出現 @LogAnnotation註解都會進入
     */
     

	/**
     * 此處的切點使用的是切點表示式
     * execution(* com.yc.service.*.*(..))在配置service層的事務管理時常用,定位於任意返回型別(第一個”*”) 在com.yc.service包下的所有類(第二個”*”)下的所有方法(第三個”*”),且這個方法的入參為任意型別、數量(體現在 “(..)“)
     * 
     */
    @Pointcut("execution(public * com.study.studyaop.service..*.*(..))")//切入點描述,這個是service包的切入點
    public void logPointCut() {
    }

    /**
     * 環繞增強,相當於MethodInterceptor
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("==============================================start==================================================");
        Object result=null;
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //獲取類名
        String className = joinPoint.getTarget().getClass().getName();
        //獲取方法名
        String methodName = signature.getName();
        long beginTime = System.currentTimeMillis();
        //獲取方法引數
        Object[] args = joinPoint.getArgs();
        //在引數中去除Request或者Response物件,(joinPoint.getArgs()返回的陣列中攜帶有Request或者Response物件,導致序列化異常。)
        Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
        List<Object> logArgs = stream
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        String params = null;
        if (args.length != 0) {
            params = JSON.toJSONString(logArgs);
        }
        HttpServletRequest request = HttpContextUtil.getRequest();
        //獲取請求方法的型別:get 或則post
        String method = request.getMethod();
        //獲取請求路勁
        String url = request.getRequestURL().toString();
        //獲取ip
        String ip = IpUtils.getIP(request);
        log.info("請求的路徑為     : {}",url);
        log.info("請求ip           : {}",ip);
        log.info("方法請求類名     : {}",className);
        log.info("方法名           : {}",methodName);
        log.info("方法型別         : {}",method);
        log.info("請求引數         : {}",params);
        //執行方法
        try {
             result = joinPoint.proceed();
        } catch (Exception e) {
            log.error("異常資訊         : {}",e.getMessage());
            e.printStackTrace();
        }finally {
            //執行時長(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            log.info("執行時間         : {}",time);
            log.info("==============================================end==================================================");
            return result;
        }
    }

/**
獲取request的方法
*/
 public HttpServletRequest getRequest() {
        return  ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
    }

/**
     * 獲取IP地址的方法
     * @return
     */
    public  String getIpAddress() {
        HttpServletRequest request = getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

基於註解實現
annotation 此註解用於定位標註了某個註解的目標切點。下面我們來看一個模擬使用者登入成功後日志記錄的示例

1.自定義註解

/**
 * 日誌註解
 *
 * @author lastwhisper
 */
@Target(ElementType.METHOD) // 方法註解
@Retention(RetentionPolicy.RUNTIME) // 執行時可見
public @interface LogAnno {
    String operateType();// 記錄日誌的操作型別
    int type();
}
  1. 目標方法
    /**
     *登入方法
     * @param request
     * @return
     * @throws Exception
     */
    @LogAnno(operateType = "登入",type=10)
    @RequestMapping(value = "/tologin", method = RequestMethod.POST)
    public String login(@RequestBody Map<String,String> map , HttpServletRequest request, HttpServletResponse response) throws Exception {
        String username = map.get("username");
        String password = map.get("password");
        String code = map.get("code");
        LoginResult loginResult = loginService.login(code,username, password,request,response);
        String s = JsonUtils.toString(loginResult);
        return s;
    }

3.增強

@Order(3)//優先順序
@Component
@Aspect
public class LogAopAspect {

    @Resource
    private PermissApiClient permissApiClient;

    @Around("@annotation(com.woyaoce.core.annotation.LogAnno)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        // 1.方法執行前的處理,相當於前置通知
        // 獲取方法簽名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 獲取方法
        Method method = methodSignature.getMethod();
        // 獲取方法上面的註解
        LogAnno logAnno = method.getAnnotation(LogAnno.class);
        // 獲取操作描述的屬性值
        String operateType = logAnno.operateType();
        int type = logAnno.type();
        // 建立一個日誌物件(準備記錄日誌)
        CmsLog cmsLog = new CmsLog();
        cmsLog.setType(type);
        String ip = HttpContextUtil.getIpAddress();
        cmsLog.setIp(ip);
        Object result = null;
        try {
            // 讓代理方法執行
            result = pjp.proceed();
            // 2.相當於後置通知(方法成功執行之後走這裡)
            cmsLog.setStatus(0);
            cmsLog.setLogMsg(operateType);// 設定操作結果
        } catch (Exception e) {
            // 3.相當於異常通知部分
            cmsLog.setStatus(1);
            cmsLog.setLogMsg(operateType);// 設定操作結果
        } finally {
            // 設定操作人,從session中獲取,
            Subject LoginUser = SecurityUtils.getSubject();
            Session session = LoginUser.getSession();
            String userName =(String) session.getAttribute("userName");
            cmsLog.setLoginName(userName);
            // 4.相當於最終通知
            //cmsLog.setCreateDate(new Date());// 設定操作日期
           // 新增日誌記錄
            permissApiClient.addLog(cmsLog);
        }
        return result;
    }
}