Java自定义注解的实现和应用

注解的定义

注解(Annotation)是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。

通过注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。

需要注意的是,Annotation 仅仅的标记作用,不影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一地执行。

而我们所看到的注解起作用,其实是背后封装了一堆代码,对注解进行读取和添加了响应的业务逻辑。

在写 Java 的时候,肯定都使用过注解,下面是 Java 提供的5个基本注解:

  • @Override:限定父类方法,强制一个之类必须覆盖父类的方法。
  • @Deprecated:标示某个类或方法已过时。当其他程序使用已过时的类、方法时,编译器会警告。
  • @SuppressWarnings:抑制编译器警告。被标记的元素以及所有子元素都不会发出编译警告。
  • @SafeVarargs:Java 7 提供的专门用于抑制 ”堆污染“ 警告。
  • @FuncationalInterface:Java 8 提供的专门用于标识函数式接口的注解(只能标注接口)

下面是 @Override 的注解定义:

package java.lang;

import java.lang.annotation.*;

/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 *
 * <ul><li>
 * The method does override or implement a method declared in a
 * supertype.
 * </li><li>
 * The method has a signature that is override-equivalent to that of
 * any public method declared in {@linkplain Object}.
 * </li></ul>
 *
 * @author  Peter von der Ah&eacute;
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

其中用到了 @Target 和 @Retention 这2个元注解。

元注解

JDK 在 java.lang.annotation 包下提供了6个 Meta Annotation(元注解),常用的有4个,分别是:

  • @Retention
  • @Target
  • @Documented
  • @Inherited

使用 @Retention

@Retention 只能用于修饰 Annotation 定义,用于指定被修饰 Annotation 可以保留多长时间。

@Retention 有一个 RetentionPolicy 类型的 value 成员变量,使用 @Retention 时必须指定 value 值。

value 变量的取值只有三个:

  • RetentionPolicy.SOURCE :注解信息仅保留在目标类代码的源码文件中,但对应的字节码文件将不再保留。
  • RetentionPolicy.CLASS :注解信息将进入目标类代码的字节码文件中,但类加载器加载字节码文件时不会将注解加载到 JVM 中,即运行期不能获取注解信息。这是默认值
  • RetentionPolicy.RUNTIME :注解信息在目标类加载到 JVM 后仍然保留,在运行期可以通过反射机制读取类中的注解信息。

例如:

// 定义的 Testable 注解会保留到运行时。
//也可以这样写:@Retention(value = RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable{}

我们常用的变量值是 RetentionPolicy.RUNTIME

使用 @Target

@Target 也只能用于修饰 Annotation 定义,用于指定被修饰的 Annotation 能用于修饰哪些程序单元。

@Target 也包含一个名为 value 的成员变量,变量值只能是如下几个:

  • ElementType.TYPE :该策略的注解只能用于 类、接口、注解类、Enum声明处,称为类型注解
  • ElementType.FIELD :该策略的注解只能用于 类成员变量或常量声明处,称为域值注解。
  • ElementType.METHOD :该策略的注解只能用于 方法声明处,称为方法注解。
  • ElementType.PARAMETER :参数声明处,称为参数注解。
  • ElementType.CONSTRUCTOR :构造函数声明处,称为构造函数注解。
  • ElementType.LOCAL_VARIABLE :局部变量声明处,称为局域变量注解。
  • ElementType.ANNOTATION_TYPE :注解类声明处,称为注解类注解,ElementType.TYPE 包含了它。
  • ElementType.PACKGE :包声明处,称为包注解。

使用 @Documented

@Documented 也是用于修饰 Annotation 定义,用于指定被修饰的 Annotation 将被 javadoc 工具提取成文档。

如果定义注解类时使用了 @Documented 修饰,则所有使用该注解修饰的程序元素的 API 文档中将会包含该注解说明。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//Testable注解将被javadoc工具提取
@Documented
public @interface Testable{}

使用 @Inherited

@Inherited 指定被它修饰的 Annotation 具有继承性----如果某个类使用了 @Xxx 注解(定义 @Xxx 时使用了 @Inherited 修饰),则其子类将自动被 @Xxx 修饰。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
//Inheritable 注解修饰的类,其子类会自动使用Inheritable修饰
@Inherited
public @interface Inheritable{}

自定义注解

@Retention(RetentionPolicy.RUNTIME)	//1.声明注解的保留期限
@Target(ElementType.METHOD)	//2.声明可以使用该注解的目标类型
public @interface Testable{	//3.定义注解
    boolean value() default true;	// 4.声明注解成员
    
    String name() default null;		// 声明注解成员
}

Java 语法规定使用 @interface 修饰符定义注解类。

一个注解可以拥有多个成员,成员声明和接口方法声明类似。

成员声明有以下限制:

  • 成员以无入参、无抛出异常的方式声明。
  • 可以通过 default 为成员指定一个默认值,当然也可以不指定默认值。
  • 成员的数据类型是受限制的,合法的类型包括:原始类型及其封装类、String、Class、enums、注解类型,以及上述类型的数组类型。

一个注解定义好之后就可以使用, 使用上述注解:

public class MyClass {
    // 使用 Testable 注解修饰方法,并且使用参数覆盖默认值
    @Testable(value = false, name = "info")
    public void info (){
        ...
    }
    ...
}

那么问题来了!!

使用了这个注解之后,这个注解有什么作用呢?

跟 @Override 定义类似,会不会有与 @Override 类似的作用呢?

答案是:没有!

开局的时候就说过,Annotation 仅仅的标记作用,不影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一地执行。

但是可以由开发者提供相应的工具来提取并处理 Annotation 信息。

提取 Annotation 信息

对于 RetentionPolicy.RUNTIME 保留期限的注解,可以通过反射机制访问类中的注解

在 Java 5.0 中,Packge、Class、Constructor、Method 及 Field 等反射对象都新增了访问注解信息的方法:

  • <T ectends Annotation>T getAnnotation(Class<T> annotationClass) :返回该程序元素上指定类型的注解,如果该注解不存在,就返回null。
  • Annotation[] getAnnotations() :返回该程序元素上存在的所有注解。

其实还有关于注解几个方法,这里只介绍常用的。

下面举个简单例子

定义注解 @AnnoTest

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoTest {
    boolean value();
}

使用注解 @AnnoTest

public class ForumService {

    @AnnoTest(value = false)//标注注解
   public void deleteForum(int id){
        System.out.println("删除论坛模块:" + id);
    }

    @AnnoTest(value = true)//标注注解
    public void deleteTopic(int id){
        System.out.println("删除论坛主题:" + id);
    }
}

提取处理 @AnnoTest

public class ServiceTest {

    //提取@AnnoTest并处理
    public void tool(Class clazz){

        // 得到 clazz 对应的 Method 数组
        Method[] methods = clazz.getDeclaredMethods();
        System.out.println(methods.length);

        for (Method method : methods) {
            // 获取方法上锁标注的注解对象
            AnnoTest at = method.getAnnotation(AnnoTest.class);
            if (at != null){
                if (at.value()){
                    System.out.println(method.getName() + ":删除成功");
                }else {
                    System.out.println(method.getName() + ":删除失败");
                }
            }
        }
    }


    //测试
    public static void main(String[] args) {

        ServiceTest test = new ServiceTest();
        test.tool(ForumService.class);
    }
}

结果


一般的项目中应用场景可能是记录系统日志,登录验证等地方,结合spring 的aop 去实现会更方便。

在Spring AOP 中使用自定义注解,我们不用自己去提取注解信息,只需用来做个标记即可。

下面举个简单例子,注解的定义还是使用上面的 @AnnoTest (注意,@Retention 的取值必须为 RetentionPolicy.RUNTIME,不然提前不到注解信息)

定义增强类

@Aspect // 开启 spring aop
@Component
public class TestAspect {

    //增强前置通知,使用 @annotation() 扫描注解AnnoTest
    @Before("@annotation(AnnoTest)")
    public void b(){
        System.out.println("--------------------");
        System.out.println("前置通知");
    }

    //增强后置通知,使用 @annotation() 扫描注解AnnoTest
    @AfterReturning("@annotation(AnnoTest)")
    public void needTestFun(){
        System.out.println("needTestFun() executed!");
        System.out.println("增加一个后置通知");
        System.out.println("--------------------");
    }

}

使用注解

@Component
public class TestImpl {

    @AnnoTest
    public void greetTo(String Name) {
        System.out.println("TestImpl:greet to " + Name);
    }

}

单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class ControllerTest {

    @Autowired
    private TestImpl test;

    @Test
    public void aopTest() {
        test.greetTo("annotation test");
    }
}

输出结果:

可以看出,使用 @AnnoTest 注解的方法被执行时,通过SpringAOP增强一个前置通知和一个后置通知。

小总结

注解起到的作用仅仅是标记作用,有或没有都不影响程序的执行。但可以通过提取注解元素并做一些处理,在项目中一般结合 springAOP 使用会更加方便。

在自定义 Annotation 的时候,我们常用到的元注解是 @Target() 和 @Retention(),而 Retention 的取值一般为 RetentionPolicy.RUNTIME ,我们才可以提取注解。

posted @ 2021-07-08 16:03  乐子不痞  阅读(1442)  评论(0编辑  收藏  举报
回到顶部