注解的定义
注解(Annotation)是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
通过注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。
需要注意的是,Annotation 仅仅的标记作用,不影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一地执行。
而我们所看到的注解起作用,其实是背后封装了一堆代码,对注解进行读取和添加了响应的业务逻辑。
在写 Java 的时候,肯定都使用过注解,下面是 Java 提供的5个基本注解:
- @Override:限定父类方法,强制一个之类必须覆盖父类的方法。
- @Deprecated:标示某个类或方法已过时。当其他程序使用已过时的类、方法时,编译器会警告。
- @SuppressWarnings:抑制编译器警告。被标记的元素以及所有子元素都不会发出编译警告。
- @SafeVarargs:Java 7 提供的专门用于抑制 ”堆污染“ 警告。
- @FuncationalInterface:Java 8 提供的专门用于标识函数式接口的注解(只能标注接口)
下面是 @Override 的注解定义:
package java.lang;
import java.lang.annotation.*;
/**
* Indicates that a method declaration is intended to override a
* method declaration in a supertype. If a method is annotated with
* this annotation type compilers are required to generate an error
* message unless at least one of the following conditions hold:
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* </li></ul>
*
* @author Peter von der Ahé
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
其中用到了 @Target 和 @Retention 这2个元注解。
元注解
JDK 在 java.lang.annotation 包下提供了6个 Meta Annotation(元注解),常用的有4个,分别是:
- @Retention
- @Target
- @Documented
- @Inherited
使用 @Retention
@Retention 只能用于修饰 Annotation 定义,用于指定被修饰 Annotation 可以保留多长时间。
@Retention 有一个 RetentionPolicy 类型的 value 成员变量,使用 @Retention 时必须指定 value 值。
value 变量的取值只有三个:
RetentionPolicy.SOURCE
:注解信息仅保留在目标类代码的源码文件中,但对应的字节码文件将不再保留。RetentionPolicy.CLASS
:注解信息将进入目标类代码的字节码文件中,但类加载器加载字节码文件时不会将注解加载到 JVM 中,即运行期不能获取注解信息。这是默认值。RetentionPolicy.RUNTIME
:注解信息在目标类加载到 JVM 后仍然保留,在运行期可以通过反射机制读取类中的注解信息。
例如:
// 定义的 Testable 注解会保留到运行时。
//也可以这样写:@Retention(value = RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable{}
我们常用的变量值是 RetentionPolicy.RUNTIME
使用 @Target
@Target 也只能用于修饰 Annotation 定义,用于指定被修饰的 Annotation 能用于修饰哪些程序单元。
@Target 也包含一个名为 value 的成员变量,变量值只能是如下几个:
ElementType.TYPE
:该策略的注解只能用于 类、接口、注解类、Enum声明处,称为类型注解ElementType.FIELD
:该策略的注解只能用于 类成员变量或常量声明处,称为域值注解。ElementType.METHOD
:该策略的注解只能用于 方法声明处,称为方法注解。ElementType.PARAMETER
:参数声明处,称为参数注解。ElementType.CONSTRUCTOR
:构造函数声明处,称为构造函数注解。ElementType.LOCAL_VARIABLE
:局部变量声明处,称为局域变量注解。ElementType.ANNOTATION_TYPE
:注解类声明处,称为注解类注解,ElementType.TYPE
包含了它。ElementType.PACKGE
:包声明处,称为包注解。
使用 @Documented
@Documented 也是用于修饰 Annotation 定义,用于指定被修饰的 Annotation 将被 javadoc 工具提取成文档。
如果定义注解类时使用了 @Documented 修饰,则所有使用该注解修饰的程序元素的 API 文档中将会包含该注解说明。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//Testable注解将被javadoc工具提取
@Documented
public @interface Testable{}
使用 @Inherited
@Inherited 指定被它修饰的 Annotation 具有继承性----如果某个类使用了 @Xxx 注解(定义 @Xxx 时使用了 @Inherited 修饰),则其子类将自动被 @Xxx 修饰。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
//Inheritable 注解修饰的类,其子类会自动使用Inheritable修饰
@Inherited
public @interface Inheritable{}
自定义注解
@Retention(RetentionPolicy.RUNTIME) //1.声明注解的保留期限
@Target(ElementType.METHOD) //2.声明可以使用该注解的目标类型
public @interface Testable{ //3.定义注解
boolean value() default true; // 4.声明注解成员
String name() default null; // 声明注解成员
}
Java 语法规定使用 @interface
修饰符定义注解类。
一个注解可以拥有多个成员,成员声明和接口方法声明类似。
成员声明有以下限制:
- 成员以无入参、无抛出异常的方式声明。
- 可以通过 default 为成员指定一个默认值,当然也可以不指定默认值。
- 成员的数据类型是受限制的,合法的类型包括:原始类型及其封装类、String、Class、enums、注解类型,以及上述类型的数组类型。
一个注解定义好之后就可以使用, 使用上述注解:
public class MyClass {
// 使用 Testable 注解修饰方法,并且使用参数覆盖默认值
@Testable(value = false, name = "info")
public void info (){
...
}
...
}
那么问题来了!!
使用了这个注解之后,这个注解有什么作用呢?
跟 @Override 定义类似,会不会有与 @Override 类似的作用呢?
答案是:没有!
开局的时候就说过,Annotation 仅仅的标记作用,不影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一地执行。
但是可以由开发者提供相应的工具来提取并处理 Annotation 信息。
提取 Annotation 信息
对于 RetentionPolicy.RUNTIME
保留期限的注解,可以通过反射机制访问类中的注解。
在 Java 5.0 中,Packge、Class、Constructor、Method 及 Field 等反射对象都新增了访问注解信息的方法:
<T ectends Annotation>T getAnnotation(Class<T> annotationClass)
:返回该程序元素上指定类型的注解,如果该注解不存在,就返回null。Annotation[] getAnnotations()
:返回该程序元素上存在的所有注解。
其实还有关于注解几个方法,这里只介绍常用的。
下面举个简单例子
定义注解 @AnnoTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoTest {
boolean value();
}
使用注解 @AnnoTest
public class ForumService {
@AnnoTest(value = false)//标注注解
public void deleteForum(int id){
System.out.println("删除论坛模块:" + id);
}
@AnnoTest(value = true)//标注注解
public void deleteTopic(int id){
System.out.println("删除论坛主题:" + id);
}
}
提取处理 @AnnoTest
public class ServiceTest {
//提取@AnnoTest并处理
public void tool(Class clazz){
// 得到 clazz 对应的 Method 数组
Method[] methods = clazz.getDeclaredMethods();
System.out.println(methods.length);
for (Method method : methods) {
// 获取方法上锁标注的注解对象
AnnoTest at = method.getAnnotation(AnnoTest.class);
if (at != null){
if (at.value()){
System.out.println(method.getName() + ":删除成功");
}else {
System.out.println(method.getName() + ":删除失败");
}
}
}
}
//测试
public static void main(String[] args) {
ServiceTest test = new ServiceTest();
test.tool(ForumService.class);
}
}
结果
一般的项目中应用场景可能是记录系统日志,登录验证等地方,结合spring 的aop 去实现会更方便。
在Spring AOP 中使用自定义注解,我们不用自己去提取注解信息,只需用来做个标记即可。
下面举个简单例子,注解的定义还是使用上面的 @AnnoTest (注意,@Retention 的取值必须为 RetentionPolicy.RUNTIME,不然提前不到注解信息)
定义增强类
@Aspect // 开启 spring aop
@Component
public class TestAspect {
//增强前置通知,使用 @annotation() 扫描注解AnnoTest
@Before("@annotation(AnnoTest)")
public void b(){
System.out.println("--------------------");
System.out.println("前置通知");
}
//增强后置通知,使用 @annotation() 扫描注解AnnoTest
@AfterReturning("@annotation(AnnoTest)")
public void needTestFun(){
System.out.println("needTestFun() executed!");
System.out.println("增加一个后置通知");
System.out.println("--------------------");
}
}
使用注解
@Component
public class TestImpl {
@AnnoTest
public void greetTo(String Name) {
System.out.println("TestImpl:greet to " + Name);
}
}
单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class ControllerTest {
@Autowired
private TestImpl test;
@Test
public void aopTest() {
test.greetTo("annotation test");
}
}
输出结果:
可以看出,使用 @AnnoTest 注解的方法被执行时,通过SpringAOP增强一个前置通知和一个后置通知。
小总结
注解起到的作用仅仅是标记作用,有或没有都不影响程序的执行。但可以通过提取注解元素并做一些处理,在项目中一般结合 springAOP 使用会更加方便。
在自定义 Annotation 的时候,我们常用到的元注解是 @Target() 和 @Retention(),而 Retention 的取值一般为 RetentionPolicy.RUNTIME ,我们才可以提取注解。