我们常用@Override,@SuppressWarnings,@Service等注解,这些都是预定义的注解。注解就是某种注解类型的一种实例,我们可以把它用在某个类上进行标注。下面这张图解释注解都是什么?
上图可以看出注解大体分为三种:元注解,标记注解,一般注解;如果我们实现自定义的标记注解,就需要了解标准元注解和相关定义注解的语法。元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
- @Target
- @Retention
- @Document
- @Inherited
@Target
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。取值(ElementType):
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述符
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER: 用于描述参数
- TYPE: 用于描述类、接口(包括注解类型)或者enum声明
@Retention
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。取值(RetentionPoicy):
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented 是一个标记注解,没有成员。
@Inherited
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
自定义注解语法
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author chengaoxing
* @date 2019年08月01日
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonAnnotation
{
String name() default "test";
int age();
}
上面是一个例子,定义了PersonAnnotation的注解,注解类用@interface修饰,有两个配置参数name和age。注解参数的可支持数据类型:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
Annotation类型里面的参数的设定:
- 只能用public或默认(default)这两个访问权修饰.例如,String name();这里把方法设为defaul默认类型;
- 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String name();这里的参数成员就为String;
- 如果只有一个参数成员,最好把参数名称设为"value",后加小括号
自定义注解使用
可以使用反射获取类的注解信息,下面是一个简单示例
import org.springframework.core.annotation.AnnotationUtils;
@PersonAnnotation(name = "11", age = 22)
public class TestAnnotation {
public static void print(Class c) {
System.out.println(c.getName());
// java.lang.Class的getAnnotation方法,如果有注解,则返回注解。否则返回null
PersonAnnotation person = AnnotationUtils.findAnnotation(c, PersonAnnotation.class);
if (person != null) {
System.out.println("name:" + person.name() + " age:" + person.age());
} else {
System.out.println("person unknown!");
}
}
public static void main(String[] args) {
TestAnnotation.print(TestAnnotation.class);
}
}
一般的项目中注解的应用场景可能是记录系统日志,登录验证等地方,结合spring 的aop 去实现
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Logined
{
/**
* 其它附加信息
* @return
*/
String[] others() default {};
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author chengaoxing on 2019/8/1
*/
@Aspect// 这个注解表明 使用spring 的aop,需要开启aop
@Component
public class LoginedAspect {
@Around(value = "@annotation(logined)")// 标示遇到logined这个注解的方法进行拦截 @Around
public Object checkLogin(ProceedingJoinPoint join, Logined logined) throws Throwable {
// 获取到使用了注解的方法的参数 这里面根据测试类 是 request,response
Object[] args = join.getArgs();
//获取使用了注解的方法的 注解的参数 这里面根据测试类 是 username,password
String[] orherString = logined.others();
// 执行使用注解方法并返回信息
Object ret = join.proceed();
// 下面是验证登录身份的逻辑代码 省略。。。。。。。
...
// 执行原方法
Object result = join.proceed();
return result;
}
}
上面的一个切面通过 @Around(value = "@annotation(logined)")拦截logined注解,在切面执行登陆的信息