注解(区别于注释)
注释 常用 “//” 和 “/* */” 是程序员编写程序时添加的一些注释性内容,不会被程序读取,对程序没有影响
注解(英文:Annotation)注解在框架中很常用 如spring中的@configation @bean @sercice @controller等。
注解是对代码的一种增强,可以在代码编译或者程序运行期间获取注解的信息,然后根据这
些信息做各种牛逼的事情。
注解可以使代码更简洁,可以完成一定的功能。下面我们来学习并使用注解
定义注解
jdk中和注解有关的类都定义在java.lang.annotation包中
注解的本质还是接口,注解继承了Annotation接口!!!
注解使用@interface来定义
例如:定义一个Myannotation注解
package com.test;
public @interface MyAnnotation {
}
带参数的注解
[public] 参数类型 参数名称() [default 默认值]
[]代表可写可不写。
package com.test;
public @interface MyAnnotation {
public String name() default "tom";
public int age() default 20;
public String address() default "BeiJing";
}
注意:
1.访问修饰符必须是public 不写默认public
2.参数类型必须是基本数据类型、string、class、枚举类型、注解类型以及上述类型的数组。
像Integer、Double这些都不行。
3.参数名字一般定义为名词,如果只有一个参数,一般定义为value。
因为:只有一个参数,并且名称为value时,注解使用时可以省略参数名。
即:
public @interface MyAnnotation {
public String name() default "tom";
public int age() default 20;
public String address() default "BeiJing";
}
这时可以直接写@MyAnnotation("aaa")
default设定参数默认值,使用注解时不指定参数,参数使用默认值。
4.参数名称后面的()内不能添加任何东西,这只是个特殊的语法。
5.如果没有默认值,使用注解时必须赋值
指定注解的使用范围:@Target
它的参数value赋值需从ElementType枚举类中选择。
自定义注解上也可以不使用@Target注解,如果不使用,表示自定义注解可以用在任何地方。
举例:
@Target(value = {ElementType.TYPE,ElementType.METHOD})
贴一下ElementType枚举类型
public enum ElementType {
/* 类、接口(包括注释类型)或枚举声明 */
TYPE,
/* 字段声明(包括enum常量) 就是成员变量 */
FIELD,
/* 方法声明 */
METHOD,
/* 正式的参数声明 */
PARAMETER,
/* 构造函数声明 */
CONSTRUCTOR,
/* 局部变量声明 */
LOCAL_VARIABLE,
/* 注释类型声明 */
ANNOTATION_TYPE,
/* 包声明 */
PACKAGE,
/* 类型参数声明 jdk1.8及以后 */
TYPE_PARAMETER,
/** 类型的使用 jdk1.8及以后 */
TYPE_USE
}
TYPE_PARAMETER 这里说一下用于泛型时的情况
先定义注解Annotation1
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
String value();
}
package com.test.annotation;
import java.lang.reflect.TypeVariable;
public class TestAnnotation <@Annotation1("在类上声明的第一个泛型") T0,@Annotation1("在类上声明的第二个泛型") T1>{
public <@Annotation1("在方法上声明的泛型") T3> void testMethod(){
}
public static void main(String[] args) {
TypeVariable<Class<TestAnnotation>>[] typeParameters = TestAnnotation.class.getTypeParameters();
for (TypeVariable index:typeParameters){
System.out.println(index.getName());
Annotation[] annotations = index.getAnnotations();
for ( Annotation example:annotations) {
System.out.println(example);
}
}
}
}
TypeVariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量
getTypeParameters()方法是获取实例的类型参数数组
getAnnotations()获取注解信息
输出结果:
T0
@com.test.annotation.Annotation1(value=在类上声明的第一个泛型)
T1
@com.test.annotation.Annotation1(value=在类上声明的第二个泛型)
指定注解的保留策略:@Retention
value:RetentionPolicy枚举类型值
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
java程序的3个过程:源码,class字节码文件,字节码文件被虚拟机加载运行。
这三个过程对应上面枚举类的三个值。
举例:
@Retention(RetentionPolicy.SOURCE)
package Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value=RetentionPolicy.RUNTIME)
public @interface myannotation {
//属性
String name() default "未赋值";//默认值
int age() default 0 ;
}
文档注解:@Documented
在声明注解时指定 @Documented,则它会被 javadoc 之类的工具处理, 注解类型信息也会会被包括在生成的文档中。
在对API生成文档是起作用。使API文档有此注解信息。
一般jdk中自带的注解都有此注解标识,使得其生成的api文档显示自带的注解类型信息。
使用注解
无参数注解
package com.test.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
}
@Ann1 //@2
public class UseAnnotation1 {
}
一个参数的注解
package com.test.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author zhangzhibin
* @create 2021-04-28 8:09
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
public String value() default "Tom";
}
@Annotation1(value = "Jack")
public class Test{
}
参数为value的注解,可以省略参数名称
@Annotation1("jack")
public class Test{
}
数组类型参数
package com.test.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
public String value() default "Tom";
public String[] address();
}
@Annotation1(address = {"上海","北京","广州"})
class Test{
}
注解信息的获取
Java在 java.lang.reflect 反射包下增设了AnnotatedElement 接口
主要用于表示目前正在虚拟机中运行的程序中已使用注解的元素,通过该
接口提供的方法可以利用反射技术地读取注解的信息
Package:用来表示包的信息
Class:用来表示类的信息
Constructor:用来表示构造方法信息
Field:用来表示类中属性信息
Method:用来表示方法信息
Parameter:用来表示方法参数信息
以上这些类实现了AnnotatedElement接口
设定Annotation1注解
package com.test.annotation;
@Target({
ElementType.PACKAGE,ElementType.TYPE,ElementType.FIELD,
ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,
ElementType.TYPE_PARAMETER,ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
String value();
}
测试类
package com.test.annotation;
import java.util.Map;
@Annotation1("用在了类上")
public class TestAnnotation<@Annotation1("用在了类变量类型V1上") V1, @Annotation1("用在了类变量类型V2上") V2> {
@Annotation1("用在了字段上")
private String name;
private Map<@Annotation1("用在了泛型类型上1") String, @Annotation1("用在了泛型类型上2") Integer> map;
@Annotation1("用在了构造方法上")
public TestAnnotation() {
this.name = name;
}
@Annotation1("用在了返回值上")
public String m1(@Annotation1("用在了参数上") String name) {
return null;
}
}
下面我们来解析以上注解信息
package com.test.annotation;
import org.jboss.logging.NDC;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
//解析类上的注解
Annotation[] annotations0 = TestAnnotation.class.getAnnotations();
for ( Annotation ex:annotations0){
System.out.println(ex);
}
//解析类上的类型变量注解 就是2个泛型
for (TypeVariable<Class<TestAnnotation>> index:TestAnnotation.class.getTypeParameters()){
Annotation[] annotations1 = index.getAnnotations();
for (Annotation ex:annotations1){
System.out.println(ex);
}
}
//解析字段上的注解
Field name = TestAnnotation.class.getDeclaredField("name");
for (Annotation ex:name.getAnnotations()){
System.out.println(ex);
}
//解析泛型字段map上的注解
Field map = TestAnnotation.class.getDeclaredField("map");
AnnotatedParameterizedType annotatedType = (AnnotatedParameterizedType)map.getAnnotatedType();
AnnotatedType[] annotatedActualTypeArguments = annotatedType.getAnnotatedActualTypeArguments();
int i = 0;
for (AnnotatedType actualTypeArgument:annotatedActualTypeArguments){
for (Annotation annotation : actualTypeArgument.getAnnotations()) {
System.out.println(annotation);
}
}
//解析方法上的注解
Method method = TestAnnotation.class.getMethod("m1", String.class);
for (Annotation annotation : method.getAnnotations()) {
System.out.println(annotation);
}
//解析方法参数注解
method = TestAnnotation.class.getMethod("m1", String.class);
for (Parameter parameter : method.getParameters()) {
for (Annotation annotation : parameter.getAnnotations()) {
System.out.println(annotation);
}
}
}
}
@Repeatable重复使用注解
使得注解可以重复使用,@Repeatable中value的值为容器注解
举例子:
先定义一个容器注解A1 A1的value属性类型为 A2注解的数组
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1 {
A2[] value();
}
在定义A2注解 要重复使用的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(A1.class)//指定容器注解
@interface A2 {
String name() default "Tom";
}
重复使用注解
package com.test.annotation;
@A2(name = "tom")
@A2(name = "tom")
public class TestAnnotation {
public static void main(String[] args) {
}
}
重复使用注解方法2
package com.test.annotation;
@A1({@A2(name = "tom"),@A2(name = "tom")})
public class TestAnnotation {
public static void main(String[] args) {
}
}
注解继承
JDK中定义注解不可继承,但是在spring中有一个@AliasFor可实现注解继承效果,介于此分类专栏是java学习而不是框架专栏,写在最后作为扩展资料。
注解虽然不可继承,但是其实现类可以。
实现类之间的注解继承:@Inherited
先看一个JDK中的一个API注解:@Inherited
@Target(ElementType.ANNOTATION_TYPE)可以看出这是一个修饰注解的注解。
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
作用:让子类可以继承修饰父类的注解,前提是此注解已被@Inherited修饰。
@Inherited使得被其修饰的注解能够被继承。
举个例子:
package com.test.annotation;
import java.lang.annotation.*;
public class TestAnnotation {
//在类中定义注解 成员注解
@Target(ElementType.TYPE)//修饰字段的 (成员变量)
@Retention(RetentionPolicy.RUNTIME)
@Inherited //指明下面定义的注解会被继承
@interface A1{
}
@A1 //注解修饰静态内部类C1
static class C1{}
//静态内部类C2继承C1
static class C2 extends C1{}
public static void main(String[] args) {
for (Annotation annotation : C2.class.getAnnotations()) {
System.out.println(annotation);
}
}
}
输出结果
@com.test.annotation.TestAnnotation$A1()
这里@Inherited使得修饰C1类的注解A1,继承到了C1的子类C2中。
扩展资料:注解的继承:spring中的@AliasFor注解
@AliasFor源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
Class<? extends Annotation> annotation() default Annotation.class;
}
AliasFor注解中 value 和 attribute 互为别名,随便设置一个,同时会给另外一个设置相同的值。
先看一段简单代码。
AnnotatedElementUtils是spring提供的一个查找注解的工具类。
使用前别忘了引入spring的maven依赖。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1 {
String value() default "a";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@A1
@interface B1 {
String value() default "b";
}
@B1("bbb")
public class TestAnnotation{
@Test
public void test1() {
System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, B1.class));
System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, A1.class));
}
}
此时如果想在 TestAnnotation测试类中,给B1上A1设置值是没办法的,注解不可继承。
下面引入Spring @AliasFor:对注解进行增强
案例:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1 {
String value() default "a";
}
添加了两行,意思是将A1中的value字段和B1中的a1Value字段建立映射。
a1Value是value的别名。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@A1
@interface B1 {
String value() default "b";
@AliasFor(annotation = A1.class,value = "value")
String a1Value();
}
测试类
package com.test.annotation;
import org.springframework.core.annotation.AnnotatedElementUtils;
@B1(value = "bbb",a1Value = "aaa")
public class TestAnnotation {
public static void main(String[] args) {
B1 test1 = AnnotatedElementUtils.getMergedAnnotation(TestAnnotation.class, B1.class);
A1 test2 = AnnotatedElementUtils.getMergedAnnotation(TestAnnotation.class, A1.class);
System.out.println(test1);
System.out.println(test2);
}
}
输出结果
@com.test.annotation.B1(value=bbb, a1Value=aaa)
@com.test.annotation.A1(value=aaa)