介绍
java中有四大类型,分别是:类、接口、枚举、注解。注解是jdk1.5加入到语言规范的,注解主要有两个作用,一,给编译器或者虚拟机传递信息,例如@Override方法重载,@SuppressWarnings抑制编译警告等等;二、开发人员可以携带一些信息到运行时期而不用改变原有的代码逻辑。所有注解都是Annotation接口的子接口,Annotation中的方法:
public interface Annotation {
//判断两个注解是否相等
boolean equals(Object obj);
//返回hashCode
int hashCode();
//返回注解的描述信息
String toString();
//返回注解的类型
Class<? extends Annotation> annotationType();
}
定义注解
import java.lang.annotation.*;
@Inherited
@Documented
@Repeatable(Another.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE,ElementType.METHOD})
public @interface MyAnnotation {
String name() default "zs";
}
Another中的定义
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface Another {
MyAnnotation[] value();
}
在MyAnnotation注解中用到了基本常用的元注解类型,下面一一解释
1、 @Inherited注解
Inherited意思是可继承的,意思是标注此注解的类,在被继承时,子类在查找注解信息是可以直接找到,而不用通过继承结构依次向上查找。java虚拟机自动帮我们实现类结构的查找。
2、@Documented注解
此注解的意思是被标注的注解会生成java的doc文档
3、@Repeatable注解
Repeatable可重复的,被标注的注解可以多次声明在一个类上`
@MyAnnotation
@MyAnnotation
@MyAnnotation
public class Order {}
但是我们要指定容器注解,容器注解有几个要求:
- 必须有一个value属性,并且返回类型必须为可重复注解的数组类型,参见Another注解
- Retention保留策略,生名周期必须大于等于可重复注解的生存周期,参见Another注解
- Target标注的目标对象,是可重复注解可标注的目标对象的子集,参见Another注解
- 可重复注解上出现的元注解,容器注解上也得出现,例如:@Inherited,@Documented,参见Another注解
4、@Retention注解
Retention保留,只有一个方法: RetentionPolicy value() 保留策略,也就是生命周期,有三种生命周期
public enum RetentionPolicy {
//源码上,也就是 .java文件
SOURCE,
//字节码文件上, .class文件
CLASS,
//运行时期,虚拟机内
RUNTIME
}
5、@Target目标
@Target中有一个方法,ElementType[] value(),可标注的元素类型,总共有十个:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
//类、接口(包括注解)、枚举
TYPE,
/** Field declaration (includes enum constants) */
//字段(包括枚举常量字段)
FIELD,
/** Method declaration */
//方法上
METHOD,
/** Formal parameter declaration */
//参数上
PARAMETER,
/** Constructor declaration */
//构造器上
CONSTRUCTOR,
/** Local variable declaration */
//局部变量
LOCAL_VARIABLE,
/** Annotation type declaration */
//注解类型
ANNOTATION_TYPE,
/** Package declaration */
//包上,基本不使用
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
//类型参数,例如: List<@MyAnnotation T>
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
//个人理解的不是很全面,可以用在泛型以及泛型边界上
//List<@MyAnnotation T extends @MyAnnotation Number>
TYPE_USE
}
注解中方法可以返回的类型
1、基本类型及其数组类型
基本类型包括四类八种,整型:byte,short,int ,long;浮点型:float,double;字符型:char;布尔型:boolean
2、引用类型及其数组类型
引用类型有限制,只能使用:String,Class,Enum,Annotation(字符串,Class对象,枚举,注解)
注意
- 注解的方法不能有参数
- 可以使用default指定默认值, String name() default “zs”;
- 注解中可以声明常量以及内部类或接口,和普通的接口一样
import java.lang.annotation.*;
@Inherited
@Documented
@Repeatable(Another.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD})
public @interface MyAnnotation {
String name() default "zs";
//声明一个常量,默认为public static final
String name = "zs";
//声明一个类,默认为public static
class Cat{
}
}
注解原理
首先把注解标注在UserMapper上,当然这个注解的保留策略需要是运行时(RUNTIME)
@MyAnnotation
public interface UserMapper {
}
进行测试
@Test
public void testAnnotationClass(){
//把注解标注在UserMapper上
MyAnnotation annotation = UserMapper.class.getAnnotation(MyAnnotation.class);
if (annotation != null) {
System.out.println(annotation.getClass());
}
}
输出的结果是:class com.sun.proxy.$Proxy5,所以注解的原理就是JDK的动态代理
解析注解
首先我们要对解析的注解做一些约定,表明我们解析注解的用途。
例如:解析方法上的注解,用于解析方法参数中的占位符,当然可以根据实际情况进行其他的约定
- 注解可以声明在方法上
- 生命周期是运行时
- 有一个name方法,返回一个字符串
//name是一个占位符#{name}
@MyAnnotation(name = "ls")
public void process(String name) {
//..............
}
//处理方法上的注解,假设这一步已经拿到process方法
public String parse(Method method) {
//获取注解上的全部方法
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
String parse = parse(annotation);
if (parse != null) {
return parse;
}
}
return null;
}
//解析注解
public String parse(Annotation annotation) {
try {
//1、拿到name方法,注解中的方法是不允许有参数的
Method method = annotation.getClass().getDeclaredMethod("name");
//2、执行value方法
Object invoke = method.invoke(annotation);
//3、如果返回值是String类型,则返回,否则返回null
if (invoke instanceof String) {
return (String) invoke;
} else {
return null;
}
} catch (Exception e) {
//没有name方法返回null
return null;
}
}
我们不需要知道注解的类型,或者注解中有哪些方法,只要这些注解按照我们的约定就行。
可以利用jdk的动态代理或者cglib代理,生成代理类对方法参数进行拦截,设置参数为我们解析的类型,如果没有解析到,则使用原来的参数即可
总结
随着时间段推移,注解使用的也越来越广泛,所以知道注解用法及如何解析对我们开发者而言是很有帮助的,我只是对注解做了简单的介绍,考虑到实际的业务场景,解析注解还是非常繁琐的,但是万变不离其宗,注解就是jdk动态代理生成的普通的java类