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】就专门放一些数据校验的自定义注解,这些以后再说