注解大致介绍
首先,让我们来声明一个注解
代码语言:javascript复制
// 注解可以作用在哪里
@Target({ElementType.TYPE})
// 该注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
// 指示默认情况下,带有类型的注释将由javadoc *和类似工具来记录
@Documented
// 可以继承父类注解
@Inherited
// bean
@Component
public @interface DIYClassAnnotation {
DIYEnum diyEnum();
// 年龄默认24 岁
int age() default 24;
}
可以注意到,我们声明的这个注解,他自己又带着很多元注解,我们依此来解释下,对应可取的值也如下
- @Target : 指此注解可以标注在哪些地方,是字段?还是类?还是方法?
- TYPE :类,接口(包括注释类型)或枚举声明
- FIELD:字段声明(包括枚举常量)
- METHOD:方法声明
- PARAMETER:形式参数声明
- CONSTRUCTOR:构造函数声明
- LOCAL_VARIABLE:局部变量声明
- ANNOTATION_TYPE:注释类型声明
- PACKAGE:包声明
- TYPE_PARAMETER:类型参数声明
- TYPE_USE:使用类型
- @Retention :指该注解的生命周期,存活在哪个阶段
- SOURCE:批注将被编译器丢弃
- CLASS:注释将由编译器记录在类文件中,但不必在运行时由VM保留。这是默认的行为。
- RUNTIME:注释将由编译器记录在类文件中,并在运行时由VM保存*,因此可以通过反射方式读取它们
- @Documented :指是默认情况下,带有类型的注释将由javadoc *和类似工具来记录
- @Inherited :可以继承父类注解
- 里面的值只能用基本类型boolean、int、double、float、long、byte、short、char和String、Enum、Class以及一些其他注解
“我们一般写注解的时候。就是用图中上面那几个加粗颜色的属性和值
实战演练
- 其实使用这个自定义注解,千言万语就一句话
- 先声明一个自定义的注解
- 通过反射等方式取出这个注解,再根据这个注解中自己设定的值去做一些定制化的操作
- 本文将演示三种类型的自定义注解怎么用,平常开发也就这三种了(我接触的)
一、自定义类注解
代码语言:javascript复制@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Component
public @interface DIYClassAnnotation {
// 自定义枚举类型
DIYEnum diyEnum();
// 年龄默认24 岁
int age() default 24;
}
看一下这个枚举类型
代码语言:javascript复制
public enum DIYEnum {
xiaoJie("小杰","打代码"),
TEACHER("老师","教书"),
CHEF("厨师","做饭");
private String name;
private String worker;
DIYEnum(String name,String worker) {
this.name = name;
this.worker = worker;
}
public String getWorker() {
return worker;
}
public void setWorker(String worker) {
this.worker = worker;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
二、自定义字段注解
代码语言:javascript复制
// 注解到什么地方 属性 上
@Target({ElementType.FIELD})
// 该注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
// 指示默认情况下,带有类型的注释将由javadoc *和类似工具来记录
@Documented
// 可以继承父类注解
@Inherited
// bean
@Component
public @interface DIYFieldAnnotation {
// 性别
String sex();
}
三、自定义方法注解
代码语言:javascript复制
// 注解到什么地方 方法 上
@Target({ElementType.METHOD})
// 该注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
// 指示默认情况下,带有类型的注释将由javadoc *和类似工具来记录
@Documented
// 可以继承父类注解
@Inherited
// bean
@Component
public @interface DIYMethodAnnotation {
// 是否校验
int verification();
// 接口名称
String interfaceName();
}
- 其实我们注意到,感觉大家都大差不差啊,是的没错,也就是target作用域不一样罢了
我们自定义注解定义完了,下面要开始真正使用啦
- 定义个抽象父类Person
// 抽象父类
public abstract class Person {
public abstract void hobby();
}
- 定义学生Student子类
@DIYClassAnnotation(diyEnum = DIYEnum.xiaoJie,age=23 )
public class Student extends Person {
@DIYFieldAnnotation(sex = "男")
private String sex;
@Override
public void hobby() {
System.out.println(DIYEnum.xiaoJie.getWorker());
}
}
- 定义老师Teacher子类
@DIYClassAnnotation(diyEnum = DIYEnum.TEACHER,age=46 )
public class Teacher extends Person {
@DIYFieldAnnotation(sex = "女")
private String sex;
@Override
public void hobby() {
System.out.println(DIYEnum.TEACHER.getWorker());
}
}
- 定义厨师Chef子类
@DIYClassAnnotation(diyEnum = DIYEnum.CHEF,age=50 )
public class Chef extends Person {
@DIYFieldAnnotation(sex = "男")
private String sex;
@Override
public void hobby() {
System.out.println(DIYEnum.CHEF.getWorker());
}
}
再来一个注解工具类
代码语言:javascript复制
public class DIYAnnotationUtils {
public static Person getPerson(Person ...persons){
for (Person person:persons) {
// 判断这个类是否有这个注解
if (person.getClass().isAnnotationPresent(DIYClassAnnotation.class)){
// 得到这个自定义的注解
DIYClassAnnotation workerAnnotation = person.getClass().getAnnotation(DIYClassAnnotation.class);
// 判断这个自定义注解注解的值是否是我们想要的
if (DIYEnum.xiaoJie.getName().equals(workerAnnotation.diyEnum().getName())){
// 反射得到这个对象的属性
Field[] fields = person.getClass().getDeclaredFields();
for (Field field:fields) {
// 如果这个字段有这个注解
if (field.isAnnotationPresent(DIYFieldAnnotation.class)){
// 打印出这个属性上有这个注解的值
DIYFieldAnnotation annotation = field.getAnnotation(DIYFieldAnnotation.class);
System.out.println(annotation.sex());
}
}
return person;
}
}
}
return null;
}
}
最主要的就是这个工具类(用到反射),其中根据传进来的对象判断符合不符合我们的要求 (注解时的名字是不是小杰),如果符合的话,把注解在属性上的注解拿出来
- 我们通过测视类来调用一下
public class Test {
public static void main(String[] args) {
Student student =new Student();
Chef chef = new Chef() ;
Teacher teacher = new Teacher();
Person person = DIYAnnotationUtils.getPerson(student, chef, teacher);
if (person != null){
person.hobby();
}
}
}
输出结果是
代码语言:javascript复制男
打代码
- 下面我们来演示下怎么在方法上使用这注解,最常见的组合就是自定义注解 AOP
下面来看下controller
代码语言:javascript复制
@RestController
public class Controller {
// 此方法需要校验
@DIYMethodAnnotation(verification = 1,interfaceName = "学生爱好接口")
@RequestMapping("/verification")
public String verificationMethod(String id){
new Student().hobby();
return "校验";
}
// 此方法不需要校验
@DIYMethodAnnotation(verification = 0,interfaceName = "老师爱好接口")
@RequestMapping("/noVerification")
public String noVerificationMethod(String id){
new Teacher().hobby();
return "不校验";
}
// 此方法没有注解
@RequestMapping("/noAnnotation")
public String noAnnotationMethod(String id){
new Chef().hobby();
return "无注解";
}
}
再看下切面类 本文注重讲解注解,这个切面类还有很多完善的地方不过不在本文范围内
代码语言:javascript复制
@Component
@Aspect
public class LogAspect {
// 注解的位置
@Pointcut("@annotation(com.example.demo.annotation.DIYMethodAnnotation)")
public void diyPointCut(){};
@Around("diyPointCut()")
public Object diyAround(ProceedingJoinPoint joinPoint){
//获得被增强的方法相关信息
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
// 获得这个方法
Method method = signature.getMethod();
// 获得这个方法上面的注解
DIYMethodAnnotation diyMethodAnnotation = method.getAnnotation(DIYMethodAnnotation.class);
// 根据注解自定义的一些属性去做自定义的操作
if (diyMethodAnnotation.verification() == 1){
System.out.println("当前校验的是:" diyMethodAnnotation.interfaceName());
System.out.println("方法名称是:" method.getName());
System.out.println("传递参数是:" JSON.toJSONString(joinPoint.getArgs()));
}
System.out.println("aop 拦截器里 verification:" diyMethodAnnotation.verification() );
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
我们将项目跑起来,分别访问两个打了注解的接口
- 需要校验的接口
http://localhost:8081/verification?id=1
当前校验的是:学生爱好接口
方法名称是:verificationMethod
传递参数是:["1"]
aop 拦截器里 verification:1
打代码
- 不需要校验的接口
http://localhost:8081/noVerification?id=1
aop 拦截器里 verification:0
做饭
- 没有注解的接口
http://localhost:8081/noAnnotation?id=1
做饭
由输出结果可以得出一个结论,
- 没有注解的接口,走不到AOP,因为我们AOP配置的是只有注解的接口才进行AOP校验,
- 如果接口上有注解的话,又有两种情况(这是我们自己设置的)
- 输出 ”当前校验的是,方法名称是,传递参数是
- verification 0 的时候 这个注解也不进行特别的操作
- verification 1 的时候 这个注解进行特别的操作
“综上所述,我们在日常开发中,如果对某个类/字段/方法有什么特殊的要求的话,可以使用自定义注解,再通过反射获取到此注解,再根据这个注解中自定义的值在进行我们自定义的操作