spring自己实现注解(自定义注解方法名)

本篇博客将从一个普通的spring项目入手,教你如何在项目中应用自定义注解

大家好,又见面了,我是你们的朋友全栈君。

1. 准备工作

首先这里创建了一个简单的springboot项目:
包结构
各个类的内容如下所示:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User { 
   

    private Integer id;

    private String name;

}
@Component
public class UserDao { 
   

    public User findUserById(Integer id) { 
   
        if(id > 10) { 
   
            return null;
        }
        return new User(id, "user-" + id);
    }
}
@Service
public class UserService { 
   

    private final UserDao userDao;

    public UserService(UserDao userDao) { 
   
        this.userDao = userDao;
    }

    public User findUserById(Integer id) { 
   
        return userDao.findUserById(id);
    }
}

@RestController
public class UserController { 
   

    private final UserService userService;

    public UserController(UserService userService) { 
   
        this.userService = userService;
    }

    @RequestMapping("user/{id}")
    public User findUser(@PathVariable("id") Integer id) { 
   
        return userService.findUserById(id);
    }
}

2. 使用注解执行固定的操作

现在我们已经有了这样的一个简单的web项目了,直接访问localhost:8080/user/6后,显然会得到一个如下的json串

{ 
   
  "id": 6,
  "name": "user-6"
}

但是我们不满足于此,这个项目也未免太简陋了,现在我们就来为它增加一个日志的功能(不要说使用log4j等日志框架,我们的目的是学习自定义注解)

假设我们现在的目的是,在调用controller中的findUser方法前,先在控制台输出一句话。好了那就开始做吧,我们先创建一个annotation包,里面创建我们自定义的注解类KthLog

package com.example.demo.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface KthLog { 
   

    String value() default "";
}

这里注解类上的三个注解称为元注解,其分别代表的含义如下:

  • @Documented:注解信息会被添加到Java文档中
  • @Retention:注解的生命周期,表示注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运行阶段
  • @Target:注解作用的位置,ElementType.METHOD表示该注解仅能作用于方法上

然后我们可以把注解添加到方法上:

    @KthLog("这是日志内容")
    @RequestMapping("user/{id}")
    public User findUser(@PathVariable("id") Integer id) { 
   
        return userService.findUserById(id);
    }

这个注解目前是没有任何作用的,因为我们仅仅是对注解进行了声明,并没有在任何地方来使用这个注解,注解的本质也是一种广义的语法糖,最终还是要利用Java的反射来进行操作

不过Java给我们提供了一个AOP机制,可以对类或方法进行动态的扩展,想较深入地了解这一机制的可以参考我的这篇文章:从源码解读Spring的AOP

我们创建切面类,如下:

@Component
@Aspect
public class KthLogAspect { 
   
    
    @Pointcut("@annotation(com.example.demo.annotation.KthLog)")
    private void pointcut() { 
   }
    
    @Before("pointcut() && @annotation(logger)")
    public void advice(KthLog logger) { 
   
        System.out.println("--- Kth日志的内容为[" + logger.value() + "] ---");
    }
}

其中@Pointcut声明了切点(这里的切点是我们自定义的注解类),@Before声明了通知内容,在具体的通知中,我们通过@annotation(logger)拿到了自定义的注解对象,所以就能够获取我们在使用注解时赋予的值了。这里如果对于切点和通知等概念不了解的,建议先去查阅一些aop的知识再回来看本文较好,本文更注重于实践,而不是概念的讲解

然后我们现在再来启动web服务,在浏览器上输入localhost:8080/user/6(使用JUnit单元测试也可以),会发现控制台成功输出:图1

3. 使用注解获取更详细的信息

刚才我们使用自定义注解实现了在方法调用前输出一句日志,但是我们并不知道这是哪个方法、哪个类输出的,如果有两个方法都加上了这个注解,且value的值都一样,那我们该怎么区分这两个方法呢?比如现在我们给UserController类中添加了一个方法:

@RestController
public class UserController { 
   

    private final UserService userService;

    public UserController(UserService userService) { 
   
        this.userService = userService;
    }

    @KthLog("这是日志内容")
    @RequestMapping("user/{id}")
    public User findUser(@PathVariable("id") Integer id) { 
   
        return userService.findUserById(id);
    }

    @KthLog("这是日志内容")
    @RequestMapping("compared")
    public void comparedMethod() { 
    }
}

如果我们调用comparedMethod()方法,显然会得到和刚才一样的输出结果,这时候我们就需要对注解做进一步改造,其实很简单,只需要在切面类的advice()方法中添加一个JoinPoint参数即可,如下:

    @Before("pointcut() && @annotation(logger)")
    public void advice(JoinPoint joinPoint, KthLog logger) { 
   
        System.out.println("注解作用的方法名: " + joinPoint.getSignature().getName());
        
        System.out.println("所在类的简单类名: " + joinPoint.getSignature().getDeclaringType().getSimpleName());
        
        System.out.println("所在类的完整类名: " + joinPoint.getSignature().getDeclaringType());
        
        System.out.println("目标方法的声明类型: " + Modifier.toString(joinPoint.getSignature().getModifiers()));
    }

然后我们再来执行一遍刚才的流程,看看会输出什么结果:
图2
现在我们再将这些内容放到日志中,顺便修改一下日志的格式,如下:

    @Before("pointcut() && @annotation(logger)")
    public void advice(JoinPoint joinPoint, KthLog logger) { 
   
        System.out.println("[" 
                + joinPoint.getSignature().getDeclaringType().getSimpleName()
                + "][" + joinPoint.getSignature().getName() 
                + "]-日志内容-[" + logger.value() + "]");
    }

图3

4. 使用注解修改参数和返回值

我们把之前添加的compare()方法删去,现在我们的注解需要对方法的参数作出修改,以findUser()方法为例,假设我们传入的用户id是从1开始计数,后端则是从0开始计数,我们的@KthLog注解的开发者喜欢“多管闲事”,想要帮助其他人减轻一点压力,那该怎么做呢?

在这个应用场景中,我们需要做的有两件事:将传入的id减1,给返回的user类中的id加1。这就涉及到如何拿到参数的问题。因为我们需要管理方法执行前和执行后的操作,所以我们使用@Around环绕注解,如下:

    @Around("pointcut() && @annotation(logger)")
    public Object advice(ProceedingJoinPoint joinPoint, KthLog logger) { 
   
        System.out.println("["
                + joinPoint.getSignature().getDeclaringType().getSimpleName()
                + "][" + joinPoint.getSignature().getName()
                + "]-日志内容-[" + logger.value() + "]");
        
        Object result = null;
        
        try { 
   
            result = joinPoint.proceed();
        } catch (Throwable throwable) { 
   
            throwable.printStackTrace();
        }
        
        return result;
    }

这里除了将@Before改为@Around之外,还将参数中的JoinPoint改为了ProceedingJoinPoint,不过不用担心,JoinPoint能做的ProceedingJoinPoint都能做。这里通过调用proceed()方法,执行了实际的操作,并获取到了返回值,那么接下来对于返回值的操作相信就不用我再多说了,现在问题就是如何获取到参数

ProceedingJoinPoint继承了JoinPoint接口,在JoinPoint中,存在一个getArgs()方法,用于获取方法参数,返回的是一个Object数组,与之匹配的则是proceed(args)方法,这两个方法结合起来,就能够实现我们的目的:

    @Around("pointcut() && @annotation(logger)")
    public Object advice(ProceedingJoinPoint joinPoint, KthLog logger) { 
   
        System.out.println("["
                + joinPoint.getSignature().getDeclaringType().getSimpleName()
                + "][" + joinPoint.getSignature().getName()
                + "]-日志内容-[" + logger.value() + "]");

        Object result = null;

        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) { 
   
            if(args[i] instanceof Integer) { 
   
                args[i] = (Integer)args[i] - 1;
                break;
            }
        }

        try { 
   
            result = joinPoint.proceed(args);
        } catch (Throwable throwable) { 
   
            throwable.printStackTrace();
        }

        if(result instanceof User) { 
   
            User user = (User) result;
            user.setId(user.getId() + 1);
            return user;
        }
        return result;
    }

这里为了代码的鲁棒性做了两次参数类型校验,接着我们重新执行之前的测试,这里为了让结果更明显,我们在UserDao处添加一些输出,来显示实际执行的参数和返回的值各自是什么:

@Component
public class UserDao { 
   

    public User findUserById(Integer id) { 
   
        System.out.println("查询id为[" + id + "]的用户");
        if(id > 10) { 
   
            return null;
        }
        User user = new User(id, "user-" + id);
        System.out.println("返回的用户为[" + user.toString() + "]");
        return user;
    }
}

现在我们访问http://localhost:8080/user/6,来看控制台打印的结果:
图4
我们发现在url上输入的6,在后端被转换成了5,最终查询的用户也是id为5的用户,说明我们参数转换成功了,然后我们来看浏览器得到的响应结果:
图5
返回的用户id是6,而不是后端查询的5,说明我们对返回值的修改也成功了

5. 总结

在Web项目(这里特指Spring项目)中使用自定义注解开发,其原理还是依赖于Spring的AOP机制,这一点就与我们普通的Java项目有所区别。当然,如果是开发其他框架而需要使用自定义注解时,则需要自己实现一套机制,不过原理本质上都是大同小异,无非是将一些模板操作进行了封装

通过自定义的注解,我们不仅能够在方法执行前后进行扩展,同时还可以获取到作用方法的方法名,所在类等信息,更重要的是还能够修改参数和返回值,这几点应用下来基本就囊括了绝大部分自定义注解的功能。了解到这里,完全就能够自己动手来写一个自定义注解来简化我们的项目

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/129035.html原文链接:https://javaforall.net

(0)
上一篇 2022年4月13日 上午9:20
下一篇 2022年4月13日 上午9:20


相关推荐

  • pycharm+anaconda安装教程_pycharm和anaconda结合安装

    pycharm+anaconda安装教程_pycharm和anaconda结合安装PyCharm的安装一.下载安装包1.安装网址点击进入PyCharm官网2.在导航栏输入网址回车3.点击DOWNLOAD。4.下载它有专业版和社区版,我们下载社区版就可以使用了。(专业版要收费)二.安装过程5.双击安装包。6.点击next7.安装位置它会有一个默认的安装位置,一般默认C盘,但我们可以点击Browse…,安装我们想要安装的位置,我安装在了D盘。如下图:8.安装选择1:createdesktops…

    2022年8月27日
    4
  • 免費AI編輯工具升級!Nano Banana正名為Gemini 2.5 Flash Image 功能與玩法一次看

    免費AI編輯工具升級!Nano Banana正名為Gemini 2.5 Flash Image 功能與玩法一次看

    2026年3月16日
    4
  • idea2021.7激活码【永久激活】「建议收藏」

    (idea2021.7激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年3月21日
    143
  • 【数学 裴蜀定理】luogu_4549 裴蜀定理

    【数学 裴蜀定理】luogu_4549 裴蜀定理题意给出 nnn 个数 AAA 求一组整数 XXX 使 X1A1 XnAnX 1A 1 X nA nX1 A1 Xn An 最小 求这个最小值 思路裴蜀定理 关于不定方程 ax by cax by cax by c 有整数解的充要条件是 gcd a b cgcd a b cgcd a b c 对于多个数 我们可以发现 ccc 的值为 那么

    2026年3月17日
    2
  • 杭州的旅游景点与民间传说故事「建议收藏」

    杭州的旅游景点与民间传说故事「建议收藏」 杭州的旅游景点与民间传说故事虎跑梦泉虎跑泉是一处以“泉”为主题的观泉、听泉、品泉、试泉的泉源景观,又是以性空、济公、弘一法师传奇故事为特色的人文景观,坐落位于西湖之南的大慈山定慧禅寺内。民间传说唐代性空大师游历此山,这里风景优美,只是无水源,决定去别处,忽然有神人告诉他即将有二只老虎会来挖泉,翌日,果然有二虎跑山出泉,甘冽醇厚,纯净无菌,从此“龙井茶叶虎跑泉”被称为“西湖双绝”。

    2022年5月31日
    37
  • Java 取余 (remain),取模 (mod) 的 区别和运算

    Java 取余 (remain),取模 (mod) 的 区别和运算Java取余(remain),取模(mod)的区别和运算范围区别:取模主要是用于计算机术语中。取余则更多是数学概念。主要的区别在于对负整数进行除法运算时操作不同那么具体是怎样的不同?首先需要知道Java中如何取模:Java中用符号%对数字进行取模,可以得到以下:System.out.println(5%3);System.out.println(-5%3);Sys…

    2022年6月3日
    120

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号