• 自定义注解(切面实现)


    自定义注解(切面实现)

    最近有一个新的功能要开发:记录http请求的耗时。

    我心想:ok,这个需求很简单啊,不就是上下都记录下时间就完事了吗
    伪代码

    long startTimeMillis = System.currentTimeMillis();
    httpService.request(); // 核心代码
    long endTimeMillis = System.currentTimeMillis();
    long spendMillis = endTimeMillis - startTimeMillis; // 耗时
    
    • 1
    • 2
    • 3
    • 4

    提交代码,搞定一个任务😁

    随着时间发展,有越来越多的系统要对接,每个接口的http请求都要写一撮low low的代码,自己看了怪恶心的🤮

    于是我另起炉灶,把埋点的方式抛弃掉,使用spring切面的方式去实现,切面外行人听着可能觉得挺高级的,咱来弱化一下对他的理解。

    在这里插入图片描述

    切面,可以方法执行前or执行后做一些特殊处理。

    这样就好办了,我只需要在切面-前置记录开始时间,切面-后置记录结束时间,然后两者相减就完成需求了。

    举个例子,定义一下http请求的原方法

    @Service
    public class HttpService{
        public void request(){
            //核心代码...发起http请求
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    定义一个HttpServiceAspect切面类

    @Aspect
    @Component
    public class HttpServiceAspect {
        /**
         * 声明AOP签名
         * 这里我指定HttpService.request执行时,会被我捕获到
         */
        @Pointcut("execution(* com.whale.data.service.impl.HttpService.request(..))")
        public void pointcut() {
        }
    
        /**
         * 环绕切入
         *
         * @param joinPoint 切面对象
         * @return 底层方法执行后的返回值
         * @throws Throwable 底层方法抛出的异常
         */
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            long startTimeMillis = System.currentTimeMillis();
            proceed = joinPoint.proceed(); // 执行原方法
            long endTimeMillis = System.currentTimeMillis();
            long spendMillis = endTimeMillis - startTimeMillis; // 耗时
            return proceed;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    搞定!任务调用到com.whale.data.service.impl.HttpService.request()的方法都会被我的切面拦截,这样我就不需要在每个系统交互上下去埋点了。(可以开心一会😄)

    快乐的日子总是短暂的,成年人的世界里总是需要经历一定的坎坷。这不,新的问题又来了。

    假如开发人员新增了一个接口httpService.post(),那上述方式切面的方式就不够用了,我还得在Pointcut里指定新的切点
    如:

        @Pointcut("execution(* com.whale.data.service.impl.HttpService.request(..)) || execution(* com.whale.data.service.impl.HttpService.post(..)) ")
        public void pointcut() {
        }
    
    • 1
    • 2
    • 3

    切面也长大了,要学会自己去管理切点才行呀!

    如果接口耗时功能的入口不在切面,而在方法上,那开发人员就可以根据自己的需要,自己选择是否启用了,这样的动态配置,谁不喜欢呢?

    于是乎,我们又进行了稍许改造,引入了自定义注解
    跟之前切面的案例一样,我们也进行一次降维打击哈

    原流程
    在这里插入图片描述

    加入注解,启动切面功能
    在这里插入图片描述

    ok,看完上图大家对注解应该有个初步的认知,通过注解去实现切面的开关

    同样我们上一段代码来看看程序是如何实现的。

    定义自定义注解HttpLog

    @Target(ElementType.METHOD) // 用于方法上
    @Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
    public @interface HttpLog {
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    改造切面HttpServiceAspect的切点,使其切入注解

    @Aspect
    @Component
    public class HttpServiceAspect {
        /**
         * 声明AOP签名
         * 这里我指定HttpService.request执行时,会被我捕获到
         */
        @Pointcut("@annotation(com.whale.data.annotate.HttpLog)")
        public void pointcut() {
        }
    
        /**
         * 环绕切入
         *
         * @param joinPoint 切面对象
         * @return 底层方法执行后的返回值
         * @throws Throwable 底层方法抛出的异常
         */
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            long startTimeMillis = System.currentTimeMillis();
            proceed = joinPoint.proceed(); // 执行原方法
            long endTimeMillis = System.currentTimeMillis();
            long spendMillis = endTimeMillis - startTimeMillis; // 耗时
            return proceed;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    以上的满足就已经完成自定义注解了。 接下来看看注解怎么使用

    只需要在方法上加入注解@HttpLog即可

    @Service
    public class HttpService{
        @HttpLog
        public void request(){
            //核心代码...发起http请求
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    举一反三,如果开发新增了post方法也需要统计耗时,那么在post方法上也加上对应的注解即可

    @Service
    public class HttpService{
        @HttpLog
        public void request(){
            //核心代码...发起http请求
        }
        
        @HttpLog
        public void post(){
            //核心代码...发起http请求
        }
        
        public void get(){
            //核心代码...发起http请求。不使用注解,不会被切面拦截
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    通过上面描述的,大家应该可以知道切面的由来,以及切面和注解的关系了。
    切面:无需侵入业务代码,在外层进行特殊的逻辑处理,常用于日志,监控记录。
    注解:切面的高级用法

  • 相关阅读:
    避免数据泄露风险!NineData提供SQL开发规范和用户访问量管理
    DevOps|1024程序员节怎么做?介绍下我的思路
    【Linux】VMware
    C语言程序设计核心详解 第四章&&第五章 选择结构程序设计&&循环结构程序设计
    el-table中合并表头的同时,合并列固定(解决办法)+表头合并受fixed的影响合并不成功(解决办法)
    基于Java毕业设计东理咨询交流论坛源码+系统+mysql+lw文档+部署软件
    【数据结构】栈
    Maven
    AI准研究生应该掌握的Linux知识
    C/C++内存管理相关知识点
  • 原文地址:https://blog.csdn.net/weixin_31257709/article/details/127799874