Java进阶知识4:注解

前言:注解目前非常的流行,很多主流框架都支持注解,特别是在Java SSM框架之中存在各种注解,因为后续会学习这几个框架,所有需要先将这几个框架中要用到的知识点反射机制和注解先学习一下。日常项目中我们也可以用到注解,编写代码更简洁高效。

一、注解的概念

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

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

1、从JDK5.0始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的。可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解,开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息(Spring的AOP面向切面编程)。

值得注意的是,注解不是代码本身的一部分。注解对于代码的运行效果没有直接影响,同样无法改变代码本身,注解主要给编译器及工具类型的软件用的。注解的提取需要借助于 Java 的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本。

2、注解的使用场景

Java 官方文档写明:

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。注解主要针对的是编译器和其它工具软件(SoftWare tool)使用的。

注解有许多用处,主要如下:

a、提供信息给编译器: 编译器可以利用注解来探测错误和警告信息

b、编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。

c、运行时的处理: 某些注解可以在程序运行的时候接受代码的提取,借助反射手段

当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。

3、其实在前面我用最基础的Servlet写APP服务端时,就接触过servlet3.0中就引入的@WebServlet ——该注解用来声明一个Servlet的注册配置,用来映射客户端Http访问路径和Servlet。使用该注解后就不需要在Dynamic Web project中传统的web.xml中注册声明麻烦的  <servlet-name>和  <servlet-mapping>配置,这样编写代码简单方便太多了。

有了@WebServlet注解,就不用配置如下图的web.xml,相当于上面注解官方文档所说的注解用处b:软件工具可以用来利用注解信息来生成类似web/xml的配置信息,解放了程序猿。

二、Java内置注解

注解的语法比较简单,除了@符号的使用之外,它基本与Java固有语法一致。

1、作用在代码的注解,位于java.lang中

Java 5.0内置了3种标准注解:

  • @Override,表示当前的方法定义将覆盖超类中的方法。

  • @Deprecated,标记被弃用的代码。

  • @SuppressWarnings,指示编译器去忽略所标注内容产生的警告

上面这三个注解或多或少我们都会在写代码的时候遇到。

2、作用在其他注解的注解(即元注解或基本注解),位于java.lang.annotation中

Java还提供了4种元注解,专门负责新注解的创建

简单解释:

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。

  • @Documented - 标记这些注解是否包含在用户文档中。

  • @Target - 标记这个注解应该是哪种 Java 成员。

3、从 Java 7 开始,额外添加了 3 个注解

  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。

  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。函数式编程很火,所以 Java 8 也及时添加了这个特性。函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。

    我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,源码可以看到它就被 @FunctionalInterface 注解。

  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。(什么样的注解会多次应用呢?通常是注解的值可以同时取多个。)

三、自定义注解

注解的基本语法:创建如同接口,但是多了个 @ 符号。并且要想注解能够正常工作,定义注解的时候还需要用到元注解。

元注解......

public @interface 注解名 {定义体}

使用@interface自定义注解时,自动实现了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。实现了之后该自定义注解其实就相当于Java中的一个类。

1、自定义一个注解的方式

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

我们观察上面自定义注解的创建代码,除了@符号,注解很像是一个接口,定义注解的时候必须用到元注解。

在注解中一般会有一些元素称为属性以表示某些值。注解的属性看起来就像接口的方法,唯一的区别在于可以为其制定默认值。没有元素的注解称为标记注解,上面的@Test就是一个标记注解。

2、注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation{
    
    int id();
    
    String msg();

}

上面代码定义了 @TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

赋值的方式是在注解使用时的括号内以 value="" 形式,多个属性之前用 ,隔开。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。

@TestAnnotation(id=3,msg="hello annotation")
public class Test {

}

3、自定义注解总结

需要注意的是,使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个属性。方法的名称就是属性的名称,返回值类型就是属性的类型(返回值类型只能是8 种基本数据类型、Class、String、enum及它们的数组)。并且要想注解能够正常工作,定义注解的时候还需要用到元注解

定义注解格式:

        元注解.......

  public @interface 注解名 {定义体}

Annotation类型里面的属性该怎么设定: 

第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;   4

第二,属性成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组。例如,String value();这里的参数成员就为String;  

第三,属性不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。

举个例子,下面看一个定义了属性的注解。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
 public @interface UseCase {
      public String id();
      public String description() default "no description";
 }

定义了注解,必然要去使用注解。

 public class PasswordUtils {
       @UseCase(id = 47, description = "Passwords must contain at least one numeric")
       public boolean validatePassword(String password) {
           return (password.matches("\\w*\\d\\w*"));
       }
   
       @UseCase(id = 48)
       public String encryptPassword(String password) {
           return new StringBuilder(password).reverse().toString();
      }
  }

使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。

从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解属性的值进行特定的处理。

public static void main(String[] args) {
     List<Integer> useCases = new ArrayList<Integer>();
     Collections.addAll(useCases, 47, 48, 49, 50);
     trackUseCases(useCases, PasswordUtils.class);
 }

 public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
     for (Method m : cl.getDeclaredMethods()) {
         UseCase uc = m.getAnnotation(UseCase.class);
         if (uc != null) {
             System.out.println("Found Use Case:" + uc.id() + " "
                         + uc.description());
             useCases.remove(new Integer(uc.id()));
         }
     }
     for (int i : useCases) {
         System.out.println("Warning: Missing use case-" + i);
     }
 }

输出结果:

Found Use Case:47 Passwords must contain at least one numeric

Found Use Case:48 no description

Warning: Missing use case-49

Warning: Missing use case-50

四、注解通过反射提取

博文前面的部分讲了注解的基本语法,现在是时候检测我们所学的内容了。

要想在程序运行时正确检阅注解的内容信息,离不开一个手段,那就是反射。

1>首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

2>然后通过 getAnnotation() 方法来获取 Annotation 对象。

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}

 或者是 getAnnotations() 方法。

public Annotation[] getAnnotations() {}

注意:前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。

如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。

比如:

@TestAnnotation()
public class Test {
    
    public static void main(String[] args) {
        
        boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
        
        if ( hasAnnotation ) {
            TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            
            System.out.println("id:"+testAnnotation.id());
            System.out.println("msg:"+testAnnotation.msg());
        }

    }

}

程序的运行结果是:

id:-1
msg:

这个正是 TestAnnotation 中 id 和 msg 的默认值。

上面的例子中,只是检阅出了注解在类上的注解,其实属性、方法上的注解照样是可以的。同样还是要假手于反射。

@TestAnnotation(msg="hello")
public class Test {
    
    @Check(value="hi")
    int a;
    
    
    @Perform
    public void testMethod(){}
    
    
    @SuppressWarnings("deprecation")
    public void test1(){
        Hero hero = new Hero();
        hero.say();
        hero.speak();
    }


    public static void main(String[] args) {
        
        boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
        
        if ( hasAnnotation ) {
            TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            //获取类的注解
            System.out.println("id:"+testAnnotation.id());
            System.out.println("msg:"+testAnnotation.msg());
        }
        
        
        try {
            Field a = Test.class.getDeclaredField("a");
            a.setAccessible(true);
            //获取一个成员变量上的注解
            Check check = a.getAnnotation(Check.class);
            
            if ( check != null ) {
                System.out.println("check value:"+check.value());
            }
            
            Method testMethod = Test.class.getDeclaredMethod("testMethod");
            
            if ( testMethod != null ) {
                // 获取方法中的注解
                Annotation[] ans = testMethod.getAnnotations();
                for( int i = 0;i < ans.length;i++) {
                    System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName());
                }
            }
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        
    }

}

它们的结果如下:

id:-1
msg:hello
check value:hi
method testMethod annotation:Perform

需要注意的是,如果一个注解要在运行时被成功提取,那么 @Retention(RetentionPolicy.RUNTIME) 是必须的。

五、Annotation 的作用

Annotation 是一个辅助类,它在 Junit、Struts、Spring 等工具框架中被广泛使用。

我们在编程中经常会使用到的 Annotation 作用有:

1)编译检查

Annotation 具有"让编译器进行编译检查的作用"。

例如,@SuppressWarnings, @Deprecated 和 @Override 都具有编译检查作用。

以@Override举例,某个方法被 @Override 的标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被 @Override 标示,但父类中却没有"被 @Override 标注"的同名方法,则编译器会报错。示例如下:

上面是该程序在 eclipse 中的截图。从中,我们可以发现 "getString()" 函数会报错。这是因为 "getString() 被 @Override 所标注,但在OverrideTest 的任何父类中都没有定义 getString() 函数"。

"将 getString() 上面的 @Override注释掉",即可解决该错误。

2)程序运行时借助反射手段提取Annotation

在反射的 Class, Method, Field 等函数中,有许多与 Annotation 相关的接口。

这也意味着,我们可以在反射中解析并使用 Annotation。

import java.lang.annotation.Annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
import java.lang.reflect.Method;

/**
 * Annotation在反射函数中的使用示例
 */
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String[] value() default "unknown";
}

/**
 * Person类。它会使用MyAnnotation注解。
 */
class Person {
   
    /**
     * empty()方法同时被 "@Deprecated" 和 "@MyAnnotation(value={"a","b"})"所标注
     * (01) @Deprecated,意味着empty()方法,不再被建议使用
     * (02) @MyAnnotation, 意味着empty() 方法对应的MyAnnotation的value值是默认值"unknown"
     */
    @MyAnnotation
    @Deprecated
    public void empty(){
        System.out.println("\nempty");
    }
   
    /**
     * sombody() 被 @MyAnnotation(value={"girl","boy"}) 所标注,
     * @MyAnnotation(value={"girl","boy"}), 意味着MyAnnotation的value值是{"girl","boy"}
     */
    @MyAnnotation(value={"girl","boy"})
    public void somebody(String name, int age){
        System.out.println("\nsomebody: "+name+", "+age);
    }
}

public class AnnotationTest {

    public static void main(String[] args) throws Exception {
       
        // 新建Person
        Person person = new Person();
        // 获取Person的Class实例
        Class<Person> c = Person.class;
        // 获取 somebody() 方法的Method实例
        Method mSomebody = c.getMethod("somebody", new Class[]{String.class, int.class});
        // 执行该方法
        mSomebody.invoke(person, new Object[]{"lily", 18});
        iteratorAnnotations(mSomebody);
       

        // 获取 somebody() 方法的Method实例
        Method mEmpty = c.getMethod("empty", new Class[]{});
        // 执行该方法
        mEmpty.invoke(person, new Object[]{});        
        iteratorAnnotations(mEmpty);
    }
   
    public static void iteratorAnnotations(Method method) {

        // 判断 somebody() 方法是否包含MyAnnotation注解
        if(method.isAnnotationPresent(MyAnnotation.class)){
            // 获取该方法的MyAnnotation注解实例
            MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
            // 获取 myAnnotation的值,并打印出来
            String[] values = myAnnotation.value();
            for (String str:values)
                System.out.printf(str+", ");
            System.out.println();
        }
       
        // 获取方法上的所有注解,并打印出来
        Annotation[] annotations = method.getAnnotations();
        for(Annotation annotation : annotations){
            System.out.println(annotation);
        }
    }
}

运行结果:

somebody: lily, 18
girl, boy, 
@com.skywang.annotation.MyAnnotation(value=[girl, boy])

empty
unknown, 
@com.skywang.annotation.MyAnnotation(value=[unknown])
@java.lang.Deprecated()

3) 根据 Annotation 生成帮助文档,还能够方便查看代码

通过给 Annotation 注解加上 @Documented 标签,能使该 Annotation 标签出现在 javadoc 中;

通过 @Override, @Deprecated 等,我们能很方便的了解程序的大致结构。

4>注解应用实例

注解的功能很强大,Spring和Hebernate这些框架在日志和有效性中大量使用了注解功能。注解可以应用在使用标记接口的地方。不同的是标记接口用来定义完整的类,但你可以为单个的方法定义注解,例如是否将一个方法暴露为服务。

在最新的servlet3.0中引入了很多新的注解,尤其是和servlet安全相关的注解。

HandlesTypes –该注解用来表示一组传递给ServletContainerInitializer的应用类。

HttpConstraint – 该注解代表所有HTTP方法的应用请求的安全约束,和ServletSecurity注释中定义的HttpMethodConstraint安全约束不同。

HttpMethodConstraint – 指明不同类型请求的安全约束,和ServletSecurity 注解中描述HTTP协议方法类型的注释不同。

MultipartConfig –该注解标注在Servlet上面,表示该Servlet希望处理的请求的 MIME 类型是 multipart/form-data。

ServletSecurity 该注解标注在Servlet继承类上面,强制该HTTP协议请求遵循安全约束。

WebFilter – 该注解用来声明一个Server过滤器;

WebInitParam – 该注解用来声明Servlet或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。

WebListener –该注解为Web应用程序上下文中不同类型的事件声明监听器。

WebServlet –该注解用来声明一个Servlet的配置,用来映射访问路径和Servlet

5>我们也可以通过自定义 Annotation 来完成某个目的。

下篇博客我会亲手自定义注解完成某个目的:Java进阶知识5:自定义注解完成某个目的

 

参考链接:

Java注解基本原理

Java自定义注解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java架构何哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值