java自定义注解研究

1、注解到底是干啥用的?

在编写java项目的时候我们用到了很多各种各样的【注解】,虽然我们用得很频繁、用起来很方便,但是有没有人去认真思考,注解到底是干什么的?

那么我们最开始学java的时候最早用到的注解应该就是【重写:@Override

当我们写了一个抽象父类,里面有一个抽象方法,那么继承他的子类就必须得【重写】这个抽象方法,那么我们知道要重写一个抽象方法,就得加一个【@Override】。当然其实你不加也行,只要你能乖乖把重写方法的方法名写对、一样就行,但是我们更倾向于加上一个【@Override】,因为这样一来开发者能知道你这是一个重写方法,“编译器程序”也会知道这个是一个重写方法。

当我们加了之后,就必须完整写完一个抽象方法重写,错了就会强制爆红线:

那么有人问为什么不写个注释://这是一个抽象方法的重写 ,这样也能区别出这个是一个重写方法了啊。

But,注释并不会运行到程序中,那就仅仅只是我们人眼睛能看到的这个一块的地方的解释而已。只有【注解】才是给程序看的,打比方:刚刚的@Overider就是编译器检测到问题;@RestController告诉spring程序:“我是一个接口类”;@SpringBootApplication告诉整个java项目:“我是启动类,扫描的时候从我这里启动”......当有了这些注解,java程序就会知道噢这是干啥的,该用它干些啥。

简单打比方就是:

注释】是老板平时对员工们的印象、外号啥的,可能见到这个人有一点印象才知道他是干啥的,但是假设我是个扫地清洁工,明天我换套西装坐他位置上可能他也不会觉察出有啥问题;

注解】是他的职称、身份识别,财务统计发工资的时候就会很明确这个叼毛是干啥的,该发多少,哪怕我穿着黄金来公司,他也知道我这个月缺勤30天、是一个清洁工,工资只发-200......

2、什么是自定义注解

那么基于【注解】的理解,自定义的注解就是我们自己根据自己的需求想法,给程序看的一个规范,打个比方我写了一个【用户类】,我希望程序编译器能看得懂我的name是姓名、age是年龄、sex性别里0代表女1代表男......

而且自定义注解定义的类的属性值是可以设置默认值的,当外界创建这个类的实例化对象时不传参数值,也会有默认值

当我们可以自己自定义一个注解的时候就会特别方便

3、怎么用自定义注解

一、元注解

在创建自定义注解的时候一般得有两个元注解:【@Taget】和【@Retention】

【@Taget】

作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
可选的参数值在枚举类ElemenetType中包括:

TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上

一般我们都是用到类、接口、方法上

那么单个作用域参数时可以:【@Target( ElementType.作用域类型 )】

也支持多个作用域参数,支持传入一个对象参数,里面参数用逗号分隔【@Target( { E1,E2 } )】

例如下面这个自定义注解,支持作用于 “类和接口、方法” 上

【@Retention】

作用:定义该注解的生命周期(有效范围)。
可选的参数值在枚举类型RetentionPolicy中包括

SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取注解。

通常我们不知道生命周期有效范围的话,你直接写【@Retention( RetentionPolicy.RUNTIME )】就行了

【@Documented】

这个可加可不加,一般还是顺手加上去吧,他的作用就是把自定义注解映射到生成的阅读文档里,我们刚学java的时候应该知道有一个javaAPI阅读文档可以在本地配置生成,利用的是javadoc这个工具,但是这个工具默认不生成自定义注解的文档解析,那么加了【@Documented】就可以映射到阅读文档了

二、自定义注解语法

定义注解

首先在普通的类的基础上,把【class】改成【@interface】,也就是【public @interface 注解名】

然后注解有两种格式,有默认值和无默认值:

格式1:数据类型 属性名();

不设置默认值,那么使用改注解的时候必须把里面没有默认值的属性传入参数值

元注解
public @interface 注解名称{
	属性数据类型 属性();
}

//例子
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation{
	String value();
}


格式2:数据类型 属性名() default 默认值;

可以设置默认值,那么使用这个自定义注解的时候可以不传有默认值的属性参数

元注解
public @interface 注解名称{
	属性数据类型 属性() defalut 默认值;
}

//例子
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation{
	String value() defalut "Hello word!";
}

使用注解

针对自定义里的属性名,也有两种使用格式

格式1:当自定义里的属性名不是【value】时,( )括号里就要写【属性 = 值】

// 以这个自定义注解为例子
// @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
// @Retention(RetentionPolicy.RUNTIME)
// public @interface MyAnnotation{
//  	String 属性();
// }

//作用于类上的注解
@自定义注解(属性 = 值)
public class 类名{
    ......
}

//或者作用于方法的注解
public class 类名{
    @自定义注解(属性 = 值)
    poublic void 方法1(){
        ......
    }
    
    @自定义注解(属性 = 值)
    poublic int 方法2(){
        ......
    }
    ......
}

//或者作用于变量上的注解
public class 类名{
    @自定义注解(属性 = 值)
    private String 变量1;

    @自定义注解(属性 = 值)
    private int 变量2;
    ......
}

格式二:如果你的自定义注解里有叫【value】的属性名,你就可以省略写【属性 = 值】,直接写成【值】就行,【值】==【value = 值】

// 以这个自定义注解为例子
// @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
// @Retention(RetentionPolicy.RUNTIME)
// public @interface MyAnnotation{
//  	String value();
// }

//写上value也行
@自定义注解(value = 值)
public class 类名{
    ......
}

//或不写也可以
public class 类名{
    @自定义注解(值)
    private String 变量1;

    @自定义注解(属性 = 值)
    private int 变量2;
    ......
}

然后切记了:

1、没默认值的,使用注解时必须给参数值

2、ElementType.TYPE、 ElementType.METHOD、ElementType.FIELD...这些类型千万别搞混,比如下面例子

使用注解的方法

首先我们写一个自定义注解,就叫”MyAnnotation“,作用域是成员变量、方法

import java.lang.annotation.*;

//设置作用域是方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    // 多位作者
    String[] authors() default {"J.K.罗琳", "张三", "李四"};
    // 书名
    String value() default "哈利波特";
    // 价格
    double price() default 100;
}

然后我们创建一个类,在类里的写两个方法,一个【加上自定义注解】,一个【不加】,这个类就叫 “testMyAnnotation”

public class testMyAnnotation {
    //有注解的被调用执行,并获取注解中的值
    @MyAnnotation(value = "《Manba out!!》", authors = {"岑梓铭","科比"})
    public void test01() {
        System.out.println("这是有注解的方法");
    }
    //没有注解的不执行
    public void test02() {
        System.out.println("没有注解的方法");
    }
}

然后我们在一个测试类里运行测试一下

这里补充的知识:

        当我们想获取一个类的所有方法的时候,需要利用【反射】这个知识点,自己去查,我这里只简单讲讲。

        首先我们需要导入一个包:【import java.lang.reflect.Method;

        然后用【反射】获取到一个类的【Class对象】,有三种方式(用我们刚刚的 “testMyAnnotation” 来举例吧)

方式一:Class c = 类名.class;

//获取Class对象1:
Class classes = testMyAnnotation.class;

方式二:Class c = 对象名.getClass;

//获取Class对象2:
testMyAnnotation obj = new testMyAnnotation();
Class classes = obj.getClass();

方式三:Class c = Class.forName("全限定包名");

//获取Class对象3:
Class classes = Class.forName("com.sky.testMyAnnotation");

其中"全限定包名"这样获取:

然后获取到Class对象之后,我们就可以用【Method[ ]】这么一个数组接收这个类的【所有方法】,其中包括【他的父类的方法】

语法格式就是:【Method[ ]  变量  =  class对象.getMethods( )】

Method[] methods = classes.getMethods();

 做完这些准备,最后就可以循环这个类里所有的方法,然后获取到我们【设置了注解的方法】了。因为所有方法里并不是所有方法都设置了注解,而且也有可能没有设置注解的方法,那就会有异常,我们就可以加一个【抛出异常处理】

并进行判断,当有注解了的方法,才去执行【注解方法】,那么判断的方法有两种,

1、方法1

在循环中用【方法.getDeclaredAnnotation( 自定义注解.class )】获取到【自定义注解对象

然后通过判断这个【自定义注解对象】是不是【null】,不是就代表是加了注解的方法,最后判断是加了注解的方法之后,就可以调用【方法.invoke( )】来执行加了注解的方法

2、方法2

在循环中用【方法.isAnnotationPresent( 自定义注解.class )】获取到一个【布尔值】,是true就代表这个方法是加了注解的方法,最后判断是加了注解的方法之后,就可以调用【方法.invoke( )】来执行加了注解的方法

最后还可以把自定义注解的属性打印输出,看看我们刚刚给加了注解的方法的注解里传的值成功了没有

完整代码:

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        //利用反射获取成员方法
        //获取Class对象1:
        // Class classes = Class.forName("com.sky.testMyAnnotation");
        //获取Class对象2:
        // Class classes = testMyAnnotation.class;
        //获取Class对象3:
        testMyAnnotation obj = new testMyAnnotation();
        Class classes = obj.getClass();

        Method[] methods = classes.getMethods();

        //执行含有注解的方法
        for (Method m : methods) {
            //从方法中获取含有指定注解
            //第一种【加了注解的方法】的判断方式
            MyAnnotation my = m.getDeclaredAnnotation(MyAnnotation.class);
            if (my != null) {
                //获取注解的参数
                System.out.println("书名为:" + my.value());
                System.out.println("作者为:" + (my.authors())[0]);
                System.out.println("价格为:" + my.price());
                //执行含有注解的方法
                m.invoke(obj);
            }

            //第二种【加了注解的方法】的判断方式
            boolean hasAnnotation = m.isAnnotationPresent(MyAnnotation.class);
            if (hasAnnotation) {
                System.out.println("\n书名为:" + my.value());
                System.out.println("作者为:" + (my.authors())[0]);
                System.out.println("价格为:" + my.price());
                m.invoke(obj);
            }
        }
    }
}

那么最后,自定义注解他的应用场景通常都是数据加密校验的,一般企业的后端项目一般会有这个目录【annotation】就专门放一些数据校验的自定义注解,这些以后再说

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值