Java 自定义注解及使用场景

简介: Java 自定义注解及使用场景

@[TOC]

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

# 1 什么是注解(Annotation)

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

> java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。

# 2 元注解

有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。

## 2.1 @Target

最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:

- 类或接口:ElementType.TYPE;

- 字段:ElementType.FIELD;

-  方法:ElementType.METHOD;

- 构造方法:ElementType.CONSTRUCTOR;

- 方法参数:ElementType.PARAMETER。

## 2.2 @Retention

另一个重要的元注解@Retention定义了Annotation的生命周期:

- 仅编译期:RetentionPolicy.SOURCE;

-  仅class文件:RetentionPolicy.CLASS;

-  运行期:RetentionPolicy.RUNTIME。

## 2.3 @Repeatable

使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。

## 2.4 @Inherited

使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效:

```java

@Inherited

@Target(ElementType.TYPE)

public @interface Report {

   int type() default 0;

   String level() default "info";

   String value() default "";

}

```

在使用的时候,如果一个类用到了@Report:

```java

@Report(type=1)

public class Person {

}

```

则它的子类默认也定义了该注解:

```java

public class Student extends Person {

}

```


#  示例1 反射获取注解

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

```java

@Target(ElementType.FIELD)  //  注解用于字段上

@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时,可通过注解获取

public @interface MyField {

   String description();

   int length();

}

```

通过反射获取注解

```java

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() +"]");

           }

       }


   }

}

```


## 示例2:自定义注解+拦截器 实现登录校验

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

```java

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface LoginRequired {

}

```

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

```java

@RestController

public class IndexController {


   @GetMapping("/sourceA")

   public String sourceA(){

       return "你正在访问sourceA资源";

   }


   @GetMapping("/sourceB")

   public String sourceB(){

       return "你正在访问sourceB资源";

   }


}

```

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

![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210414222411395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxODk4NjQ3,size_16,color_FFFFFF,t_70)实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:

```java

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,创建配置类把拦截器添加到拦截器链中

```java

@Configuration

public class InterceptorTrainConfigurer implements WebMvcConfigurer {

   @Override

   public void addInterceptors(InterceptorRegistry registry) {

       registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");

   }

}

```

拦截成功如下

![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210414222559331.png)

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

```java

@RestController

public class IndexController {


   @GetMapping("/sourceA")

   public String sourceA(){

       return "你正在访问sourceA资源";

   }


   @LoginRequired

   @GetMapping("/sourceB")

   public String sourceB(){

       return "你正在访问sourceB资源";

   }


}

```

简单实现登录拦截逻辑

```java

@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则不用登录.

![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210414222753521.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxODk4NjQ3,size_16,color_FFFFFF,t_70)


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


1 什么是注解(Annotation)

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


java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。


2 元注解

有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。


2.1 @Target

最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:


类或接口:ElementType.TYPE;

字段:ElementType.FIELD;

方法:ElementType.METHOD;

构造方法:ElementType.CONSTRUCTOR;

方法参数:ElementType.PARAMETER。

2.2 @Retention

另一个重要的元注解@Retention定义了Annotation的生命周期:


仅编译期:RetentionPolicy.SOURCE;

仅class文件:RetentionPolicy.CLASS;

运行期:RetentionPolicy.RUNTIME。

2.3 @Repeatable

使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。


2.4 @Inherited

使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效:


@Inherited

@Target(ElementType.TYPE)

public @interface Report {

   int type() default 0;

   String level() default "info";

   String value() default "";

}

在使用的时候,如果一个类用到了@Report:


@Report(type=1)

public class Person {

}

则它的子类默认也定义了该注解:


public class Student extends Person {

}

示例1 反射获取注解

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


@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() +"]");

           }

       }


   }

}

示例2:自定义注解+拦截器 实现登录校验

接下来,我们使用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则不用登录.

在这里插入图片描述


发文助手

发文助手会检测您的文章标题、错别字、内容质量,助您提升文章质量。【创作规范】


暂无建议

Markdown 4395 字数 179 行数 当前行 1, 当前列 0HTML 3792 字数 134 段落

相关文章
|
1月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
61 1
|
16天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
53 7
|
1月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
36 1
|
2月前
|
Java
让星星⭐月亮告诉你,自定义定时器和Java自带原生定时器
定时器是一种可以设置多个具有不同执行时间和间隔的任务的工具。本文介绍了定时器的基本概念、如何自定义实现一个定时器,以及Java原生定时器的使用方法,包括定义定时任务接口、实现任务、定义任务处理线程和使用Java的`Timer`与`TimerTask`类来管理和执行定时任务。
59 3
|
13天前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
78 34
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
98 43
Java学习十六—掌握注解:让编程更简单
|
22天前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
60 5
|
1月前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
57 14
|
1月前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
37 12
|
2月前
|
安全 Java
如何在 Java 中创建自定义安全管理器
在Java中创建自定义安全管理器需要继承SecurityManager类并重写其方法,以实现特定的安全策略。通过设置系统安全属性来启用自定义安全管理器,从而控制应用程序的访问权限和安全行为。
61 1