Java 基础 - 注解机制详解

2021-12-17 16:37:45 浏览数 (1)

Java中的各类注解以及自定义注解的使用和应用

[TOC]

1. Java注解概述

注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

常见注解分类:

  • Java自带的标准注解,包括@Override@Deprecated@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
  • 元注解,元注解是用于定义注解的注解,包括@Retention@Target@Inherited@Documented@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
  • 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。

2. 内置注解&元注解

2.1 内置注解

2.1 .1 内置注解 - @Override

代码

代码语言:javascript复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

作用

这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。这个注解的作用并不陌生,那就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错

2.1.2 内置注解 - @Override

代码

代码语言:javascript复制
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

作用

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用

2.1.3 内置注解 - @SuppressWarnings
代码语言:javascript复制
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示

参数

作用

原描述

all

抑制所有警告

to suppress all warnings

boxing

抑制装箱、拆箱操作时候的警告

to suppress warnings relative to boxing/unboxing operations

cast

抑制映射相关的警告

to suppress warnings relative to cast operations

dep-ann

抑制启用注释的警告

to suppress warnings relative to deprecated annotation

deprecation

抑制过期方法警告

to suppress warnings relative to deprecation

fallthrough

抑制确在switch中缺失breaks的警告

to suppress warnings relative to missing breaks in switch statements

finally

抑制finally模块没有返回的警告

to suppress warnings relative to finally block that don’t return

hiding

抑制与隐藏变数的区域变数相关的警告

to suppress warnings relative to locals that hide variable()

incomplete-switch

忽略没有完整的switch语句

to suppress warnings relative to missing entries in a switch statement (enum case)

nls

忽略非nls格式的字符

to suppress warnings relative to non-nls string literals

null

忽略对null的操作

to suppress warnings relative to null analysis

rawtype

使用generics时忽略没有指定相应的类型

to suppress warnings relative to un-specific types when using

restriction

抑制与使用不建议或禁止参照相关的警告

to suppress warnings relative to usage of discouraged or

serial

忽略在serializable类中没有声明serialVersionUID变量

to suppress warnings relative to missing serialVersionUID field for a serializable class

static-access

抑制不正确的静态访问方式警告

to suppress warnings relative to incorrect static access

synthetic-access

抑制子类没有按最优方法访问内部类的警告

to suppress warnings relative to unoptimized access from inner classes

unchecked

抑制没有进行类型检查操作的警告

to suppress warnings relative to unchecked operations

unqualified-field-access

抑制没有权限访问的域的警告

to suppress warnings relative to field access unqualified

unused

抑制没被使用过的代码的警告

to suppress warnings relative to unused code

2.2 元注解

在JDK 1.5中提供了4个标准的元注解:@Target@Retention@Documented@Inherited, 在JDK 1.8中提供了两个元注解 @Repeatable@Native

2.2.1 元注解 - @Target

Target注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。

Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。

代码语言:javascript复制
public enum ElementType {
    TYPE, // 类、接口、枚举类
    FIELD, // 成员变量(包括:枚举常量)
    METHOD, // 成员方法
    PARAMETER, // 方法参数
    CONSTRUCTOR, // 构造方法
    LOCAL_VARIABLE, // 局部变量
    ANNOTATION_TYPE, // 注解类
    PACKAGE, // 可用于修饰:包
    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
2.2.2 元注解 - @Retention & @RetentionTarget

Reteniton注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。

Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。

代码语言:javascript复制
public enum RetentionPolicy {
    SOURCE,    // 源文件保留
    CLASS,       // 编译期保留,默认值
    RUNTIME   // 运行期保留,可通过反射去获取注解信息
}
2.2.3 元注解 - @Documented

Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

2.2.4 元注解 - @Inherited

Inherited注解的作用:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

这个注解在Spring中特别常见,例如启动类的@SpringBootApplication就可以看到

2.2.5 元注解 - @Repeatable (Java8)

重复注解的可读性更强

JDK8之前

代码语言:javascript复制
public @interface Authority {
     String role();
}

public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseOldVersion {
    @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
    public void doSomeThing(){
    }
}

JDK8之后

代码语言:javascript复制
@Repeatable(Authorities.class)
public @interface Authority {
     String role();
}

public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseNewVersion {
    @Authority(role="Admin")
    @Authority(role="Manager")
    public void doSomeThing(){ }
}

不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。

3. 自定义注解

3.1 注解与反射接口

​ 在定义注解以后,在程序执行时得想办法拿到注解。反射包java.lang.reflect下的AnnotatedElement接口提供这些方法。这里注意:只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取

​ AnnotatedElement 接口是反射相关类(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的方法来访问Annotation信息。我们看下具体的先关接口

  • boolean isAnnotationPresent(Class<?extends Annotation> annotationClass) ​判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。注意:此方法会忽略注解对应的注解容器。
  • <T extends Annotation> T getAnnotation(Class<T> annotationClass) ​返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  • Annotation[] getAnnotations() ​返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组。
  • <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) ​返回该程序元素上存在的、指定类型的注解数组。没有注解对应类型的注解时,返回长度为0的数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。getAnnotationsByType方法与 getAnnotation的区别在于,getAnnotationsByType会检测注解对应的重复注解容器。若程序元素为类,当前类上找不到注解,且该注解为可继承的,则会去父类上检测对应的注解。
  • <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) ​返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释。如果没有注释直接存在于此元素上,则返回null
  • <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) ​返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释
  • Annotation[] getDeclaredAnnotations() ​返回直接存在于此元素上的所有注解及注解对应的重复注解容器。与此接口中的其他方法不同,该方法将忽略继承的注解。如果没有注释直接存在于此元素上,则返回长度为零的一个数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响

3.2 自定义注解的使用

定义注解

代码语言:javascript复制
@Target(ElementType.METHOD)  //使用范围:方法
@Retention(RetentionPolicy.RUNTIME) // 生命周期:运行中
@interface MyMethodAnnotation {
    public String title() default "默认标题";
    public String description() default "默认描述";
}

注解内容获取

代码语言:javascript复制
// 获取对应类的所有方法
Method[] methods = AnnotationAndReflection.class.getMethods();
for (Method method : methods) {
    // 判断是否含有指定注解
    if (method.isAnnotationPresent(MyMethodAnnotation.class)){
        // 获取所有注解
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        // 获取指定注解
        MyMethodAnnotation annotation = method.getAnnotation(MyMethodAnnotation.class);
    }
}

4. 注解的使用场景

Spring框架 配置话到注解化

Junit3->Junit4 从继承实现到注解实现

被测试类

代码语言:javascript复制
public class HelloWorld {
 	
 	public void sayHello(){
 		System.out.println("hello....");
 		throw new NumberFormatException();
 	}
 	public void sayWorld(){
 		System.out.println("world....");
 	}
 	public String say(){
 		return "hello world!";
 	}
}

Junit3 实现

通过继承 TestCase来实现,初始化是通过Override父类方法来进行,测试方式通过test的前缀方法获取。

代码语言:javascript复制
public class HelloWorldTest extends TestCase{
 	private HelloWorld hw;
 	
 	@Override
 	protected void setUp() throws Exception {
 		super.setUp();
 		hw=new HelloWorld();
 	}
 	
 	//1.测试没有返回值
 	public void testHello(){
 		try {
 			hw.sayHello();
 		} catch (Exception e) {
 			System.out.println("发生异常.....");
 		}
 		
 	}
 	public void testWorld(){
 		hw.sayWorld();
 	}
 	//2.测试有返回值的方法
 	// 返回字符串
 	public void testSay(){
 		assertEquals("测试失败", hw.say(), "hello world!");
 	}
 	//返回对象
 	public void testObj(){
 		assertNull("测试对象不为空", null);
 		assertNotNull("测试对象为空",new String());
 	}
 	@Override
 	protected void tearDown() throws Exception {
 		super.tearDown();
 		hw=null;
 	}	
}

Junit 4 实现UT

通过定义@Before,@Test,@After等等注解来实现。

代码语言:javascript复制
public class HelloWorldTest {
 	private HelloWorld hw;
 
 	@Before
 	public void setUp() {
 		hw = new HelloWorld();
 	}
 
 	@Test(expected=NumberFormatException.class)
 	// 1.测试没有返回值,有别于junit3的使用,更加方便
 	public void testHello() {
 		hw.sayHello();
 	}
 	@Test
 	public void testWorld() {
 		hw.sayWorld();
 	}
 	
 	@Test
 	// 2.测试有返回值的方法
 	// 返回字符串
 	public void testSay() {
 		assertEquals("测试失败", hw.say(), "hello world!");
 	}
 	
 	@Test
 	// 返回对象
 	public void testObj() {
 		assertNull("测试对象不为空", null);
 		assertNotNull("测试对象为空", new String());
 	}
 
 	@After
 	public void tearDown() throws Exception {
 		hw = null;
 	}
}

参考

  • Java 自定义注解及使用场景
  • Java 基础 - 注解机制详解
  • java注解的本质以及注解的底层实现原理
  • Java注解处理器
  • JUnit4源码分析运行原理

0 人点赞