Java 注解
引言:
在Spring框架中,及servlet当中会经常看到注解形式,在使用注解之前(甚至在使用之后),XML被广泛的应用于描述元数据,但是有时候XML的维护会越来越糟糕。我们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的代码描述。目前,许多框架将XML和注解两种方式结合使用,平衡两者之间的利弊。
理解注解:
类似于修饰符,比如SpringBoot中的@SpringBootApplication,运行方法时,Spring会自动识别该方法并单独调用。注解对语意没有直接的影响,他们只负责提供信息供相关的程序使用。更一般的讲,注解永远不会改变被注解代码的语意。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MasterSpringMvcApplication {
public static void main(String[] args) {
SpringApplication.run(MasterSpringMvcApplication.class, args);
}
}
以下是@SpringBootApplication的部分源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}
对于一个注解的基本形式为:
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
//保留的环境
@Retention(RUNTIME)
//注释起作用的位置,此处表示它只能给方法注解
@Target(METHOD)
public @interface Test {
}
其中@Target限定注解标记范围,@Retention限定注解的生命周期,@Target和@Retention是Java提供的元注解,所谓元注解,就是注解的注解。
下面分别介绍@Target和@Retention
@Target用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型,其定义如下,也代表可能的取值范围
public enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 类型使用声明(1.8新加入)
* @since 1.8
*/
TYPE_USE
}
@Retention中文译为保留期,用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),其含有如下:
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等
RUNTIME:注解信息将在运行期(JVM)也保留,如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
int id();
String msg();
}
此处有两个属性id和msg使用时,我们通过下面方式赋值
@Test(id=1, msg="test")
public class Test {
}
在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
- 所有基本类型(int,float,boolean,byte,double,char,long,short)
- String
- Class
- enum
- Annotation
- 上述类型的数组
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
public int id() default -1;
public String msg() default "Hi";
}
此时注解可以不用输入值,而使用默认值
@Test()
public class Test {}
注解的提取
注解就好比标签,并且在合适的时候撕下来查看标签的内容,此时需要用到方法就是反射。也就是说,注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
然后通过 getAnnotation() 方法来获取 Annotation 对象
public <T extends Annotation> T getAnnotation(Class<T extends Annotation> annotationClass) {}
或者是 getAnnotations() 方法返回一个Annatation[]数组
public Annotation[] getAnnotations() {}
通过第一种方法获取注解元素代码如下:
@Test
public class Main {
public static void main(String[] args) {
//判断是否为Test注解
boolean hasAnnotation =
Main.class.isAnnotationPresent(Test.class);
if(hasAnnotation) {
Test testAnnotation = Main.class.getAnnotation(Test.class);
System.out.println(testAnnotation);
System.out.println(testAnnotation.id());
}
}
}
返回结果:
对于上面的@Test再稍微深入的了解一下,这个Test到底是类还是接口呢?由上面的方法:
public <T extends Annotation> T getAnnotation(Class<T extends Annotation> annotationClass) {}
并且根据上面的代码
Test testAnnotation = Main.class.getAnnotation(Test.class);
综合推测Test是一个继承自Annatation接口的接口,也有可能是抽象类或者类(因为在注解定义中没有显式的结构,所以让我有点疑惑),于是我们来反编译一下看看
javap -p Test.class
由此可证实,自定义的注解继承自Annotation的接口类型。
参考:
《Effect Java》
上一篇: 4.3日早普及组模拟赛总结