zoukankan      html  css  js  c++  java
  • 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 ,我们才可以提取注解。

  • 相关阅读:
    nightwatchjs --Expect element to not include text
    Iterating elements using NightWatchJS
    nightwatch 切换窗口
    nodejs读取配置文件
    spring 事务
    重载,重写,重构
    python 元组不变 列表可变
    WebStorm ES6 语法支持设置
    docker日志
    curl -O 下载文件
  • 原文地址:https://www.cnblogs.com/luler/p/14986537.html
Copyright © 2011-2022 走看看