1. 注解是什么

首先,我们先来康康注解在百度百科上的解释
面试官:注解五问你怕了吗?-LMLPHP

而在 Java 中,简单通俗的讲,就是一个标签,对类、方法、变量的一个解释说明,在早些年,我们通常使用 xml 去对我们的代码进行增强的解释,但是格式繁杂,代码可读性差,维护起来很困难,在 Java SE 5.0 以后,注解的出现为这种情况得到了改善,越来越多的开源项目开始使用注解,抛弃了 xml 。
xml 就像一段代码的补充解释和说明,是一段单独的文档,比如我们 Spring 项目中使用 xml 配置 Bean 的作用域,而注解是写在代码旁边,对代码进行标记和进行进一步的解释。

  • xml 配置 Bean
<bean name="user" class="shanhe.show.User" scope="prototype"
</bean>
  • 注解配置 Bean
@Bean
public class User{}

2. 注解该怎么用

我们使用注解的方法非常的简单,可以分别这样去用

@Data
public class User{}

方法

@Override
public String print(){}

变量

@Notnull
private String id;

参数

String getIdByName(@Param("name") String name);

其使用的简单和方便其实也是我们选择使用的注解的最大原因:)

3. 自定义注解

要想真正的理解注解的实现原理,首先我们要学会自己去实现一个自定义的注解,当我们得到一个自己的注解之后,相信可以对注解的理解有更为深刻的认识。
自定义注解之前,我们先来认识几个定义注解的注解——元注解
@Target
@Retention
@Docuemented
Inherited
通过这四个元注解,我们就可以去自定义一个我们想要的注解,首先来分别解释一下,这四个元注解在构建自定义注解的时候起到的作用
@Target正如它的名字那样,它是用于限定这个自定义注解能够应用的 Java 元素,在这个注解中维护着一个枚举类:

public enum ElementType {
    /** 类,接口(包括注解类型)或枚举的声明 */
    TYPE,

    /** 属性的声明 */
    FIELD,

    /** 方法的声明 */
    METHOD,

    /** 方法形式参数声明 */
    PARAMETER,

    /** 构造方法的声明 */
    CONSTRUCTOR,

    /** 局部变量声明 */
    LOCAL_VARIABLE,

    /** 注解类型声明 */
    ANNOTATION_TYPE,

    /** 包的声明 */
    PACKAGE
}

其使用的方法如下

@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface AnnotionDemo {
    String value();
}

@Retention注解是对自定义注解的生命周期进行限定,分为了源文件、编译期、运行期这三类,同样的,它也有一个好搭档——枚举类去维护这三个阶段。

public enum RetentionPolicy {
    /**
     * 注解将被编译器忽略掉
     */
    SOURCE,

    /**
     * 注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为
     */
    CLASS,

    /**
     * 注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

我们实际开发中使用自定义注解的时候,最常使用的还是RUNTIME类型,我们可以通过另外一个神器——反射去获取到我们自定义注解的相关内容,从而对这些不同的内容进行不同的判断,后面项目实战部分,会举例说明实际使用的方法

@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotionDemo {
    String value();
}

@Document注解相对来说就比较简单了,它只是用来指定自定义注解能否跟随着它被使用的 Java 文件一起生成到 JavaDoc 中,就目前来看,这个元注解对于我们的作用并不是很大。
@Inherited的使用则是有条件限制,只有当ElementTypeTYPE的时候才会生效,而它的作用就是将父类的作用域扩充到子类中,是子类可以去继承原本处于父类上的注解。
所以综上所述,我们就可以运用元注解去自定义出一个属于我们自己的注解:

@Document
@Inherited
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotionDemo {
    String value();
}

4. 注解实现原理

首先,在 Java 的文档中,我找到了这样的一句话:

@Document
@Inherited
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public interface AnnotionDemo extends Annotation {
    String value();
}

那么我们是使用注解的时候,怎么去给注解中的value赋值呢?
我们使用该注解后,通过反编译后的代码,我们可以找到(这里就不贴出反编译后的代码了,浪费空间,大家知道怎么回事就好~)在堆内存中有一个代理对象,大概长下面这样

public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements java.lang.annotation.Annotation

然后在这个代理类中去完成了对value的赋值,而执行这一系列动作的是一个叫做AnnotationInvocationHandler的东西,它完成了在运行期间生成动态代理对象的操作,整体的流程大概是这样的
面试官:注解五问你怕了吗?-LMLPHP

5.在项目中我们如何去使用注解?

下面,我们通过一个实际应用的一个小🌰去看一下

我们在使用系统的时候,通常会有权限的控制,在项目中,我们会在 gateway 中去设置过滤器,通过过滤请求之中的token,获取对应的用户信息,从而拿到用户的权限,完成对权限的控制,但是有一些接口是处于非登录状态(即没有token的时候)也需要去访问的,而这些接口并非固定一成不变的,这个时候,我们就需要一个标志,也就是注解去注明,哪些接口的访问是无需进行权限的,相当于颁发了一个绿牌,可以跳过权限的控制。
面试官:注解五问你怕了吗?-LMLPHP
自定义@Pass注解

/**
 * 既可以作用于方法上,也可以作用于类上,作用于类上时,该类下的所有接口均可跳过
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pass {
    boolean required() default true;
}

处理方式

// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
    return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有pass注释,有则跳过认证
if (method.isAnnotationPresent(Pass.class)) {
    Pass pass = method.getAnnotation(Pass.class);
    if (pass.required()) {
        return true;
    }
}

在拦截器中获取Method方法,通过反射去获取注解中的值,这样就可以跳过过滤直接去访问接口啦,具体使用方法如下:

@Pass
@GetMapping("hello")
public String hello(){
    return "hello";
}

相信我聪明的读者一定可以举一反三,使用注解去巧妙的实现更多的功能,本次注解的相关内容就到此为止了~

如果你有学到,请给我点赞+关注,原创不易,且看且珍惜。

11-03 15:33