Java ”框架 = 注解 + 反射 + 设计模式“ 之 注解详解

每博一文案

刹那间我真想令时光停住,好让我回顾自己,回顾失去的年华,缅怀哪个穿一身短小的连衣裙和瘦窄的短衫的小女孩。让我追悔少年时代,我心灵的愚钝无知,它轻易地错过了我一生中本来可以获得欢乐和幸福。                             —————— 《平凡的世界》                             真的,如果痛苦不能改变生存,那还不如平静地将自己毁灭,毁灭,一切都毁灭了,只有生命还在苟延残喘,这样的生命还有有什么存在的价值。                            —————— 《平凡的世界》                     

@

目录

  • Java ”框架 = 注解 + 反射 + 设计模式“ 之 注解详解
    • 每博一文案
    • 1. 注解的概念
    • 2. 注解的作用
    • 3. 文档注释中的注解
    • 4. 自定义注解
      • 4.1 注解中的属性
      • 4.2 注解中属性为:数组的赋值
    • 5. JDK 内置的三个基本注解
      • 5.1 @Override: 限定重写父类方法, 该注解只能用于方法
      • 5.2 @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。
      • 5.3 @SuppressWarnings: 抑制编译器警告
    • 6. 元注解
      • 6.1 @Target
      • 6.2 @Retention
      • 6.3 @Documented
      • 6.4 @Inherited
    • 7. 通过反射获取到注解信息
    • 8. 总结:
    • 9. 最后:

1. 注解的概念

注解,一种元数据形式提供了一个不属于程序本身的程序的数据。注解对他们注解的代码的操作没有直接的影响。

注解有很多用途,其中:

  • 编译器的信息 – 编译器可以使用注解来检测错误或抑制警告。
  • 编译和部署时处理 – 软件工具可以处理注解信息以生成代码,XML 文件等。
  • 运行时处理 – 一些注解可以在运行时检查

JDK5.0 开始,Java增加了对元数据(MetaData) 的支持,也就是 Annotation 注解。

  • 注解: 其实就是代码里的 特殊标记 ,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用 注解,

程序员可以在不改变原有的逻辑的情况下,在源文件种嵌入一些补充的信息。代码分析工具,开发工具和部署工具可以通过这些补充信息进行验证或进行部署。

  • 注解: 可以像修饰符一样被使用,可以用于 修饰:包,类,构造器,方法,成员变量,参数,局部变量的声明 。这些信息被保存在 注解 Annotaion 的“ name = value” 键值对中。

  • JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如

    用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 代码和XML配置等。

  • 未来的开发模式都是基于注解的,JPA 是基于注解的,Spring2.5 以上都是基于注解的,Hibernate3.x 以后也是基于注解的,

现在Struts2 有一部分也是基于注解的了。注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式

2. 注解的作用

JVM 的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。

Java的注解可以分为三类:

  • 第一类:是由编译器使用的注解:换句话说就是给编译器看的,不是给 JVM 看的。例如:
    • @Override : 让编译器检查该方法是否正确的实现了 重写操作。
    • @Deprecated : 表示所修饰的元素(类,方法等)已过时了,不建议使用它了。
    • @SuppressWarnings: 告诉编译器忽略此处代码产生的警告。
  • 第二类: 是由工具处理 .class 文件使用的注解,比如有些工具会在加载 class 的时候,对 class 做动态修改,实现一些特殊的功能。这类注解会被编译进入到 .class 文件,但加载结束后并不会存在于内存中。这类注解只被一些底层使用,一般我们不必自己处理。
  • 第三类: 是在程序运行期间能够读取的注解,它们在加载后一直存在于 JVM 中,这也是最常用的注解。例如:一个配置了@PostConstruct 的方法会在调用构造方法后自动被调用,(这是Java代码读取该注解实现的功能,JVM 并不会识别该注解)。

3. 文档注释中的注解

  • @author 标明开发该类模块的作者,多个作者之间使用,分割。
  • @version 标明该类的模块的版本。
  • @see 参考转向,也就是相关类的主题。
  • @since 从哪个版本开始增加的。
  • @param 对方法中某参数的说明,如果没有参数就不能写。
  • @return 对方法返回值的说明,如果方法的返回值类型是 void 就不能写
  • @exception 对方法可能抛出的异常进行说明,如果方法没有用 throws 显抛出的异常就不能写 其中:
    • @param @ return 和 @ exeption 这三个标记都是只用于方法的。
    • @param 的格式要求:@param 形参名 形参类型 形参说明
    • @return 的格式要求:@return 返回值类型 返回值说明
    • @exception 的格式要求:@exception 异常类型 异常说明
    • @ param 和 @exception 可以并列多个。

4. 自定义注解

Annotaion注解 其实也是一种引用数据类型,编译之后也是生成 xxx.class 字节码文件的。

  • 定义新的 Annotation 注解 类型使用 @interface 关键字

  • 自定义注解自动实现了 java.lang.annotation.Annotation接口

自定义注解的格式如下:

public @interface MyAnnotation {    }// [修饰列表] @interface 注解名/(注解类名) {    }

使用 IDEA 创建一个 注解类型: 鼠标右键 ——> new 一个选择 :

如下查看该 我们该自定义的注解 MyAnnotation 的 UML 图:可以清楚的看到该 注解类型是自动继承了该 java.lang.annotation.Annotation 接口的

但是事实上却是自动实现了 java.lang.annotation.Annotation 接口。

Java 8之前,注解只能是在声明的地方所使用,Java8 开始,注解可以应用 在任何地方 。这里的任何地方包括:包,类,构造器,方法,成员变量,参数,局部变量的声明 。这些信息被保存在 注解 Annotaion 的“ name = value” 键值对中。

举例如下: 并没有出现任何的报错的情况。

4.1 注解中的属性

在注解中可以定义属性。

Java中的注解中的属性:看着像方法,但实际在注解当中是属性 name

格式如下:

String value();// 数据类型 属性名();  // 看似是方法,其实在注解中是属性

注解中的属性可以是任何类型:byte, short, int, long, float, double, boolean, char, String, long, Class, 枚举类型。再或者是自定义类型。

举例:

public @interface MyAnnotation {        String value();}

注意: 如果该注解中定义了属性,则声明使用该注解时 必须 为其注解中的属性值赋上值,不然,是会报错的。

如下,我们为该 @MyAnnotation 注解定义了属性,使用时却没有赋值,报如下编译错误。

为注解中的属性赋值的格式如下:

@MyAnnotaion(value="Tom") // 注意:在该注解中定义的是什么类型的值,就赋值对应的值,不然会报错的// @注解名(注解中的属性名=对应赋值的属性值)

举例:

如果该注解中只有一个属性值,并且该注解的属性名为 value ,则在赋值时,可以省略其 为value的属性名,直接写值

注意一定要满足两个条件:1. 该注解中只有一个属性值,2.该属性名为 value

举例:


为注解中的多个属性赋值格式如下: 多个属性值,使用逗号分隔开来,就可以了。

 @MyAnnotation(value = "Tom",value2 = "123")// @注解名(注解中的属性名=值,注解中的属性名=值) :多个属性值使用逗号分隔开。

注意: 当注解中存在多个属性值时,其中所有该注解中的属性值都必须赋值,不然编译报错,如下:

必须将注解中的所有属性值都赋值上值才行:如下:如果注解中存在两个或两个以上的属性,就算其中存在一个属性名为 value ,其赋值时,该value 属性名是不可以省略的。必须写明所有的属性名的进行赋值。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注解中的属性可以设置默认值,使用关键字“格式如下:

String value() default "Tom";// 数据类型 属性名() default  默认值;  注意需要和定义的类型保证一致。

举例:

public @interface MyAnnotation {    String value() default "Tom";}

注解中属性设置了,默认值的是可以选择不进行赋值操作,使用定义的默认值。

举例如下:

4.2 注解中属性为:数组的赋值

注解中的属性值是可以定义为数组属性的格式如下:

String[] arr(); // 定义数组为属性值数据类型[] 属性名(); 

举例:

public @interface MyAnnotation {    String value();    String[] arr();}

注解中以数组为属性的赋值格式如下:

@MyAnnotation(value = "Tom",arr = {"hello","world"})@注解名(属性名=值,属性名={值,值})

举例:

当数组属性所赋值的参数只有一个时,可以省略{} 花括号。如下

5. JDK 内置的三个基本注解

下面我们来认识一下,JDK 中三个常见的基本注解。

5.1 @Override: 限定重写父类方法, 该注解只能用于方法

@Override : 的作用就是在编译期间:让编译器检查该方法是否正确的实现了 重写 操作。其中的重写的方法名是否存在错误,方法的返回值类型是否是父类中/接口中的一致。不一致编译报错,提示我们改正。

@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: * * 
  • * The method does override or implement a method declared in a * supertype. *
  • * The method has a signature that is override-equivalent to that of * any public method declared in {@linkplain Object}. *
* * @author Peter von der Ahé * @author Joshua Bloch * @jls 9.6.1.4 @Override * @since 1.5 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}

注意 : 该注解只能使用在方法上(因为该注解的源码中被@Target(ElementType.METHOD)元注解修饰了),在其他位置上是会编译报错的。如下:


@OVerride 注解的使用: 较少了我们编程程序时,上的简单符号上以及一些基本的语法错误。

5.2 @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。

@Deprecated : 该注解表示:表示所修饰的元素(类,方法等)已过时了,不建议使用它了。建议替换成其他的方法。

@Depecated 源码: 可以看到该注解是没有定义属性的。

package java.lang;import java.lang.annotation.*;import static java.lang.annotation.ElementType.*;/** * A program element annotated @Deprecated is one that programmers * are discouraged from using, typically because it is dangerous, * or because a better alternative exists.  Compilers warn when a * deprecated program element is used or overridden in non-deprecated code. * * @author  Neal Gafter * @since 1.5 * @jls 9.6.3.6 @Deprecated */@Documented@Retention(RetentionPolicy.RUNTIME)@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})public @interface Deprecated {}

常见的,在我们的 java.util.lang 包下的 Date 中的 Date(String s) 构造器是被 @Deprecated 注解修饰了的。

在IDEA 中 如果我们调用了,被 @Deprecated 修饰的属性,方法,构造器,会给一个直观的将该属性/方法以删除线的方式显示处理并提示你建议使用别的方式替换 。如下

我们也可以自己写一个这样的被@Deprecated 修饰的属性/方法/类/构造器。举例如下:

5.3 @SuppressWarnings: 抑制编译器警告

@SuppressWarnings ** :指示应该在注解元素(以及包含在该注解元素中所有程序元素中的所有程序元素)中取消显示指定的编译器警告。换句话说:就是告诉编译器忽略此处代码产生的警告**。 注意是警告不是异常。

@SuppressWarnings的源码 ,可以看到该注解定义了一个名为 value 的属性。

package java.lang;import java.lang.annotation.*;import static java.lang.annotation.ElementType.*;/** * Indicates that the named compiler warnings should be suppressed in the * annotated element (and in all program elements contained in the annotated * element).  Note that the set of warnings suppressed in a given element is * a superset of the warnings suppressed in all containing elements.  For * example, if you annotate a class to suppress one warning and annotate a * method to suppress another, both warnings will be suppressed in the method. * * 

As a matter of style, programmers should always use this annotation * on the most deeply nested element where it is effective. If you want to * suppress a warning in a particular method, you should annotate that * method rather than its class. * * @author Josh Bloch * @since 1.5 * @jls 4.8 Raw Types * @jls 4.12.2 Variables of Reference Type * @jls 5.1.9 Unchecked Conversion * @jls 5.5.2 Checked Casts and Unchecked Casts * @jls 9.6.3.5 @SuppressWarnings */@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings { /** * The set of warnings that are to be suppressed by the compiler in the * annotated element. Duplicate names are permitted. The second and * successive occurrences of a name are ignored. The presence of * unrecognized warning names is not an error: Compilers must * ignore any warning names they do not recognize. They are, however, * free to emit a warning if an annotation contains an unrecognized * warning name. * *

The string {@code "unchecked"} is used to suppress * unchecked warnings. Compiler vendors should document the * additional warning names they support in conjunction with this * annotation type. They are encouraged to cooperate to ensure * that the same names work across multiple compilers. * @return the set of warnings to be suppressed */ String[] value();}

举例: 这里我们定义一个 int num = 1 / 0 语句,IDEA 该我们提示了警告了。

当我们加上了 @SuppressWarnings IDEA 就没有这个警告了。如下:

具体的其他应用大家可以移步至:???
https://blog.csdn.net/qq_43842093/article/details/122386115?ops_request_misc=&request_id=&biz_id=102&utm_term=%20@SuppressWarnings%E7%9A%84%E4%BD%BF%E7%94%A8&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-122386115.142
Eclipse Galileo版本支持的抑制警告的名称:

关键字用途
allto suppress all warnings (抑制所有警告)
boxingto suppress warnings relative to boxing/unboxing operations (抑制装箱、拆箱操作时候的警告)
castto suppress warnings relative to cast operations (抑制映射相关的警告)
dep-annto suppress warnings relative to deprecated annotation (抑制启用注释的警告)
deprecationto suppress warnings relative to deprecation (抑制过期方法警告)
fallthroughto suppress warnings relative to missing breaks in switch statements (抑制确在switch中缺失breaks的警告)
finallyto suppress warnings relative to finally block that don’t return (抑制finally模块没有返回的警告)
hidingto suppress warnings relative to locals that hide variable(抑制相对于隐藏变量的局部变量的警告)
incomplete-switchto suppress warnings relative to missing entries in a switch statement (enum case)(忽略没有完整的switch语句)
nlsto suppress warnings relative to non-nls string literals( 忽略非nls格式的字符)
nullto suppress warnings relative to null analysis( 忽略对null的操作)
rawtypesto suppress warnings relative to un-specific types when using generics on class params( 使用generics时忽略没有指定相应的类型)
restrictionto suppress warnings relative to usage of discouraged or forbidden references( 抑制禁止使用劝阻或禁止引用的警告)
serialto suppress warnings relative to missing serialVersionUID field for a serializable class( 忽略在serializable类中没有声明serialVersionUID变量)
static-accessto suppress warnings relative to incorrect static access( 抑制不正确的静态访问方式警告)
synthetic-accessto suppress warnings relative to unoptimized access from inner classes( 抑制子类没有按最优方法访问内部类的警告)
uncheckedto suppress warnings relative to unchecked operations( 抑制没有进行类型检查操作的警告)
unqualified-field-accessto suppress warnings relative to field access unqualified( 抑制没有权限访问的域的警告)
unusedto suppress warnings relative to unused code( 抑制没被使用过的代码的警告)

6. 元注解

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

6.1 @Target

@Target 源码:其中存在一个类型为 ElementType[] 枚举类型数组属性名为 value 的属性。

package java.lang.annotation;@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target {    /**     * Returns an array of the kinds of elements an annotation type     * can be applied to.     * @return an array of the kinds of elements an annotation type     * can be applied to     */    ElementType[] value();}

ElementType的枚举源码 :

/package java.lang.annotation;public enum ElementType {    /** Class, interface (including annotation type), or enum declaration */    TYPE,    /** Field declaration (includes enum constants) */    FIELD,    /** Method declaration */    METHOD,    /** Formal parameter declaration */    PARAMETER,    /** Constructor declaration */    CONSTRUCTOR,    /** Local variable declaration */    LOCAL_VARIABLE,    /** Annotation type declaration */    ANNOTATION_TYPE,    /** Package declaration */    PACKAGE,    /**     * Type parameter declaration     *     * @since 1.8     */    TYPE_PARAMETER,    /**     * Use of a type     *     * @since 1.8     */    TYPE_USE}

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

  • 类或接口:ElementType.TYPE
  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER

举例:

@Target元注解中的 value 属性是枚举数组类型的,可以赋值多个值:比如表示该注解可以声明在方法,变量,类中 ,举例:

6.2 @Retention

@Retention 源码 : 我们可以看到如下注解中只定义了一个RetentionPolicy 的枚举类型名为 value 的属性名的属性

package java.lang.annotation;@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Retention {    /**     * Returns the retention policy.     * @return the retention policy     */    RetentionPolicy value();}

RetentionPolicy 枚举类型的源码

package java.lang.annotation;public enum RetentionPolicy {    /**     * Annotations are to be discarded by the compiler.     */    SOURCE,    /**     * Annotations are to be recorded in the class file by the compiler     * but need not be retained by the VM at run time.  This is the default     * behavior.     */    CLASS,    /**     * Annotations are to be recorded in the class file by the compiler and     * retained by the VM at run time, so they may be read reflectively.     *     * @see java.lang.reflect.AnnotatedElement     */    RUNTIME}

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

@Rentention 时必须为该 value 成员变量指定值

  • 仅编译期:RetentionPolicy.SOURCE 表示该注解的生命周期只在编译期间有效,在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释。
  • 仅class文件:RetentionPolicy.CLASS : 在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
  • 运行期:RetentionPolicy.RUNTIME,注意:只有定义该属性的注解,才能被反射读取到。上面两种方式都无法被反射读取到的。

如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解:

举例:

6.3 @Documented

@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。

  • 定义为Documented的注解必须设置Retention值为RUNTIME

@Documented 的源码 从源码看,该注解没有任何属性。

package java.lang.annotation;@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {}

6.4 @Inherited

@Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被

@Inherited : 修饰的 Annotation, 则其子类将自动具有该注解。

  • 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解

  • 实际应用中,使用较少

@Inherited 源码 从源码看,该注解没有任何属性。

package java.lang.annotation;@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Inherited {}

7. 通过反射获取到注解信息

想要让反射可以读取到注解中的信息,则该反射中的元注解必须是: @Retention(RetentionPolicy.RUNTIME) 才行。

举例: 这里我们使用反射读取到 fun() 方法中的 注解中的 value 属性值:

注解

package blogs.blog10;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)  // 生命周期在:运行期,才可以被反射读取到public @interface MyAnnotation {    String value() default "Tom";}
package blogs.blog10;import java.lang.reflect.Method;public class AnnotationTest {    public static void main(String[] args) {        Method method = null;        try {            // 获取类加载器,类对象            Class clazz = Class.forName("blogs.blog10.AnnotationTest"); // 全类路径名            // 获取 fun()方法            method = clazz.getDeclaredMethod("fun");        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        }        // 判断该方法是否存在该注解,存在才读取该注解上的属性值        if(method.isAnnotationPresent(MyAnnotation.class)) {            // 获取该注解对象            MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);            // // 获取该注解的属性值,就像对象.属性一样            String value = annotation.value();            System.out.println(value);        }    }    @MyAnnotation("lihua")    public void fun() {        int num = 0;    }}

8. 总结:

  1. 设计注解类型时,必须考虑该类型注解的基数。现在可以使用注解零次,一次,或者如果注解的类型被标记为 @Repeatable 多次。也可以通过使用 @Target 元注解来限制注解类型的使用位置。例如,您可以创建只能在方法和字段上使用的可重复注解类型。重要的是仔细设计注解类型,以确保使用注解的程序员发现它尽可能灵活和强大。
  2. 注解的作用:减少程序中的错误,提高程序员的开发效率。以及框架上的运用。
  3. 注意:注解中的属性必须赋值,不然编译无法通过,除非该属性设置了默认值信息,建议注解中的属性设置上默认值。
  4. 当注解中只有一个属性,并且该属性名为 value ,则在赋值上可以省略属性名。
  5. 注解多个值上的赋值,以及数组类型的属性值的赋值。
  6. 元注解:修饰注解上的注解,特别掌握:@Target,@Retention 这两个元注解,其中的属性值上的赋值的意义。

9. 最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善 。谢谢大家,江湖再见,后会有期 !!!