18.注解
学习目标:
掌握注解的定义与使用
熟悉2个元注解的作用
掌握使用注解处理器获取注解信息
注解与注释
注释
- 单行注释//
- 多行注释/* */
- 文档注释/** */
注释的作用:
传递额外的信息进行解释说明, 给程序员看的
注释不参与编译
注释只有语法形式, 具体内容没有要求
// 年龄在18-25之间, [18,25], 18<=age<=25
boolean judegeAge(int age){
}
什么是注解
Annotation其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理
注解的作用
通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息
Annotation就像修饰符一样被使用,可用于修类、构造器、方法、成员变量、参数...,这些信息被存储在Annotation的“属性名=属性值”对中
注解 VS 注释
- 相同点
- 都是用来传递额外信息的
- 不同点
- 注解可以参与编译,注释不行
- 注解有使用范围,注释没有(想咋写咋写)
- 注解作为一种数据类型,跟class interface具有同等地位
注解定义
语法
权限修饰符 @interface 注解名字{
// 注解体定义
属性类型 属性名();
属性类型 属性名();
属性类型 属性名();
......
}
举例:
public @interface Override {
}
属性类型:
基本数据类型
String类型
Class类型
注解类型
枚举类型
以及以上类型的数组形式
注意:
注解不允许继承
注解和接口的关系:
注解和接口的定义都使用同一个关键字interface,而且注解体的定义也非常类似于接口中的抽象方法。
这当然不是巧合,而是因为注解本身就是一种特殊的接口。查看java.lang.annotation.Annotation接口的JDK文档,原文是:
所有注解类型都隐式扩展自该接口。但要注意,手动扩展该公共接口的接口不会定义为注解类型。还要注意此接口本身不是定义注解型。
也就是说:
- 当你使用"@interface"关键字定义一个注解类型时,它会自动实现java.lang.annotation.Annotation接口,即使你没有显式地声明这个继承关系。
- 如果使用"interface"关键字定义一个接口类型,并显式地让它继承java.lang.annotation.Annotation接口,那么这个新接口也不会被视为一个注解类型。
- java.lang.annotation.Annotation接口本身并不是一个注解类型,它只是一个普通的接口类型。
- 注解类型并不能显式地继承其他类或接口,虽然它确实隐式实现了接口java.lang.annotation.Annotation~
当然,以上概念了解即可。虽然注解和接口确实共享了同一个关键字,但它们在实际使用中具有不同的目的和功能,可谓千差万别
元注解
元注解的概念来源于元数据,什么是元数据呢?
图中框起来的一列数据是什么意思呢?表头的学校就负责解释这一列数据,所以这一列数据都代表某个学生的学校信息。
像“学校”这样的,用于解释数据的数据,就是元数据 meta data
元注解:描述修饰注解的注解(注解的注解)
常用元注解:
@Retention元注解,来定义我们自己定义的注解的保留级别.
- RetentionPolicy.RUNTIME
- RetentionPolicy.CLASS 默认
- RetentionPolicy.SOURCE
@Target元注解,注解可以作用的目标
对于注解而言,可以作用的目标:
- 整个类 ElementType.TYPE
- 成员变量 ElementType.FIELD
- 构造方法 ElementType.CONSTRUCTOR
- 成员方法 ElementType.METHOD
注解的使用(重点)
类比类对象
User user = new User("zs",20);
User user2 = new User();
使用的时候注解需要通过@符号进行实例化, 对每个属性都要赋值
@注解名(属性1=属性值,属性2=属性值)
解释:
- "@"可以认为相当于“new”关键字,必不可少。
- 注解相当于给Java代码打上一个标签,所以它必须要修饰Java代码的一个结构。比如修饰一整个类,一整个方法,一个成员变量等等。
- 实例化注解时,必须给注解的各个属性赋值,赋值方式是:属性名 = 属性值。如果是数组类型的注解属性,用"{}"赋值。如果有多个属性,赋值时用","隔开。
注意事项:
每个属性都要赋值
可以不赋值,但是要有默认值, default
数组形式赋值 {}
如果只有1个属性, 名字叫value, 可以简化赋值
如果属性类型是引用类型, 不能是null
示例
@MyAnnotation
public class Test {
@MyAnnotation(666)
int num;
@MyAnnotation(value = 777, b = "777", c = {"777","aaa","bb"})
public void test() {
}
}
@interface MyAnnotation {
int value() default 123;
String b() default "abc";
String[] c() default {"123"};
}
注解处理器
什么是注解处理器?
- 获取注解信息, 根据注解信息进行处理
如何获取注解信息?
- 通过反射获取注解信息
package _24annotation.com.cskaoyan._04handle;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
/**
* @description:
* @author: 景天
* @date: 2022/10/24 17:41
**/
public class Demo {
public static void main(String[] args) throws Exception{
// 获取字节码文件对象
Class<?> c = Class.forName("_24annotation.com.cskaoyan._04handle.Demo");
// 拿到方法对象
Method loginMethod = c.getDeclaredMethod("login");
// 判断方法上是否使用了注解
boolean annotationPresent = loginMethod.isAnnotationPresent(Login.class);
if (annotationPresent) {
// 获取注解实例
Login loginAnnotation = loginMethod.getAnnotation(Login.class);
// 获取属性值
String password = loginAnnotation.password();
String username = loginAnnotation.username();
// 打印
System.out.println(password);
System.out.println(username);
}else {
System.out.println("没有使用注解");
}
}
@Login
public static void login(){
}
}
// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Login{
// 属性
String username() default "admin";
String password() default "123456";
}
练习:
定义2个注解
AgeLimit 属性 maxAge minAge
NameLimit 属性 length
定义学生类Student 年龄18-25之间 名字长度不超过5
package _24annotation.com.cskaoyan._04handle;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* @description:
* @author: 景天
* @date: 2022/10/25 9:37
**/
public class StudentFactory {
public static Class stuCls;
static {
try {
stuCls = Class.forName("_24annotation.com.cskaoyan._04handle.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 提供一个方法 来产生学生对象
public static Student getInstance(String name,int age) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 名字校验
judgeName(name);
// 年龄校验
judgeAge(age);
// 获取构造方法对象
Constructor declaredConstructor =
stuCls.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
// newInstance实例化对象
Student student = (Student) declaredConstructor.newInstance(name, age);
return student;
// 最终返回学生对象
}
private static void judgeAge(int age) throws NoSuchFieldException {
// 获取age成员变量
Field ageField = stuCls.getDeclaredField("age");
// 判断是否用了注解
boolean annotationPresent = ageField.isAnnotationPresent(AgeLimit.class);
// 如果用了注解
if (annotationPresent) {
// 获取注解实例
AgeLimit ageLimit = ageField.getAnnotation(AgeLimit.class);
// 获取属性值
int maxAge = ageLimit.maxAge();
int minAge = ageLimit.minAge();
// 判断是否满足要求
// 如果不满足要求 抛出异常
if (age < minAge || age > maxAge) {
throw new IllegalArgumentException("年龄不合法");
}
}
}
private static void judgeName(String name) throws NoSuchFieldException {
// 获取name成员变量
Field nameField = stuCls.getDeclaredField("name");
// 判断是否用了注解
boolean annotationPresent = nameField.isAnnotationPresent(NameLimit.class);
// 如果用了注解
if (annotationPresent) {
// 获取注解实例
NameLimit nameLimit = nameField.getAnnotation(NameLimit.class);
// 获取属性值
int length = nameLimit.length();
// 判断是否满足要求
// 如果不满足要求 抛出异常
if (name.length() > length) {
throw new IllegalArgumentException("名字不合法");
}
}
}
}
注解VS配置文件
配置文件 优点:可配置,不用改源码。管理方便 缺点:不直观,开发效率低
注解 优点:直观开发效率高 缺点:硬编码,修改之后需要重新编译运行 难以和代码分开独立管理
注解的使用场景
SE : @Test @Override @Deprecated@FunctionalInterface
EE : @WebService
框架: @AutoWired @Service @Mapping @Data @Parm