JAVA基础5--注解的实现原理
一、注解简介
注解也叫元数据,是 JDK1.5 版本开始引入的一个特性,用于对代码进行标记说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解修饰
1.1、注解的类型
1、JDK 注解和框架注解:JDK 本身提供了很多注解比如 @Resource、@PostConstruct 等;另外常用的框架也提供了很多注解,比如 Spring 的 @Autowired,@Service 等等, 这些注解使用时会自动被 JDK 或框架进行识别解析;
2、元注解:元注解用于修饰注解的,如 @Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成 javadoc 文档 )
3、自定义注解:用户可以根据自行需求自定义注解
1.2、元注解
@Retention:定义注解的生命周期,默认是 CLASS,取值范围如下:
SOURCE | 在编译阶段被抛弃,通常用于编译时使用,比如 @Override 注解 |
CLASS | 在编译阶段会被写入字节码,当类加载的时候会被丢弃 |
RUNTIME | 不会被丢弃,运行期间也可以使用,所以通过反射机制就可以读取该注解的信息,通常自定义注解都会采用 RUNTIME 类型 |
@Target:定义注解可以修饰的目标,默认是可以修饰任意目标,取值范围如下
TYPE | 用于描述类、接口或 enum 声明,如 @Service、@Component 等 |
FIELD | 用于描述属性,比如 @JSONField 等 |
METHOD | 用于描述方法,比如 @Override |
PARAMETER | 用于描述方法参数,比如 Mybatis 框架中的 @Param |
CONSTRUCTOR | 用于描述构造函数 |
LOCAL_VARIABLE | 用于描述局部变量 |
ANNOTATION_TYPE | 用于描述注解类型,比如 @Target 本身,@Retention,@Document 注解等 |
PACKAGE | 用于描述包名 |
TYPE_PARAMETER | 用于描述参数类型 |
TYPE_USE | 表示该注解能使用在使用类型的任意语句中 |
@Inherited:定义注解是否继承给子类
当 @Inherited 注解修饰了一个注解,那么如果这个注解修饰了一个类,那么这个类的子类也会继承该注解
@Documented:定义注解是否将注解信息加到 Java 文档中
1.3、注解的组成
注解通常有几个部分组成,包括修饰该注解的元注解,注解名称和注解方法,当然也可以将注解仅当作标记作用,没有任何方法也行,比如 @Override 注解就没有任何方法,仅当作标记使用
二、注解的使用
通常我们 Web 服务提供接口需要用户登录之后才可以访问,此时如果每个接口都判断下用户是否登录就会冗余很多的代码,所以需要在执行接口方法之前有一层验证用户登录的逻辑,可以通过过滤器,拦截器等方式实现,此时就可以配合注解来实现,在需要进行登录验证的方法上添加一个自定义的注解,然后每个添加了注解的方法就需要验证登录,没有注解的方法就不需要登录,实现方式如下:
自定义注解 @Logined
@Documented @Target(ElementType.METHOD) /** 修饰方法*/ @Retention(RetentionPolicy.RUNTIME)/** 生命周期为运行期间*/ public @interface Logined {</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 定义方法,如果没有登录的情况下是否直接抛异常</span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> exception() <span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
定义了注解之后就可以直接使用,但是想要使注解的效果生效,就需要有一套获取注解并处理业务的逻辑,此时就离不开 Java 的反射机制,需要通过反射机制获取到修饰在方法、类、属性上的注解来进行判断是否加了注解。
另外在使用 Spring 框架时,可以配置 AOP 来配合使用自定义注解,比如以下案例就是用来处理 @Logined 注解的逻辑:
@Aspect @Component public class LoginAspect {<strong><span style="color: rgba(255, 0, 0, 1)">@Around(</span></strong></span><strong><span style="color: rgba(255, 0, 0, 1)">"@annotation(com.test.annotation.Logined)") </span></strong><span style="color: rgba(0, 0, 255, 1)">public</span> Object doBefore(ProceedingJoinPoint jp) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (MessageConfig.LOCAL.get() == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { System.out.println(</span>"请求用户为空,返回401:" +<span style="color: rgba(0, 0, 0, 1)"> jp.getSignature().getName()); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Result.returnUnauthorized(); } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> jp.proceed(); }
}
三、注解的实现原理
注解本身没有任何逻辑,只能起到标记的作用,实现的逻辑完全取决于处理注解的逻辑,而处理注解就需要先找到注解,此时就离不开 Java 的反射机制,主要是通过 Constructor、Class、Method、Field 等反射相关类的 getAnnotation(Class annotationClass) 方法获取对应的注解,如果能获取到注解那么就表示被注解修饰了,案例如下:
1 /** 1. 查找类上的注解 */ 2 Annotation classAnnotation = cla.getAnnotation(Logined.class); 3 if(classAnnotation != null){ 4 System.out.println("类被 @Logined 注解修饰"); 5 } 6 7 /** 2. 查找方法上的注解 */ 8 Method[] methods = cla.getMethods(); 9 for (Method method : methods){ 10 if(method.getAnnotation(Logined.class) != null){ 11 System.out.println("方法:" + method.getName() + "被注解 @Logined" + "修饰"); 12 } 13 } 14 15 /** 3. 查找属性上的注解 */ 16 Field[] fields = cla.getFields(); 17 for (Field field : fields){ 18 if(field.getAnnotation(Logined.class) != null){ 19 System.out.println("属性:" + field + "被注解 @Logined" + "修饰"); 20 } 21 } 22 23 /** 4. 查找构造函数上的注解 */ 24 Constructor constructor = cla.getConstructor(String.class); 25 if(constructor.getAnnotation(Logined.class)!=null){ 26 System.out.println("构造器被 @Logined 注解修饰"); 27 }