什么是注解
用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据
元注解
JDK1.5之后内部提供的注解
- @Deprecated 意思是“废弃的,过时的”
- @Override 意思是“重写、覆盖”
- @SuppressWarnings 意思是“压缩警告”
- @Documented –注解是否将包含在JavaDoc中
- @Target? –注解用于什么地方
- @Inherited – 是否允许子类继承该注解
- @Retention –什么时候使用该注解
-
@Repeatable (since jdk1.8)-多次赋值
-
@SafeVarargs(since jdk1.7)
-
@FunctionalInterface (since jdk1.8)-函数式接口
@Retention
定义注解的生命周期
- RetentionPolicy.SOURCE
在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
- RetentionPolicy.CLASS
在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
- RetentionPolicy.RUNTIME
始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
@Target
表示该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。以下是一些可用的参数。需要说明的是:属性的注解是兼容的,如果你想给7个属性都添加注解,仅仅排除一个属性,那么你需要在定义target包含所有的属性。
ElementType.TYPE:用于描述类、接口或enum声明
ElementType.FIELD:用于描述实例变量
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE
ElementType.PACKAGE 用于记录java文件的package信息
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
@Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
说的比较抽象。代码来解释
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}@Other
public class C extends A
{}
注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
但是C不拥有@Test注解,因为被@Other注解应用了
@Repeatable
在JDK1.8之前注解同时只能被应用一次,应用2次会编译错误。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。
@interface Persons {
Person[] value();
}@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{}
注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。
什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。
我们再看看代码中的相关容器注解。
@interface Persons {
Person[] value();
}
按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。
如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。
我们可能对于 @Person(role=”PM”) 括号里面的内容感兴趣,它其实就是给 Person 这个注解的 role 属性赋值为 PM .
@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
上面的代码中,编译阶段不会报错,但是运行时会抛出 ClassCastException 这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。
@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
注解定义
定义
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME )
@Target(ElementType.TYPE)
public @interface Controller {
String name() default "";
}
注解不支持继承
注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口
属性
注解内部可以定义一些属性。
语法:类型 属性名() [ default 默认值 ];
@Retention(RetentionPolicy.RUNTIME )
@Target(ElementType.TYPE)
public @interface Controller {
String name() default "";
}
其实从代码的写法上来看,注解更像是一种特殊的接口,注解的属性定义方式就和接口中定义方法的方式一样,而应用了注解的类可以认为是实现了这个特殊的接口。
注解属性支持以下类型:
-
所有基本类型(int,float,boolean,byte,double,char,long,short)
-
String
-
Class
-
enum
-
Annotation
-
上述类型的数组
注解的属性必须有明确的值,要么使用默认值,要么在应用注解时指定值。
注解合并
每个开发人员都应该有过这样的经历:在编写某个类或接口的时候,需要声明Spring本身的注解(@Controller、@Service,@Dao),又需要声明自己公司编写的注解来完成公司的独特业务,然后就悲剧了,一个类上边声明了五六个注解,茫茫然不知所云。注解本身是好的,它可以替我们完成一些事情。但和XML一样,过度使用就编程了一种灾难。
于是,一种新的替代方案出现了,那就是组合注解。比较经典的组合注解就是SpringBoot的@SpringBootApplication注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
Class<?>[] exclude() default {};
String[] excludeName() default {};
//spring 在解析此处时,做特殊处理
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
//spring 在解析此处时,做特殊处理
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
注解应用
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
enum EnumType
{
First ,
Second
}@interface Reference
{
}@interface Test
{
String value();
}@Retention(RetentionPolicy.RUNTIME )
@Target(value={ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {//基本类型
int size() default 0;
//String
String name() default "";
//枚举
EnumType Order() default EnumType.First;
//Class类
Class<?> ClassType() default Void.class;
//注解
Reference Reference() default @Reference;
//数组
int[] Ids() default {1,3,4};}
@RequestMapping(
size = 5
,name="jack"
,Order=EnumType.Second
,ClassType=Integer.class
,Reference=@Reference
,Ids={4,5,7}
)@Test("mytest") //当注解仅有一个名称为value的属性时,可以省略属性名称
class AAA
{
}
注解提取
通过反射可以获取注解的信息。
首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
然后通过 getAnnotation() 方法来获取 Annotation 对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
//AA:class
//P3:@Merge的属性
AA.class.getAnnotation(Merge.class).P3();