谈谈 Java 中自定义注解及使用场景

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

每日英文

The happiest people are not those who have the best of everything, but who make the best out of everything.

最幸福的人不是拥有最好一切的人,而是把一切都变成最好的人。

每日掏心

一个人的气质,并不在容颜和身材,而是所有经历过的往事,在內心和外表留下的印迹,令人深沉而安谧。

来自:快给我饭吃 | 责编:乐乐

链接:jianshu.com/p/a7bedc771204

程序员小乐(ID:study_tech)第 980 次推文  图源:百度

往日回顾:大公司为什么都有API网关?聊聊API网关的作用

     

   正文   

Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP。


一. 什么是注解(Annotation)

Java注解是什么,以下是引用自维基百科的内容

Java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。

Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。


二. 注解体系图

元注解:java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。主要使用的是Target和Retention注解

注解处理类:既然上面定义了注解,那得有办法拿到我们定义的注解啊。java.lang.reflect.AnnotationElement接口则提供了该功能。注解的处理是通过java反射来处理的。

如下,反射相关的类Class, Method, Field都实现了AnnotationElement接口。

因此,只要我们通过反射拿到Class, Method, Field类,就能够通过getAnnotation(Class<T>)拿到我们想要的注解并取值。


三. 常用元注解

Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:

  • METHOD:用于描述方法

  • PACKAGE:用于描述包

  • PARAMETER:用于描述方法变量

  • TYPE:用于描述类、接口或enum类型

Retention: 表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为:

  • SOURCE:在源文件中有效,编译过程中会被忽略

  • CLASS:随源文件一起编译在class文件中,运行时忽略

  • RUNTIME:在运行时有效

只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。

所以,假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用。

@Target(ElementType.FIELD)  //  注解用于字段上
@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时,可通过注解获取
public @interface MyField {
    String description();
    int length();
}

四. 示例-反射获取注解

先定义一个注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
    String description();
    int length();
}

通过反射获取注解

public class MyFieldTest {

    //使用我们的自定义注解
    @MyField(description = "用户名", length = 12)
    private String username;

    @Test
    public void testMyField(){

        // 获取类模板
        Class c = MyFieldTest.class;

        // 获取所有字段
        for(Field f : c.getDeclaredFields()){
            // 判断这个字段是否有MyField注解
            if(f.isAnnotationPresent(MyField.class)){
                MyField annotation = f.getAnnotation(MyField.class);
                System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]");
            }
        }

    }
}

运行结果

应用场景一:自定义注解+拦截器 实现登录校验

接下来,我们使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。

首先定义一个LoginRequired注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
    
}

然后写两个简单的接口,访问sourceA,sourceB资源

@RestController
public class IndexController {

    @GetMapping("/sourceA")
    public String sourceA(){
        return "你正在访问sourceA资源";
    }

    @GetMapping("/sourceB")
    public String sourceB(){
        return "你正在访问sourceB资源";
    }

}

没添加拦截器之前成功访问

实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:

public class SourceAccessInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入拦截器了");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中

@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");
    }
}

拦截成功如下

在sourceB方法上添加我们的登录注解@LoginRequired

@RestController
public class IndexController {

    @GetMapping("/sourceA")
    public String sourceA(){
        return "你正在访问sourceA资源";
    }

    @LoginRequired
    @GetMapping("/sourceB")
    public String sourceB(){
        return "你正在访问sourceB资源";
    }

}

简单实现登录拦截逻辑

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入拦截器了");

        // 反射获取方法上的LoginRequred注解
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
        if(loginRequired == null){
            return true;
        }

        // 有LoginRequired注解说明需要登录,提示用户登录
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().print("你访问的资源需要登录");
        return false;
    }

运行成功,访问sourceB时需要登录了,访问sourceA则不用登录

应用场景二:自定义注解+AOP 实现日志打印

先导入切面需要的依赖包

<dependency>
      <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义一个注解@MyLog

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
    
}

定义一个切面类,见如下代码注释理解:

@Aspect // 1.表明这是一个切面类
@Component
public class MyLogAspect {

    // 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
    // 切面最主要的就是切点,所有的故事都围绕切点发生
    // logPointCut()代表切点名称
    @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")
    public void logPointCut(){};

    // 3. 环绕通知
    @Around("logPointCut()")
    public void logAround(ProceedingJoinPoint joinPoint){
        // 获取方法名称
        String methodName = joinPoint.getSignature().getName();
        // 获取入参
        Object[] param = joinPoint.getArgs();

        StringBuilder sb = new StringBuilder();
        for(Object o : param){
            sb.append(o + "; ");
        }
        System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString());

        // 继续执行方法
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println(methodName + "方法执行结束");

    }
}

在步骤二中的IndexController写一个sourceC进行测试,加上我们的自定义注解:

    @MyLog
    @GetMapping("/sourceC/{source_name}")
    public String sourceC(@PathVariable("source_name") String sourceName){
        return "你正在访问sourceC资源";
    }

启动springboot web项目,输入访问地址

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

用聪明的方式学习Vim,不再死记硬背,复杂命令一学就会 | GitHub 3200星

别了,腾讯微博!仅比新浪晚8个月,用户超3亿,马化腾曾亲自宣传,刚刚宣布即将关停

Spring Boot 最流行的 16 条实践解读,值得收藏!

关注订阅号「程序员小乐」,收看更多精彩内容

嘿,你在看吗

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java自定义注解是一种在代码声明的特殊标记,用于在运行时对代码进行处理。通过定义注解,我们可以在代码添加一些额外的元数据或标记信息,以实现对代码的自定义处理。使用自定义注解可以简化代码的编写和维护,提高代码的可读性和可维护性。 自定义注解使用场景有很多,以下是一些常见的使用场景: 1. 代码检查和规范:通过定义注解并在代码添加注解,可以对代码进行静态检查,以确保代码符合规范和标准。例如,可以定义一个@NonNull注解用于标记不允许为null的参数或返回值,通过编译器插件或静态检查工具进行检查,从而避免了空指针异常。 2. 代码生成:通过定义注解和处理器,可以自动生成一些重复性的代码,提高开发效率。例如,可以定义一个@Entity注解用于标记实体类,通过注解处理器自动生成数据库表的建表语句或实体类的序列化/反序列化方法。 3. 运行时的动态处理:通过定义注解,并在运行时使用反射机制获取注解信息,可以实现一些动态处理的功能。例如,可以定义一个@Cacheable注解用于标记需要缓存的方法,然后使用反射在方法执行前判断是否有缓存数据,如果有则直接返回缓存结果,提高系统性能。 4. 测试框架:通过定义注解和处理器,可以实现自定义的测试框架。例如,可以定义一个@Test注解用于标记测试方法,然后使用注解处理器在测试运行时自动执行标记的测试方法。 总之,自定义注解Java语言一种强大的元数据扩展机制,可以在代码实现各种功能的自定义处理。通过合理设计和使用自定义注解,可以提高代码的可读性、可维护性和可扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值