【Java 注解】自定义注解(注解属性与使用)

本文详细介绍了Java注解的使用,包括自定义注解的属性类型,元注解的作用,注解的生命周期(SOURCE,CLASS,RUNTIME)和作用目标(TYPE,METHOD等)。通过实例展示了如何在AOP中使用注解进行异常处理,以及如何通过注解进行数据初始化和校验。文章还提到了注解属性赋值的简化规则。
摘要由CSDN通过智能技术生成


前言

Java注解是一种元数据(metadata)机制,它提供了在Java代码中添加附加信息的方式。注解可以应用于类、方法、字段和其他程序元素,以提供关于这些元素的额外信息。
注解以 @ 符号开头,后面跟着注解的名称。注解可以包含属性,属性可以带有默认值。使用注解时,可以为属性提供值,也可以使用默认值。
注解可以用于各种目的,例如:

  • 提供编译时的静态检查和验证。
  • 在运行时通过反射获取元数据信息。
  • 自动生成代码或配置文件。
  • 在框架和库中进行配置和扩展。
    Java提供了一些内置的注解,如 @Override@Deprecated@SuppressWarnings 。此外,开发人员还可以自定义注解,以满足特定的需求。
    通过使用注解,可以在代码中添加额外的信息,从而提供更多的灵活性和可读性,同时也可以为工具和框架提供更多的上下文信息。

一、自定义注解与元注解

分析 Java 中自带的 @Override 注解 , 源码如下 :

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

注解分为两部分 :

  1. 元注解:
    元注解是专门用来注解其他注解的注解,听起来有些绕口,实际上就是专门为自定义注解提供的注解。java.lang.annotation提供了五种元注解:
    @Documented – 注解是否将包含在JavaDoc中
    @Retention – 什么时候使用该注解
    @Target – 注解用于什么地方
    @Inherited – 是否允许子类继承该注解
    @Repeatable - 是否可重复注解,jdk1.8引入
  2. 自定义注解:public @interface 注解名称 ;
    除了上面的元注解其他的注解其实都是自定义注解。
    这里不准备深入介绍以上四个元注解的作用,大家可以自行学习。

本文即将提到的几个例子,都是在日常工作中真实使用到的场景,这例子有一个共同点,那就是都用到了Spring的AOP技术。

什么是AOP以及他的用法相信很多人都知道,这里也就不展开介绍了。

1.注解属性类型

注解属性 ( 接口方法 ) 返回值类型要求 :

① 基本数据类型 : byte , short , int , long , float , double , char , boolean ;

② 字符串类型 : String ;

③ 枚举类型 : enum ;

④ 注解类型 ;

⑤ 以上类型的数组形式 ;

注解属性返回值必须是以上的类型 , 不能设置其它类型返回值 , 否则会报错 ;

注解中定义了属性 , 在使用注解时 , 需要 给 注解属性 赋值;

定义 注解属性 时 , 可以 使用 default 关键字 指定属性默认值 , 下面代码中 , 制定 注解属性 intValue 值类型为 int 整型 , 默认值 88;

int intValue() default 88

如果 注解属性 指定了默认值 , 在使用注解时 , 可以选择 不为该属性赋值 ( 此时使用默认属性值 ) , 也可以进行赋值 ( 指定一个新的属性值 ) ;

如果 注解属性 没有指定默认值 , 则使用 注解 时 , 必须为其指定一个默认值 , 否则编译时报错 ;

数组类型 的 注解属性 赋值 时 , 使用大括号进行赋值 , 大括号内是数组元素 , 如果只有一个属性 , 可以省略大括号 ,

注解声明示例 :

public @interface Annotation {
    /**
     * 字符串类型
     * @return
     */
    String stringValue();

    /**
     * int 基本类型
     * @return
     */
    int intValue() default 88;

    /**
     * 枚举类型
     * @return
     */
    Number enumValue();

    /**
     * 注解类型
     * @return
     */
    Annotation2 annotationValue();

    /**
     * 字符串数组类型
     * @return
     */
    String[] stringArrayValue();
}

枚举类:

public enum Number {
    ONE, TWO, THREE
}

Annotation2 注解类 :

public @interface Annotation2 {
	// 自定义属性可以为空,只声明一个自定义注解
}

自定义注解使用:

/**
 * 注解生成文档
 *
 * @author xjw
 * @version  1.0
 * @since 1.0
 */
public class Example{
    /**
     * 构造函数
     * @param name 参数一
     * @param age 参数二
     */
     // 如果你的注解定义了属性,在引用时这些属性都是不可缺少的
    @Annotation(
            stringValue = "tom",
            enumValue = Number.ONE,
            annotationValue = @Annotation2,
            stringArrayValue = {"tom", "jerry"})
    Student(String name, int age){
    }

    @SuppressWarnings("all")
    @Override
    public String toString() {
        return super.toString();
    }
}

代码分析 : 重点关注注解的使用 , 使用注解时 , 需要给 没有默认值 的 注解属性 赋值 , 格式为 注解属性名称 = 对应类型属性值 , 如果 注解属性 有默认值 , 则

@Annotation(stringValue = "tom", enumValue = Number.ONE, stringArrayValue = {"tom", "jerry"})

二、注解的生命周期以及作用目标

1.生命周期

通过@Retention定义注解的生命周期,格式如下:

@Retention(RetentionPolicy.SOURCE)

其中RetentionPolicy的不同策略对应的生命周期如下:

RetentionPolicy.SOURCE : 仅存在于源代码中,编译阶段会被丢弃,不会包含于class字节码文件中。@Override, @SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS : 默认策略,在class字节码文件中存在,在类加载的时被丢弃,运行时无法获取到。
RetentionPolicy.RUNTIME : 始终不会丢弃,可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。

2.作用目标

通过@Target定义注解作用的目标,比如作用于类、属性、或方法等,默认可用于任何地方。格式如下:

@Target(ElementType.TYPE)

对应ElementType参数值适用范围如下:

  • ElementType.TYPE: 类、接口、注解、enum
  • ElementType.CONSTRUCTOR: 构造函数
  • ElementType.FIELD: 成员变量、对象、属性、枚举的常量
  • ElementType.LOCAL_VARIABLE: 局部变量
  • ElementType.METHOD: 方法
  • ElementType.PACKAGE: 包
  • ElementType.PARAMETER: 参数
  • ElementType.ANNOTATION_TYPE): 注解
  • ElementType.TYPE_PARAMETER:类型参数,表示这个注解可以用在 Type的声明式前,jdk1.8引入。
  • ElementType.TYPE_USE:类型的注解,表示这个注解可以用在所有使用Type的地方(如:泛型,类型转换等),jdk1.8引入。

Documented
@Documented,表示是否将此注解的相关信息添加到javadoc文档中。

Inherited
@Inherited,定义该注解和子类的关系,使用此注解声明出来的自定义注解,在使用在类上面时,子类会自动继承此注解,否则,子类不会继承此注解。注意,使用Inherited声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效。


三,简单使用

结合aop使用,完成一个当方法抛出异常时发送通知:

package com.kimo.shop.admin.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 异常捕获切面
 * author:xjw💕
 * time:2023.07.20 10:52
 * */
@Aspect
@Component
public class AbnormalAspect {

    @AfterThrowing(value = "@annotation(com.kimo.shop.admin.aop.AbnormalAnnotations)",throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint,Exception exception){

        // get joinPoint methodName
        String methodName = joinPoint.getSignature().getName();
        // get joinPoint className
        String className = joinPoint.getTarget().getClass().getName();
        // Get the exception information
        String exceptionMessage = exception.getMessage();
        // // Perform other operations, such as querying switch status and sending notifications
        System.out.println("方法 " + methodName + " 在类 " + className + " 中抛出了异常");
        System.out.println("异常信息:" + exceptionMessage);

// 当你需要从自定义注解里面取出属性,可以看下面的方法
//        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//        Method method = methodSignature.getMethod();
//        AbnormalAnnotations annotation = method.getAnnotation(AbnormalAnnotations.class);
//        System.out.println("发生异常时间:" + annotation.date());
    }
}

第二个示例:通过注解进行赋值和通过注解进行校验。
两个注解:一个用来赋值,一个用来校验。

/**
 * 性别赋值
 * @author xjw
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface InitSex {
    enum SEX_TYPE {MAN, WOMAN}
    SEX_TYPE sex() default SEX_TYPE.MAN;
}
/**
 * 年龄校验
 * @author xjw
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface ValidateAge {
    /**
     * 最小值
     */
    int min() default 18;
    /**
     * 最大值
     */
    int max() default 99;
    /**
     * 默认值
     */
    int value() default 20;
}

定义数据模型
这里用User类来表示具体待处理的数据对象。

/**
 * user
 * @author xjw
 */
public class User {
    private String username;
    @ValidateAge(min = 20, max = 35, value = 22)
    private int age;
    @InitSex(sex = InitSex.SEX_TYPE.MAN)
    private String sex;
    // 省略getter/setter方法
}

测试调用
具体测试调用的过程,参考代码中的注解,其中initUser方法来演示通过反射给属性赋值,checkUser方法通过反射拿到当前属性的值进行对比校验。

import java.lang.reflect.Field;
/**
 * @author xjw
 */
public class TestInitParam {
    public static void main(String[] args) throws IllegalAccessException {
        User user = new User();
        initUser(user);
        // 年龄为0,校验为通过情况
        boolean checkResult = checkUser(user);
        printResult(checkResult);
        // 重新设置年龄,校验通过情况
        user.setAge(22);
        checkResult = checkUser(user);
        printResult(checkResult);
    }
    static void initUser(User user) throws IllegalAccessException {
        // 获取User类中所有的属性(getFields无法获得private属性)
        Field[] fields = User.class.getDeclaredFields();
        // 遍历所有属性
        for (Field field : fields) {
            // 如果属性上有此注解,则进行赋值操作
            if (field.isAnnotationPresent(InitSex.class)) {
                InitSex init = field.getAnnotation(InitSex.class);
                field.setAccessible(true);
                // 设置属性的性别值
                field.set(user, init.sex().toString());
                System.out.println("完成属性值的修改,修改值为:" + init.sex().toString());
            }
        }
    }
    static boolean checkUser(User user) throws IllegalAccessException {
        // 获取User类中所有的属性(getFields无法获得private属性)
        Field[] fields = User.class.getDeclaredFields();
        boolean result = true;
        // 遍历所有属性
        for (Field field : fields) {
            // 如果属性上有此注解,则进行赋值操作
            if (field.isAnnotationPresent(ValidateAge.class)) {
                ValidateAge validateAge = field.getAnnotation(ValidateAge.class);
                field.setAccessible(true);
                int age = (int) field.get(user);
                if (age < validateAge.min() || age > validateAge.max()) {
                    result = false;
                    System.out.println("年龄值不符合条件");
                }
            }
        }
        return result;
    }
    static void printResult(boolean checkResult) {
        if (checkResult) {
            System.out.println("校验通过···");
        } else {
            System.out.println("校验未通过···");
        }
    }
}

打印日志

完成属性值的修改,修改值为:MAN
年龄值不符合条件
校验未通过···
校验通过···

四,注解属性赋值简化

如果注解属性名称是 value , 并且注解中只有1个属性 , 那么在使用注解为注解属性赋值时 , 可以省略注解名称 , 直接传入注解属性值;

示例 : JDK 自带的 SuppressWarnings 注解:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

注解使用 : 使用 SuppressWarnings 注解时 , 直接传入 “all” 参数 , 省略了注解属性名称:

    @SuppressWarnings("all")
    @Override
    public String toString() {
        return super.toString();
    }

满足两个条件 , 才能使用上述简化方式 ;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值