一看就会的使用AOP记录日志

如果大家不想看知识点,可以点击目录直接跳转到操作哦,阿里嘎多大家~

一 AOP的含义简介

在开始正式讲解之前,大家一定要明确AOP的具体含义是什么

A : Alone
O : OverWatch
P : Play
AOP的含义呢,大概就是独自(Alone)游玩(Play)守望先锋(OverWatch)这款游戏,众所周知《守望先锋》(Overwatch,简称OW) 是由暴雪娱乐公司开发的一款第一人称射击游戏,于2016年5月24日全球上市,中国大陆地区由网易公司代理......

好了好了,不和大家开玩笑了,AOP的真正含义是Aspect Oriented Programming,意思是面向切面编程,和他对应的是OOP(面向对象程序设计)。大家肯定都对OOP了解的很透彻,毕竟面试热门话题封装继承多态,那么既然已经有了OOP,为什么还要有一个AOP呢?别急,听我慢慢道来~

在以往的开发过程中,如果想要通过OOP去实现代码的重用,那么需要通过组合和继承,同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。

AOP的思想就是采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充

Ps : 重点来啦,面试又可以吹牛逼啦,AOP并不是OOP的替代品,只是OOP的延申和补充。

在AOP中,类与切面的关系如下:
在这里插入图片描述
从图上可以看出通过Aspect(切面)分别在Class1和Class2的方法中加入了事务、日志、权限和异常等通用的功能。

二 AOP实现简单接口调用记录日志

准备工作

1 建立相关数据表及实体类等

UserInfo表:

CREATE TABLE `user_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '姓名',
  `name` varchar(255) DEFAULT NULL,
  `phone` varchar(255) DEFAULT NULL COMMENT '电话号码',
  `sex` varchar(255) DEFAULT NULL COMMENT '性别',
  `idcard` varchar(255) DEFAULT NULL COMMENT '身份证',
  `type` varchar(50) DEFAULT NULL COMMENT '1 用户 2 管理员',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='用户表';

LogInfo(日志记录)表:

CREATE TABLE `log_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '姓名',
  `msg` varchar(50) DEFAULT NULL COMMENT '信息',
  `ip` varchar(50) DEFAULT NULL COMMENT 'ip地址',
  `param` varchar(100) DEFAULT NULL COMMENT '参数信息',
  `call_time` varchar(50) DEFAULT NULL COMMENT '调用时间',
  `type` varchar(50) DEFAULT NULL COMMENT '调用类型',
  `method` varchar(50) DEFAULT NULL COMMENT '调用方法',
  `url` varchar(60) DEFAULT NULL COMMENT '调用接口URL',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1522478698815574018 DEFAULT CHARSET=utf8 COMMENT='简单日志表';

建好表记得去完善实体类Dao层ServiceServiceImpl等等哦(别偷懒!很重要!)

在这里插入图片描述

2 引入相关的Jar包
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

开始操作!

1 首先我们需要写一个自定义注解

在这里插入图片描述自定义注解里面你可以自己定义信息比如我写的调用终端,他是一个枚举,具体如何使用看步骤2

2 在你需要的接口上添加此自定义注解

在这里插入图片描述在接口上,可以看到自定义注解中的属性,这是你可以自己填写的具体接口是来自哪个模块,是做什么的,是通过什么方式(如步骤1上的枚举 有manage管理端PC和mobile手机两种)

3 切面操作(划重点!!!很重要!!!)

因为代码太长,各位帅哥靓女可以copy下来看~

@Aspect
@Component
public class WebLogAspect {
    @Autowired(required = false)
    private LogInfoDao logInfoDao;
    /** 换行符 */
    private static final String LINE_SEPARATOR = System.lineSeparator();
    private final static Logger log = LoggerFactory.getLogger(WebLogAspect.class);
    /** 以自定义 @WebLog 注解为切点 */
    @Pointcut("@annotation(edu.etime.panda.Aspect.WebLog)")
    public void webLog(){
    }
    /**
     * 在切点之前织入
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable{
        LogInfo logInfo=new LogInfo();
        // 进入方法前 调用开始
        System.out.println("切面开始:=============== Strat ===================");
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取请求地址URL
        String servletPath = request.getServletPath();
        //ip地址
        String ipAddr = getIpAddr(request);
        // 获取 @WebLog 注解上的的描述信息
        List<String> listDesc = getAspectLogDescription(joinPoint);
        //描述信息
        String description=listDesc.get(0);
        //调用模块
        String businessType=listDesc.get(1);
        //调用方式(终端)
        String invokingType=listDesc.get(2);
        //参数信息
        StringBuilder builder=new StringBuilder();
        builder.append(description+":"+joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
        Object obj= joinPoint.getArgs();
        String s = JSON.toJSONString(obj);
        //调用时间
        SimpleDateFormat simpleDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        logInfo.setCallTime(simpleDate.format(new Date()));
        logInfo.setUrl(servletPath);
        logInfo.setIp(ipAddr);
        logInfo.setMsg(description);
        logInfo.setType(businessType);
        logInfo.setMethod(invokingType);
        logInfo.setParam(s);
        logInfoDao.insert(logInfo);
    }
    /**
     * 在切点之后织入
     * @throws Throwable
     */
    @After("webLog()")
    public void doAfter() throws Throwable {
        // 接口结束后换行,方便分割查看
        System.out.println("切面结束:=========== End ================");
    }
    /**
     * 获取切面注解的描述
     * @param joinPoint 切点
     * @return 描述信息
     * @throws Exception
     */
    public List<String> getAspectLogDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        StringBuilder description = new StringBuilder("");
        StringBuilder businessType=new StringBuilder("");
        StringBuilder invokingType=new StringBuilder("");
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description.append(method.getAnnotation(WebLog.class).description());
                    businessType.append(method.getAnnotation(WebLog.class).businessType());
                    invokingType.append(method.getAnnotation(WebLog.class).invokingType());
                    break;
                }
            }
        }
        List<String> list=new ArrayList<>();
        list.add(description.toString());
        list.add(businessType.toString());
        list.add(invokingType.toString());
        return list;
    }
    public final static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        return ipAddress;
    }
}
4 结束!

是不是很快?当然 具体的讲解不可能这么快结束,我们运行这个程序调用一下接口~
我们先不打断点,正常调用一次查询用户的接口

在这里插入图片描述接口调用成功
在这里插入图片描述我们看一下数据库的日志表:
在这里插入图片描述这一条调用的信息已经存入了数据库。
下面我们来debugger一步一步看AOP是如何进行操作的:
在图片的代码里 可以看到我在很多地方打印输出了=======,我在每一个输出的地方都打上断点大家请看!

点击调用后的第一个输出断点
在这里插入图片描述在切点之前织入,进行日志保存在这里操作
第二个断点
在这里插入图片描述进入方法,在调用方法之前
第三个断点
在这里插入图片描述在调用方法结束后,进入。
第四个断点
在这里插入图片描述在切点织入之后
再看数据库
在这里插入图片描述

三 总结

其实我呢只是做了一个很简单的AOP记录日志的操作,很多地方实现的并不规范,重点是步骤3中的切面操作,大家可以放在自己的代码上一步一步debug看到是如何拿到参数的,重点就是切面操作,如果文章有疑问或问题,希望大家指出,我会立即修改,祝大家愉快~

敬礼!
salute!

猜你喜欢

转载自blog.csdn.net/weixin_49190101/article/details/124612004