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         }