背景
最近想做Spring Boot的总结,众所周知,Spring有不少自己的注解,Spring Boot更甚,它的口号是做到零配置,目的也是为了避免配置零散到多个地方的场景。那么如果想要深入了解Spring Boot的话势必要知道它读取到注解后做哪些处理,这些后面会总结。这篇文章算是前提吧。顺势而为。
什么是注解
官方定义如下:
注解是一系列
元数据
,它提供数据
用来解释程序代码
,但是注解并非是所解释的代码本身的一部分
。注解对于代码的运行效果没有直接影响
。注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
有些小伙伴可能一看到元数据就已经有些懵了,这里先简单介绍一下元数据:元数据即描述数据的数据。比如说我们描述windows系统里面的一个文件,它是由很多属性定义的,像大小、编码、权限等等,这些就叫做它的元数据。如果想更深入理解可以看下wiki,说的实在是太太太详细了。
回到主题,对于官方定义我觉得可以一句话一句话的理解,参照我定义中标出的关键语句。
-
元数据
根据我上面元数据粗浅的解释,我们得出注解也是用来定义或者描述某个东西的。
-
提供数据
元数据可以定义并提供一些我们想要传达的数据。
-
解释程序代码
程序代码就是被定义或者描述的那个主体。
-
并非是所解释的代码本身的一部分
这句话其实跟上面的
解释
是同样的意思,我们可以想一想我们还有其他解释程序代码的方式比如说注释
,这样就很容易理解它们并不是代码的一部分这个含义。 -
对于代码的运行效果没有直接影响
我猜测大家大部分对这句话抱有怀疑。会想“不是这样吧,只有我类上标明
@service
后才能被Spring扫描到呀,怎么就说对代码没有影响了?”。其实换句话说可能大家就能更好的理解了,注解自身不会对代码产生任何影响,但是我们可以读取注解及其提供的数据来编写代码来对系统造成影响。还是上面的例子,@service
本身没有什么作用,但是Spring框架中有部分代码会依据它加载实例,所以我们看结果是它产生了影响。
最后三句话是简单说了一下注解可应用的主要场景,分别对应了不同的三个周期,周期的问题下面篇幅说明。
注解的定义
定义注解其实很简单,就跟定义一个类或者接口区别不是很大,只是把class | interface
换为@interface
。
同时定义注解我们还需要引入另一个概念,就是元注解
。
元注解
大家估计现在看名字就能猜出元注解
的作用了,就是修饰描述注解的注解。
元注解有以下五种。
@Retention
Retention翻译过来是保留的意思。它描述的就是被修饰注解的生命周期。
它的取值如下,这里我们可以跟上一节里面它的主要用途来对应着看:
-
RetentionPolicy.SOURCE
被修饰注解只能在源码阶段保留,不会进入编译器进行的编译期。
-
RetentionPolicy.CLASS
被修饰注解只能保留到编译期,不会加载到JVM。
-
RetentionPolicy.RUNTIME
注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Documented
其作用是能够将注解中的元素包含到 Javadoc 中去。
@Target
其作用是指定被修饰注解可用到哪些地方。
取值如下:
-
ElementType.ANNOTATION_TYPE
可以给一个注解进行注解
-
ElementType.CONSTRUCTOR
可以给构造方法进行注解
-
ElementType.FIELD
可以给属性进行注解
-
ElementType.LOCAL_VARIABLE
可以给局部变量进行注解
-
ElementType.METHOD
可以给方法进行注解
-
ElementType.PACKAGE
可以给一个包进行注解
-
ElementType.PARAMETER
可以给一个方法内的参数进行注解
-
ElementType.TYPE
可以给一个类型进行注解,比如类、接口、枚举
@Inherited
继承的意思。如果超类被某个注解修饰,而这个注解被@Inherited
修饰,那么继承超类的子类默认也被这个注解修饰。
@Repeatable
其为Java 1.8版本新引入元注解。场景可以举例说明,比如各个公司都有职位设置,而且也有兼职的这种制度,如果我们定义一个注解为@Role
,属性是职位那么可能就有@Role("manager")
或者@Role("developer")
,而我们修饰的那个人可能就是一个做开发的经理。
代码举例如下:
@interface Roles {
Role[] value();
}
@Repeatable(Roles.class)
@interface Role{
String value() default "";
}
@Role("manager")
@Role("developer")
public class SuperMan{
}
注解的属性
在注解的定义中我们知道注解是可以提供数据的,而这个方式就是定义属性。
这个属性的定义跟我们正常的认知不太一样,它采用的是定义无参无方法体类似定义方法的方式来定义的。其中方法名就是属性的名字,而返回值就是属性的类型。
而且可以通过default
关键字来定义默认值。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
public int id() default -1;
public String msg() default "hello world";
}
如上所示,我们定义了一个名为MyAnnotation
的注解,它包含两个属性且都有默认值。
可以这样使用:
@MyAnnotation(id=1, msg="msg")
public class MyClass() {}
//因为注解中有默认值,所以我们也可以不自定义属性值
@MyAnnotation()
public class MyClass() {}
//因为有些注解可能只有一个value属性,那直接引号就可以
@OnlyValue("bye")
public class MyClass() {}
//有些注解一个属性都没有,那就完全可以不带括号
@NoAttribute
public class MyClass() {}
如何获取注解
我们知道注解的生命周期主要有三种,而我们大部分用的都是Runtime
。不同周期获取注解的方式是不一样的,我们这版先主要讨论Runtime
期间的获取,其他的以后再扩充。
而运行期间进行获取的方式主要是反射(有没有其他方式没有研究过,如果知道其他的方式大家可以留言补充)。作为开发人员的明显看代码更形象。如下所示:
@MyAnnotation(msg="hello")
public class Test {
@Check(value="hi")
int a;
@Perform
public void testMethod(){}
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(MyAnnotation.class);
if ( hasAnnotation ) {
MyAnnotation testAnnotation = Test.class.getAnnotation(MyAnnotation.class);
//获取类的注解
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
try {
Field a = Test.class.getDeclaredField("a");
a.setAccessible(true);
//获取一个成员变量上的注解
Check check = a.getAnnotation(Check.class);
if ( check != null ) {
System.out.println("check value:"+check.value());
}
Method testMethod = Test.class.getDeclaredMethod("testMethod");
if ( testMethod != null ) {
// 获取方法中的注解
Annotation[] ans = testMethod.getAnnotations();
for( int i = 0;i < ans.length;i++) {
System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName());
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
参考资料
- 维基:元数据定义
- Java注解