Java小知识(七)、注解

377 阅读6分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

注解

从Java5 版本之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),是 Java 平台中非常重要的一部分。注解都是 @ 符号开头的,本质是一个继承了Annotation接口的接口(编译后)。

无论是哪一种注解,本质上都一种数据类型,是一种接口类型。到 Java 8 为止 Java SE 提供了 11 个内置注解。其中有 5 个是基本注解,它们来自于 java.lang 包。有 6 个是元注解,它们来自于 java.lang.annotation 包,自定义注解会用到元注解。

image.png

元注解

元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。Java 5 定义了 4 个注解,分别是 @Documented@Target@Retention@InheritedJava 8 又增加了 @Repeatable@Native 两个注解

@Documented

表示注解是否应当被包含在 JavaDoc 文档中。@Documented是一个标记注解,没有成员变量。 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。没什么实际作用,了解就好。

@Target

表示注解的作用目标。所修饰的对象范围: Annotation可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)

@Target(value = {ElementType.FIELD})

ElementType 是一个枚举类型

名称说明
CONSTRUCTOR用于构造方法
FIELD用于成员变量(包括枚举常量)
LOCAL_VARIABLE用于局部变量
METHOD用于方法
PACKAGE用于包
PARAMETER用于类型参数(JDK 1.8新增)
TYPE用于类、接口(包括注解类型)或 enum 声明

@Retention

表示注解的生命周期

@Retention(value = RetentionPolicy.RUNTIME

RetentionPolicy 依然是一个枚举类型

名称说明
SOURCE当前注解编译期可见,不会写入 class 文件
CLASS类加载阶段丢弃,会写入 class 文件
RUNTIME永久保存(运行时有效),可以反射获取

@Inherited

表示是否允许子类继承该注解。是一个标记注解。

使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。

@Repeatable

它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。

java 8 之前

public @interface Roles {
    Role[] roles();
}

public @interface Role {
    String value();
}

public class RoleTest {
    @Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
    public String doString(){
        return "java";
    }
}

java 8 之后

public @interface Roles {
    Role[] roles();
}

@Repeatable(Roles.class)
public @interface Role {
    String value();
}

public class RoleTest {
    @Role(roleName = "role1")
    @Role(roleName = "role2")
    public String doString(){
        return "java";
    }
}

@Native

使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。

基本注解

@Override

表示用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。

@Deprecated

可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告。

@SuppressWarnings

表示抑制编译器警告注解,使用了当前注解的方法,类及当前类的子类都会取消来自指定的类型警告

注解的使用有以下三种:

  1. 抑制单类型的警告:@SuppressWarnings("unchecked")
  2. 抑制多类型的警告:@SuppressWarnings("unchecked","rawtypes")
  3. 抑制所有类型的警告:@SuppressWarnings("unchecked")
关键字用途
all抑制所有警告
boxing抑制装箱、拆箱操作时候的警告
cast抑制映射相关的警告
dep-ann抑制启用注释的警告
deprecation抑制过期方法警告
fallthrough抑制在 switch 中缺失 breaks 的警告
finally抑制 finally 模块没有返回的警告
hiding抑制相对于隐藏变量的局部变量的警告
incomplete-switch忽略不完整的 switch 语句
nls忽略非 nls 格式的字符
null忽略对 null 的操作
rawtypes使用 generics 时忽略没有指定相应的类型
restriction抑制禁止使用劝阻或禁止引用的警告
serial忽略在 serializable 类中没有声明 serialVersionUID 变量
static-access抑制不正确的静态访问方式警告
synthetic-access抑制子类没有按最优方法访问内部类的警告
unchecked抑制没有进行类型检查操作的警告
unqualified-field-access抑制没有权限访问的域的警告
unused抑制没被使用过的代码的警告

@SafeVarargs

在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。

// 会有编译警告
display("10", 20, 30);

public static <T> void display(T... array) {
    for (T arg : array) {
        System.out.println(arg.getClass().getName() + ":" + arg);
    }
}

// 没有编译警告
    
display("10", 20, 30); 

@SafeVarargs
public static <T> void display(T... array) {
    for (T arg : array) {
        System.out.println(arg.getClass().getName() + ":" + arg);
    }
}

注意:@SafeVarargs注解不适用于非 static 或非 final 声明的方法,对于未声明为 static 或 final 的方法,如果要抑制 unchecked 警告,可以使用 @SuppressWarnings 注解。

@FunctionalInterface

@FunctionalInterface 就是用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。

在学习 Lambda 表达式时,我们提到如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口。

编译上面程序,可能丝毫看不出程序中的 @FunctionalInterface 有何作用,因为 @FunctionalInterface 注解的作用只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错

自定义注解

一般在我们程序中都是需要前端请求接口时在请求头中放入token进行校验,但是并不是所有接口都需要做这个校验,如登录接口,在用户还没登录时,前端是没有token的,所以就需要在特定接口上做一个白名单的过滤

下面的例子是自定义一个标记类型的注解,标记的接口方法就是一个白名单接口,在用户请求时,判断是白名单接口时则不需要进行token校验

/**
* 白名单注解,作用于方法
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WhiteList {
}

// 前置拦截器
@Component
public class WhiteListInterceptor extends HandlerInterceptorAdapter {

    /**
     * 检测是否白名单,fasle阻止,true放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (handler instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                Method method = handlerMethod.getMethod();
                PreventRepeatReq annotation = method.getAnnotation(WhiteList.class);
                if (annotation != null) {
                    // 是白名单
                    return true;
                }
            }
            return false;
    }
}

// 接口
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;
    
    @PostMapping("/login")
    @WhiteList
    public Result login(@RequestBody oginDTO loginDTO){
        userService.login(loginDTO);
        return Result.success();
    }
}