什么是注解
我们知道类是对于某一特征的事物进行定义,这些事物具有相同的属性,相同的行为。那注解可以理解为对于类的一个横向扩展,然后在类进行编译时对注解信息进行解析或丢弃,运行时可以根据注解信息进行横向操控.
如下图所示,假设我有这样一个需求,A,B,C是三个不同的类,我需要在应用程序启动时分别调用类A method1(), 类B method2(),类C的method3()方法。如果不使用注解的话我们可以考虑定义一个接口StartUpService,同时会有initWhenStart()方法,而method1(), method2()和method3()就都变成了initWhenStart()方法,同时定义StartupService类,将所有实现了StartService接口的所有实现类通过反射机制查找出来,然后逐个调用initWhenStart()方法.
那如果使用注解之后我们可以怎么实现呢?我们可以通过在类A method1(),类B method2(), 类C method3()方法上都加入注解A, 然后当我们程序启动时,获取所有实现注解A的方法,然后分别对这些方法进行调用。
从上面的例子来看,好像可以通过接口的形式来解决方法注解的问题,那是不是所有问题都能解决呢?
我们通过Spring中一个常用的注解@RequestMapping来再深入的研究一下(如果对这个注解不了解的可以先去百度或google了解一下,这里就不深入研究了)。
这个注解主要用来将请求与处理函数关联起来,如下图所示当请求为/hello的时候,我们就会调用home()函数,当请求为/test的时候,就调用test()函数。看到这里,是不是发现用前面的接口实现起来就比较麻烦了呢,那就让我们来好好来认识认识注解吧~
package com.mary.demo.controller;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
String home() {
return "Hello World!";
}
@RequestMapping("/test")
String test(){
return "test";
}
}
JDK的元Annotation
通过元Annotation,我们可以规范定义的注解具体能用在什么地方(比如方法上面,类上面还是属性上面),保存时间有多久(如只是存在于编译之前,或者存在到编译过程还是说可以一直到jvm运行),或者这个注解能不能被继承等等
- @Retention : 用来描述定义的注解能保存多长时间,具体见下表
类型 | 描述 |
---|---|
RetenionPolicy.CLASS | 编译器把该注解记录在class文件中。当运行java程序时,JVM不可获取注解信息。这是默认值! |
RetenionPolicy.RUNTIME | 编译器把该注解记录在class文件中。当运行java程序时,JVM可获取注解信息,程序可以通过反射获取该注解信息 |
RetenionPolicy.SOURCE | 该注解只保存在源代码中,编译器直接丢弃该注解 |
- @Target: 用来描述定义的注解具体能用在哪个地方
类型 | 描述 |
---|---|
METHOD | 用于方法上 |
TYPE | 用于类或接口(同时也包括了ANNOTATION_TYPE即注解)上 |
ANNOTATION_TYPE | 可用于注解类型上(被@interface修饰的类型) |
CONSTRUCTOR | 用于构造函数上 |
FIELD | 用于属性上 |
LOCAL_VARIABLE | 用于局部变量上 |
PACKAGE | 用于记录java文件的package信息 |
PARAMETER | 用于参数上 |
- @Documented: 指定了是否对使用了该注解的API添加到document中
- @Inherited:主要用于表明该注解是否可继承,即如果注解可继承,则当我们去查找某个类是否有该注解时,会自动去其父类进行查找,一直查找到Object类。如果没设置可继承,则我们对某个类中是否定义了某个注解的查找只会在当前类中查找。另外,该注解指针对@Target为Element.Type和Element.ANNOTATION_TYPE有效,对于其他类型就没啥意义了。
JDK自带注解
对于JDK自带的注解,我们是可以直接使用的,同时对于注解的处理JDK也已经做好了。
注解名称 | 描述 |
---|---|
@Override | 当子类重写父类方法时,子类可以加上这个注解,那这有什么什么用?这可以确保子类确实重写了父类的方法,避免出现低级错误 |
@Deprecated | 这个注解用于表示某个程序元素类,方法等已过时,当其他程序使用已过时的类,方法时编译器会给出警告 |
@SuppressWarnings | 被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告,例如修饰一个类,那他的字段,方法都是显示警告 |
@SafeVarargs | “堆污染”警告 |
@Functionallnterface | .函数式接口 |
如何自定义注解
定义注解跟定义类一样,只是定义类我们使用class关键字,而定义注解我们使用@interface,同时我们可以添加元注解对注解进行修饰。
以Spring框架中的Indexed注解为例,看一下如何自定义注解。
- 首先我们使用@interface关键字定义注解
- 指定@Target元注解 --此例中这个注解定义在类或接口(同时包括了Annotation)上面
- 指定@Retention–此例中设置RetentionPolicy.RUNTIME, 即该注解能够一直存活到JVM运行,可以通过反射机制去查找出所有实现了该注解的接口跟类
- 可指定注解@Documented: 表明对于定义了该注解的接口跟类,所有的API都将被document.
- 可指定注解@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Indexed {
}
看一个复杂一点的注解
在注解Controller定义时,同时了声明了注解Component,此时我们猜到注解Component在定义的时候@Target应该是ElementType .ANNOTATION_TYPE或者ElementType.TYPE,直接上Component注解定义代码如下,由图可知,确实跟我们想的是一样的。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
特别说明
- 注解上面除了使用元注解之外,也是可以使用自定义注解(如上图的@Controller注解)