java注解是jdk1.5以后新出的特性,注解提升了Java语言的表达能力,有效地实现了应用功能和底层功能的分离,框架/库的程序员可以专注于底层实现。
1、Java内置注解
主要有三个:
@Override:用于标明此方法覆盖了父类的方法
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
@Deprecated:用于标明已经过时的方法或类,源码如下
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
@SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
value是一个数组,可以有如下值:
- deprecation:使用了不赞成使用的类或方法时的警告;
- unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
- fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
- path:在类路径、源文件路径等中有不存在的路径时的警告;
- serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
- finally:任何 finally 子句不能正常完成时的警告;
- all:关于以上所有情况的警告。
三个综合使用的示例:
1 //注明该类已过时,不建议使用 2 @Deprecated 3 class A{ 4 public void A(){ } 5 6 //注明该方法已过时,不建议使用 7 @Deprecated() 8 public void B(){ } 9 } 10 11 class B extends A{ 12 13 @Override //标明覆盖父类A的A方法 14 public void A() { 15 super.A(); 16 } 17 18 //去掉检测警告 19 @SuppressWarnings({"uncheck","deprecation"}) 20 public void C(){ } 21 //去掉检测警告 22 @SuppressWarnings("uncheck") 23 public void D(){ } 24 }
2、自定义注解
2.1、注解定义
Java提供了一些定义好的注解如@SuppressWarnings、@Override等。也可以借助元注解来自定义注解,其格式示例如下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) @Documented @Inherited public @interface Mark { }
2.1.1、四个元注解
@Target,指定注解的应用目标,如@Target(ElementType.METHOD)。目标可以是多个,用{}表示,如@Target({TYPE, FIELD, METHOD, PARAMETER}),若未声明@Target,默认为适用于所有类型。目标是个枚举值ElementType,可以是:
- TYPE:表示类、接口(包括注解),或者枚举声明
- FIELD:字段,包括枚举常量
- METHOD:方法
- PARAMETER:方法中的参数
- CONSTRUCTOR:构造方法
- LOCAL_VARIABLE:本地变量
- ANNOTATION_TYPE:注解类型
- PACKAGE:包
- TYPE_PARAMETER:表示注解可以用于标注类型参数(java 1.8新加入),如class D<@Parameter T> { }
- TYPE_USE:表示注解可以用于标注任意类型(不包括class)(java 1.8新加入),如用于标注父类或接口:class Image implements @Rectangular Shape { }
@Retention,表示注解信息保留到什么时候。若未声明@Retention,则默认为CLASS。Retention取值只能有一个,类型为RetentionPolicy,它是一个枚举,有三个取值:
- SOURCE:只在源码中保留,编译器将代码编译为字节码后就会丢掉注解信息。如@Override、Lombok的大多数注解如@Data
- CLASS:保留到字节码文件中,但JVM将class文件加载到内存时注解丢弃。
- RUNTIME:一直保留到运行时,故能在运行时被JVM或其他使用反射机制的代码所读取和使用。如@Deprecated、Spring中的@Controller、@Autowired、@RequestMapping等。
@Documented,表示注解包含到Javadoc中,即被修饰者在javadoc中仍然带着该注解。若未声明则默认不被包含。
@Inherited,表示子类是否会继承父类的注解。若未声明则默认不会被继承。注:这里说的继承是指被注解修饰的类的继承。注解本身是不支持继承的,因此定义注解时不能使用关键字extends来继承某个注解。
另:@Repeatable,Java1.8新增了@Repeatable元注解,以允许对同一个位置重复使用相同注解。在Java1.8前没法对同一个元素重复使用同一个注解,要实现该效果只能为注解定义一个数组元素以接收多个值。
//Java8前无法这样使用 @FilterPath("/web/update") @FilterPath("/web/add") public class A {} //只能为@FilePath注解定义一个数组元素 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface FilterPath { String [] value(); } //使用 @FilterPath({"/update","/add"}) public class A { }
//使用Java8新增@Repeatable原注解 @Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(FilterPaths.class)//参数指明接收的注解class public @interface FilterPath { String value(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface FilterPaths { FilterPath[] value(); } //使用案例 @FilterPath("/web/update") @FilterPath("/web/add") @FilterPath("/web/delete") class AA{ }
2.1.2、注解参数
可以为注解定义一些参数,定义方式为:在注解内定义一些方法,方法返回值类型表示参数的类型。
参数的类型不是什么都可以的,合法的类型有:8钟基本类型(不允许使用包装类型)、String、Class、枚举(enum)、注解(Annotation)、以及这些类型的数组。倘若使用了其他数据类型,编译器将会丢出一个编译错误。
如对于如下注解,可以这样使用:使用: @SuppressWarnings(value={"deprecation","unused"})
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
注:
- 当注解只有一个参数,且名称为value时,提供参数值时可以省略"value=",如@SuppressWarnings({"deprecation","unused"})
- 参数定义时可以使用default指定一个默认值。
- 元素不能有不确定的值:必须要么具有默认值,要么在使用注解时提供元素的值,且值不能为null(无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值)。
2.1.3、注解特点
注解的定义不支持继承:无法使用关键字extends来继承某个@interface,因为注解在编译后,编译器会让注解自动继承java.lang.annotation.Annotation接口。
import java.lang.annotation.Annotation; //反编译后的代码 public interface DBTable extends Annotation { public abstract String name(); }
注解可以组合:虽然注解的定义不能继承,但可以通过类似组合的写法来继承其他注解的功能,如RestController就拥有了@Controller、@ResponseBoddy的功能(另外可以发现上面的元注解也是组合了其他元注解以拥有相应的功能),其定义如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) * @since 4.0.1 */ @AliasFor(annotation = Controller.class) String value() default ""; }
另外,javax validation中自定义validation注解也是通过组合@Constraint注解来实现的,详情可参阅:https://www.cnblogs.com/z-sm/p/4872259.html
2.2、注解解析器
注解解析有两种方式:运行时通过反射解析;编译时解析。
2.2.1、运行时解析
通过反射。
在运行时被解析的注解必须将@Retention设为RetentionPolicy.RUNTIME。与反射相关的类(Class、Field、Method、Constructor)中定义了一些方法:如Student.class.getAnnotation,可以利用反射机制在运行时获取注解信息。方法如:
//获取所有的注解 public Annotation[] getAnnotations() //获取所有本元素上直接声明的注解,忽略inherited来的 public Annotation[] getDeclaredAnnotations() //获取指定类型的注解,没有返回null public <A extends Annotation> A getAnnotation(Class<A> annotationClass) //判断是否有指定类型的注解 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
2.2.2、编译时解析
通过SPI调用注解解析器从而改变AST。
定义好注解解析器代码,在编译时javac会通过SPI加载并执行注解解析器,注解解析器可以修改抽象语法是(AST)从而实现注解应有的功能。定义注解解析器的两种方式:
Annotation Processing Tool:JDK5引入,JDK8废除。废弃原因:集成在com.sun.mirror非标准包下;没有集成到javac中,需要额外运行
Pluggable Annotation Processing API:JDK6引入,作为前者的替代。编译时(javac执行时)会调用该API,故可通过实行该API来改变编译行为。
例子:Lombok通过改变编译后的AST来达到修改代码的目的、AspectJ也是在编译期改变目标代码来实现类似于静态代理功能。