使用AOP实现对接口的实时监控

在这里插入图片描述

📫 作者简介:「子非我鱼」,专注于研究全栈
🔥 三连支持:欢迎 ❤️关注、👍点赞、👉收藏三连,支持一下博主~

引言

在 Java 开发中,AOP(面向切面编程)是一种强大的编程范式,它允许我们在程序运行时动态地横切应用的关注点。本文将介绍如何使用 AOP 实现对 Java 接口的实时监控,以便了解接口方法的执行情况。

步骤一:创建日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnno {

    /**
     * 日志名称
     */
    String description() default "";


    /**
     * 记录日志的操作类型
     */
    String type();
}
步骤二:自定义线程连接池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author myqxin
 */
public class ThreadPoolUtil {

    /**
     * 线程缓冲队列
     */
    private static BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(100);
    /**
     * 核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量
     */
    private static final int SIZE_CORE_POOL = 5;
    /**
     * 线程池维护线程的最大数量
     */
    private static final int SIZE_MAX_POOL = 10;
    /**
     * 线程池维护线程所允许的空闲时间
     */
    private static final long ALIVE_TIME = 2000;

    private static ThreadPoolExecutor pool = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, ALIVE_TIME, TimeUnit.MILLISECONDS, bqueue, new ThreadPoolExecutor.CallerRunsPolicy());

    static {

        pool.prestartAllCoreThreads();
    }

    public static ThreadPoolExecutor getPool() {
        return pool;
    }

    public static void main(String[] args) {
        System.out.println(pool.getPoolSize());
    }
}
步骤三:创建日志类
package com.myqxin.sx.modules.base.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

import java.io.Serializable;
import java.util.Map;

import com.myqxin.sx.common.utils.ObjectUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 *
 * </p>
 *
 * @author myqxin
 * @since 2021-08-06
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sx_log")
@Builder
@AllArgsConstructor//创建全参的构造方法,配合@Builder
@ApiModel(value = "SxLog对象", description = "操作日志实体类")
public class SxLog implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    public SxLog() {

    }

    @ApiModelProperty(value = "操作人")
    private String operateor;

    @ApiModelProperty(value = "操作类型")
    private String operateType;

    @ApiModelProperty(value = "操作日期")
    private String operateDate;


    @ApiModelProperty(value = "方法操作名称")
    private String name;

    @ApiModelProperty(value = "请求路径")
    private String requestUrl;

    @ApiModelProperty(value = "请求类型")
    private String requestType;

    @ApiModelProperty(value = "请求参数")
    private String requestParam;

    @ApiModelProperty(value = "请求结果")
    private String requestBody;

    @ApiModelProperty(value = "ip")
    private String ip;

    @ApiModelProperty(value = "ip信息")
    private String ipInfo;

    @ApiModelProperty(value = "花费时间")
    private Integer costTime;

    /**
     * 转换请求参数为Json
     *
     * @param paramMap
     */
    public void setMapToParams(Map<String, String[]> paramMap) {
        // TODO 实现map转json字符串
        this.requestParam = ObjectUtil.mapToString(paramMap);
    }
}

步骤四:创建日志类Mapper
import com.myqxin.sx.modules.base.entity.SxLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author myqxin
 * @since 2021-08-06
 */
public interface SxLogMapper extends BaseMapper<SxLog> {

}

步骤五:创建日志service接口及实现类
public interface SxLogService extends IService<SxLog> {

    public boolean addLog(SxLog sxLog);

}
@Service
public class SxLogServiceImpl extends ServiceImpl<SxLogMapper, SxLog> implements SxLogService {

    @Override
    public boolean addLog(SxLog sxLog) {
        return super.save(sxLog);
    }
}
步骤六:创建日志切面类
package com.myqxin.sx.common.aop;

import com.myqxin.sx.common.annotation.LogAnno;
import com.myqxin.sx.common.utils.IpInfoUtil;
import com.myqxin.sx.common.utils.ThreadPoolUtil;
import com.myqxin.sx.modules.base.entity.SxLog;
import com.myqxin.sx.modules.base.service.mybatis.SxLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: myqxin
 * @Desc:
 * @create: 2021-08-06 15:11
 **/
@Aspect
@Component
@Slf4j
public class LogAopAspect {

    private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");

    @Autowired
    private SxLogService sxLogService;

    @Autowired(required = false)
    private HttpServletRequest request;

    @Autowired
    private IpInfoUtil ipInfoUtil;

    /**
     * Controller层切点,注解方式
     */
    //@Pointcut("execution(* *..controller..*Controller*.*(..))")
    @Pointcut("@annotation(com.myqxin.sx.common.annotation.LogAnno)")
    public void controllerAspect() {

    }

    /**
     * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
     *
     * @param joinPoint 切点
     * @throws InterruptedException
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException {

        //线程绑定变量(该数据只有当前请求的线程可见)
        Date beginTime = new Date();
        beginTimeThreadLocal.set(beginTime);
    }

    /**
     * 后置通知(在方法执行之后返回) 用于拦截Controller层操作
     * 此注解能对返回值作处理
     * @param joinPoint 切点
     */
   // @AfterReturning(value = "controllerAspect()", returning = "result")
   // public void after(JoinPoint joinPoint, Object result) {}


    /**
     * 后置通知(在方法执行之后返回) 用于拦截Controller层操作
     *
     * @param joinPoint 切点
     */
    @After("controllerAspect()")
    public void after(JoinPoint joinPoint) {
        try {
            Object[] objs = joinPoint.getArgs();
            if (objs != null && objs.length > 0) {
                for (Object object : objs) {
                    if (object instanceof HttpServletRequest) {
                        request = (HttpServletRequest) object;
                        break;
                    }
                }
            }

            SxLog log = new SxLog();
            // 创建日期
            LocalDateTime now = LocalDateTime.now();
            String format = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss"));
            log.setOperateDate(format);
            //日志标题
            log.setName(getControllerMethodInfo(joinPoint).get("description").toString());
            //操作类型
            log.setOperateType(getControllerMethodInfo(joinPoint).get("type").toString());
            //日志请求url
            log.setRequestUrl(request.getRequestURI());
            //请求方式
            log.setRequestType(request.getMethod());
            //请求参数
            Map<String, String[]> logParams = request.getParameterMap();
            log.setMapToParams(logParams);
            //请求用户
            log.setOperateor("myqxin");
            //请求IP
            log.setIp(ipInfoUtil.getIpAddr(request));
            //请求开始时间
            Date logStartTime = beginTimeThreadLocal.get();
            long beginTime = beginTimeThreadLocal.get().getTime();
            long endTime = System.currentTimeMillis();
            //请求耗时
            Long logElapsedTime = endTime - beginTime;
            log.setCostTime(logElapsedTime.intValue());

            //调用线程保存至ES
            ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(log, sxLogService));

        } catch (Exception e) {
            log.error("AOP后置通知异常", e);
        }
    }

    /**
     * 保存日志至数据库
     */
    private static class SaveSystemLogThread implements Runnable {

        private SxLog sxLog;
        private SxLogService sxLogService;

        public SaveSystemLogThread(SxLog sxLog, SxLogService logService) {
            this.sxLog = sxLog;
            this.sxLogService = logService;
        }

        @Override
        public void run() {
            sxLogService.addLog(sxLog);
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static Map<String, Object> getControllerMethodInfo(JoinPoint joinPoint) throws Exception {

        Map<String, Object> map = new HashMap<String, Object>(16);
        //获取目标类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取相关参数
        Object[] arguments = joinPoint.getArgs();
        //生成类对象
        Class targetClass = Class.forName(targetName);
        //获取该类中的方法
        Method[] methods = targetClass.getMethods();

        String description = "";
        String type = null;

        for (Method method : methods) {
            if (!method.getName().equals(methodName)) {
                continue;
            }
            Class[] clazzs = method.getParameterTypes();
            if (clazzs.length != arguments.length) {
                //比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载哦
                continue;
            }
            description = method.getAnnotation(LogAnno.class).description();
            type = method.getAnnotation(LogAnno.class).type();
            map.put("description", description);
            map.put("type", type);
        }
        return map;
    }
}

步骤七:创建获取ip工具类
package com.myqxin.sx.common.utils;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;


/**
 * @author myqxin
 */
@Slf4j
@Component
public class IpInfoUtil {

    /**
     * 获取客户端IP地址
     *
     * @param request 请求
     * @return
     */
    public String getIpAddr(HttpServletRequest request) {

        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.getRemoteAddr();
            if (ip.equals("127.0.0.1")) {
                //根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ip = inet.getHostAddress();
            }
        }
        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ip != null && ip.length() > 15) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        if ("0:0:0:0:0:0:0:1".equals(ip)) {
            ip = "127.0.0.1";
        }
        return ip;
    }

}

步骤八:创建格式转化工具类
package com.myqxin.sx.common.utils;

import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;

import java.util.HashMap;
import java.util.Map;

/**
 * @author myqxin
 */
public class ObjectUtil {

    public static String mapToString(Map<String, String[]> paramMap){

        if (paramMap == null) {
            return "";
        }
        Map<String, Object> params = new HashMap<>(16);
        for (Map.Entry<String, String[]> param : paramMap.entrySet()) {

            String key = param.getKey();
            String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : "");
            String obj = StrUtil.endWithIgnoreCase(param.getKey(), "password") ? "你是看不见我的" : paramValue;
            params.put(key,obj);
        }
        return new Gson().toJson(params);
    }

    public static String mapToStringAll(Map<String, String[]> paramMap){

        if (paramMap == null) {
            return "";
        }
        Map<String, Object> params = new HashMap<>(16);
        for (Map.Entry<String, String[]> param : paramMap.entrySet()) {

            String key = param.getKey();
            String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : "");
            params.put(key, paramValue);
        }
        return new Gson().toJson(params);
    }
}

步骤九:使用注解
    @GetMapping("/register")
    @LogAnno(description = "注册接口",type = "注册")
    public BaseResult register(@RequestParam int[] arr,@RequestParam int aa) {
        System.err.println(arr);
        return BaseResult.ok("注册成功");
    }
步骤十:实现效果

在这里插入图片描述

结论

通过使用 AOP,我们能够轻松实现对接口方法的实时监控,而无需修改接口实现类的代码。这种方式使得我们可以在不影响原有逻辑的情况下,加入一些横切关注点,例如性能监控、日志记录等。 AOP 是一个强大的编程范式,能够提高代码的模块化和可维护性。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
对于Python接口监控,可以使用Python编写一个监控程序来实现。根据引用\[1\]中的描述,可以通过监控第三方接口调用过程中是否出现大量错误来判断接口是否存在问题,并及时采取措施。 在Python中,可以使用一些库和工具来实现接口监控。例如,可以使用Python的requests库来发送HTTP请求并获取接口的响应。通过监控接口的响应状态码和返回数据,可以判断接口是否正常工作。 此外,还可以使用Python的日志库来记录接口调用情况和错误信息。通过查看日志文件,可以及时发现接口调用中的异常情况,并进行分析和处理。 另外,引用\[2\]中提到的Django框架也可以用于接口监控。Django是一个强大的Python Web框架,可以用于构建Web应用程序。通过Django的管理界面,可以方便地查看和管理接口调用情况。 总之,通过编写Python监控程序,结合使用相关库和工具,可以实现对接口实时监控和错误处理,以提高接口的可靠性和稳定性。 #### 引用[.reference_title] - *1* [使用Python写一个小小的项目监控](https://blog.csdn.net/zhongyi_yang/article/details/46122073)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [如何用Python搭建监控平台](https://blog.csdn.net/qq_35030548/article/details/131425745)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子非我鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值