注解的本质

参考文章

一、何为注解

注解,可以看作是对 一个 类/方法 的一个扩展的模版,每个 类/方法 按照注解类中的规则,来为 类/方法 注解不同的参数,在用到的地方可以得到不同的 类/方法 中注解的各种参数与值。

二、分类

普通注解、元注解、自定义注解。

  1. 普通注解
    常见的主要有三个 :@Override、@Deprecated、@SuppressWarnings
    1. @Override注解我们可能见到的比较多,主要用于子类对父类方法的重写。
    2. @Deprecated主要用于注解过期、废弃的方法或者类等。
    3. @SuppressWarnings对被批注的代码元素内部的某些警告保持静默,可以忽略编译器的警告信息。
  2. 元注解
    元注解是用来注解其他注解的注解。Java中总共有5种元注解:@Retention,@Documented,@Target,@Inherited,@Repeatable
    1. @Retention :
      在这里插入图片描述

      • RetentionPolicy.SOURCE:注解只在源码阶段保留,编译器开始编译时它将被丢弃忽视。
      • RetentionPolicy.CLASS:注解会保留到编译期,但运行时不会把它加载到JVM中。
      • RetentionPolicy.RUNTIME:注解可以保留到程序运行时,它会被加载到JVM中,所以程序运行过程中可以获取到它们。
    2. @Documented
      这个注解跟文档相关,它的作用是能够将注解中的元素包含到Javadoc中去,应该被JavaDoc所记录

    3. @Target
      在这里插入图片描述
      具体参考:@target 取值

    4. @Inherited
      一个父类被该类注解修饰,那么它的子类如果没有任何注解修饰,就会继承父类的这个注解。

    5. @Repeatable
      它是java1.8引入的,标记的注解可以多次应用于相同的声明或类型

三、注解的本质

所有的注解本质上都是继承自 Annotation 接口。但是,手动定义一个接口继承 Annotation 接口无效的,需要通过 @interface 声明注解,Annotation 接口本身也不定义注解类型,只是一个普通的接口。
在这里插入图片描述

public interface Annotation {
    
    boolean equals(Object obj);
    
    int hashCode();
    
    String toString();
    
    /**
     *获取注解类型 
     */
    Class<? extends Annotation> annotationType();
}

来对比下 @interface 定义注解和继承 Annotation 接口

public @interface TestAnnotation1 {
}

public interface TestAnnotation2 extends Annotation  {
}

通过使用 javap 指令对比两个文件的字节码,发现通过 @interface 定义注解,本质上就是继承 Annotation 接口。

// javap -c TestAnnotation1.class
Compiled from "TestAnnotation1.java"                                                                 
public interface com.hncboy.corejava.annotation.TestAnnotation1 extends java.lang.annotation.Annotation {}

// javap -c TestAnnotation2.class
Compiled from "TestAnnotation2.java"                                                                 
public interface com.hncboy.corejava.annotation.TestAnnotation2 extends java.lang.annotation.Annotation {}

虽然本质上都是继承 Annotation 接口,但即使接口可以实现多继承,注解的定义仍然无法使用继承关键字来实现。
虽然注解不支持继承其他注解或接口,但可以使用组合注解的方式来解决这个问题。如 @SpringBootApplication 就采用了组合注解的方式。
在这里插入图片描述

四、自己写一个注解

首先,注解中是可以加入属性的,聚类属性的类型如下:

  • 所有的基本类型(int,float,boolean 等)
  • String
  • Class
  • enum(@Retention 中属性的类型为枚举)
  • Annotation
  • 以上类型的数组(@Target 中属性类型为枚举类型的数组)
    编译器对属性的默认值也有约束。首先,属性不能有不确定的的值。也就是说,属性要么具有默认值,要么在使用注解时提供属性的值。对于非基本类型的属性,无论是在源代码中声明时,或是在注解接口中定义默认值时,都不能使用 null 为其值。因此,为了绕开这个约束,我们需要自己定义一些特殊的值,例如空字符串或负数,来表示某个属性不存在。

下面举一个具体的例子:
自定义注解实现 Spring IOC Bean 实例创建,自定义简单的注解: @MyComponent、@MyMethod 和 @MyComponentScan。

  1. 三个注解:
package com.Annotation.my;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author kunzhang25
 * @Classname MyComponent
 * @Description MyComponent
 * @Date 2022/1/19 11:04
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface MyComponent {
    String value() default "";
}

package com.Annotation.my;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author kunzhang
 * @Classname MyComponentScan
 * @Description MyComponentScan
 * @Date 2022/1/19 11:07
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface MyComponentScan {

    String value() default "";
}

package com.Annotation.my;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author kunzhang
 * @Classname MyBean
 * @Description MyBean注解
 * @Date 2022/1/19 10:30
 */
//运行时可以通过反射获取
@Retention(RetentionPolicy.RUNTIME)
//注解范围,作用于方法上
@Target(value = ElementType.METHOD)
public @interface MyMethod {
    String value() default "";
}

  1. 新建 T1、T2、T3 三个类
package com.Annotation.my;

/**
 * @author kunzhang
 * @Classname T1
 * @Description TODO
 * @Date 2022/1/19 11:18
 */
public class T1 {
    public T1() {
        System.out.println("T1的无参构造方法");
    }

    public static T2 getInstance(){
        System.out.println("T1的静态方法");
        return new T2();
    }

    public T3 getT(){
        System.out.println("T1的实例方法");
        return new T3();
    }
}

class T2{

}

class T3{

}
  1. 新建 IocContainer 类
package com.Annotation.my;

import java.util.HashMap;

/**
 * @author kunzhang
 * @Classname IOCContainer
 * @Description 用map 存放Bean,通过id获取Bean
 * @Date 2022/1/19 11:22
 */
public class IocContainer {

    private static HashMap<String,Object> hashMap = new HashMap<>();
    public static Object getBean(String id){
        return hashMap.get(id);
    }
    public static void putBean(String id,Object object){
        hashMap.put(id,object);
    }
}

  1. 新建 Test 类
  • 通过@MyComponentScan 扫描包
  • 遍历该包下所有的类
  • 扫描类上是否有@MyComponent,有的话,存入到IOC容器中
  • 扫描方法上是否有@MyMethod ,有的话,存入到IOC容器中

在运行过程中,需要通过反射实现 Spring IOC Bean 实例的三种。具体可以参考:反射的原理,反射创建类实例的三种方式

package com.Annotation.my;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.HashSet;

/**
 * @author kunzhang25
 * @Classname Test
 * @Description 测试
 * @Date 2022/1/19 11:33
 */
@MyComponentScan("com.Annotation.my")
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Test test = new Test();

        String scanPackage = test.getScanPackage();
        HashSet<String> classPath = new HashSet<>();
        test.doScanPackage(classPath,scanPackage);
        for(String path : classPath){
            Class<?> aClass = Class.forName(path);
            if(aClass.isAnnotationPresent(MyComponent.class)){
                // String id = aClass.getDeclaredAnnotation(MyComponent.class).value();
                IocContainer.putBean(path,aClass.newInstance());
            }

            Method[] declaredMethods = aClass.getDeclaredMethods();
            for(Method declaredMethod : declaredMethods){
                if(declaredMethod.isAnnotationPresent(MyMethod.class)){
                    String BeanName = declaredMethod.getDeclaredAnnotation(MyMethod.class).value();
                    if(Modifier.isStatic(declaredMethod.getModifiers())){
                        IocContainer.putBean(BeanName,declaredMethod.invoke(null));
                    }else{
                        // 首先获取该类的实例对象,再调用实例方法进行实例化
                        IocContainer.putBean(BeanName, declaredMethod.invoke(IocContainer.getBean(path)));
                    }

                }
            }
        }

    }

    /**
     * 获取MyComponentScan注解中的值
     * @return
     */
    private String getScanPackage() {
        Class<? extends Test> aClass = this.getClass();
        //如果不包含注解类型
        if(!aClass.isAnnotationPresent(MyComponentScan.class)){
            return "";
        }
        MyComponentScan annotation = aClass.getDeclaredAnnotation(MyComponentScan.class);
        return annotation.value();
    }

    /**
     * 扫描该包下的类
     *
     * @param classPathSet
     * @param scanPackage
     */
    private void doScanPackage(HashSet<String> classPathSet, String scanPackage) {
        // 通过正则表达式将包名中的 . 替代为 /,并获取到该路径的 class url
        URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        // 获取该 url 下的所有 File(目录/文件)
        File classDir = new File(url.getFile());
        // 遍历所有 File
        for (File file : classDir.listFiles()) {
            // 判断该 file 如果是目录的话
            if (file.isDirectory()) {
                // 拼接该目录的名字并递归遍历该目录
                doScanPackage(classPathSet, scanPackage + "." + file.getName());
            } else {
                // 如果文件不是以 .class 结尾
                if (!file.getName().endsWith(".class")) {
                    continue;
                }

                // 通过 包名+目录名+除去.class的类名 拼接该类的全限定名
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                // 将该类的全限定名放入 classPathSet
                classPathSet.add(clazzName);
            }
        }
    }
}

输出:
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值