面试官:注解五问你怕了吗?
1. 注解是什么
首先,我们先来康康注解在百度百科上的解释
而在 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
的使用则是有条件限制,只有当ElementType
为TYPE
的时候才会生效,而它的作用就是将父类的作用域扩充到子类中,是子类可以去继承原本处于父类上的注解。
所以综上所述,我们就可以运用元注解去自定义出一个属于我们自己的注解:
@Document
@Inherited
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotionDemo {
String value();
}
4. 注解实现原理
首先,在 Java 的文档中,我找到了这样的一句话:
The direct superinterface of every annotation type is java.lang.annotation.Annotation
意思就说,我们不管是自定义的注解也好,JDK中原生的注解也好,都是继承自Annotation
这个接口的,也就是说我们上面自定义的注解经过了编译器编译后,大概是这个样子的
@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
的东西,它完成了在运行期间生成动态代理对象的操作,整体的流程大概是这样的
5.在项目中我们如何去使用注解?
下面,我们通过一个实际应用的一个小🌰去看一下
我们在使用系统的时候,通常会有权限的控制,在项目中,我们会在 gateway 中去设置过滤器,通过过滤请求之中的token,获取对应的用户信息,从而拿到用户的权限,完成对权限的控制,但是有一些接口是处于非登录状态(即没有token的时候)也需要去访问的,而这些接口并非固定一成不变的,这个时候,我们就需要一个标志,也就是注解去注明,哪些接口的访问是无需进行权限的,相当于颁发了一个绿牌,可以跳过权限的控制。
自定义@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";
}
相信我聪明的读者一定可以举一反三,使用注解去巧妙的实现更多的功能,本次注解的相关内容就到此为止了~
如果你有学到,请给我点赞+关注,原创不易,且看且珍惜。