[ Java基础系列 ] 注解入门指南 ! 造轮子必备知识 !

174 阅读4分钟

Java 注解入门指南

1. 基本注解

常见的基本注解 :

  • @Override : 重写方法的注解
  • @Deprecated : 标识过期的注解
  • @SfeVarargs : Java 7 中引入 , 抑制堆污染的注解
  • @SuppressWarings("unchecked") : 抑制编译器警告
  • @FunctionalIntrerface : 标识函数式接口 , 常用于Lambda表达式

2. 元注解

元注解 : 元注解修饰注解 , 而元注解也是一种注解 (套娃警告) , 它们是一种专门用于修饰非元注解的注解

上图可以看到有两个注解修饰了 @Override , 它们就是其中的两个元注解

JDK 5.0 提供了 4 个元注解 :

  • @Retension
  • @Target
  • @Documented
  • @Inherited

@Retension

@Retension 限定注解的生命周期

其中取值有 :

  • SOURCE : 该注解只会保留在源文件中 , 被编译成 CLASS 文件就消失
  • CLASS : 该注解会保留在 CLASS 文件中 , 在运行时 JVM 无法获取注解信息 , 是该注解的默认值
  • RUNTIME : 该注解会跟随到运行时状态 , JVM 可以获取到该注解信息 , 程序也可以获取到注解信息作额外操作

示例 :

/**
  * 自定义用户登录注解 , 运行时被该注解标识的方法会判断用户是否登录
  */
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

由于 @UserLoginToken 被 RUNTIME 修饰 , 所以在运行时可以通过反射获取到该注解的属性值 , 作额外的逻辑处理

@Target

@Target 指定被修饰的注解能用于修饰哪些作用域

其中取值有 :

  • ANNOTATION_TYPE :只能修饰注解
  • CONSTRUCTOR : 只能修饰构造器
  • FIELD : 只能修饰成员变量
  • LOCAL_VARIABLE : 该策略的注解只能修饰局部变量
  • METHOD : 该策略的注解只能修饰方法定义 (常用于自定义注解)
  • PACKAGE : 只能修饰包定义
  • TYPE : 该注解可以修饰 类 , 接口 (包括注解类型的接口) , 或者枚举定义

@Target 可以同时指定多个作用域 , @UserLoginToken 被标注只能作用再方法和类上 , 示例 :

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

@UserLoginToken 由于被 METHODTYPE 修饰 , 所以该注解可以修饰 方法 , , 接口 等作用域

@Documented

@Documented 用于指定被该元注解修饰的注解将被 javadoc 工具提取成文档

示例 :

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserLoginToken {
    boolean required() default true;
}

添加了 @Documented , 就会被 javadoc 工具抓取到文档中

@Inherited

被 @Inherited 注解修饰的注解将具备 继承性 , 即如果某个类使用了被 @Inherited 修饰的注解A , 那么子类也会具备该注解A

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface UserLoginToken {

    boolean required() default true;

}

UML 类图结构 :

下面这段代码将会输出 true , 代表子类 Sub 上存在 @UserLoginToken注解

@UserLoginToken
public class Base {

}

public class Sub extends Base {
    public static void main(String[] args) {
        // true
        System.out.println(Sub.class.isAnnotationPresent(UserLoginToken.class));
    }
}

如果 @UserLoginToken 去掉 @Inherited 注解 , 程序会输出 false

3. 获取注解信息的方法

AnnotatedElement 接口是所有程序元素的父接口 (类 , 接口 , 方法 , 成员等) , 通过反射获取到该对象后 , 可以利用如下方法访问注解信息 :

4. 注解实战案例

通过自定义注解 @UserLoginToken 判断用户是否已登录

// 用户登录验证注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {

    boolean required() default true;

}
// 允许匿名访问注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}

拦截器拦截请求 :

public class MemberAuthenticationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 如果不是映射到方法直接通过
        if(!(object instanceof HandlerMethod)){
            return true;
        }
        // 强转成对应方法
        HandlerMethod handlerMethod=(HandlerMethod)object;
        Method method=handlerMethod.getMethod();
        //检查是否有@Passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            // 获取该方法上的注解 , 并获取它的属性值
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 执行认证
            }
        }
        return true;
    }
}