注解的基本原理

118 阅读4分钟

注解的基本原理

面试时卡壳的一个问题,spring注解生效的原理是什么,为什么打上一个注解就可以实现功能。

注解的本质

注解是一种标记式的高耦合的配置方式与xml配置相对应。 java.lang.annotation.Annotation中写明所有的注解都继承自这个接口,注解本质就是一个继承了Annotation接口的接口。

注解如何生效

注解想要生效需要被代码解析,常见的两种解析方式。一种是在编译期直接扫描,一种是运行期通过反射解析。
编译期直接扫描
编译期直接扫描指的是编译器在把Java代码编译成字节码的时候可以检查到某个类或方法是否被一些注解修饰。比如编译器检测到@Override的时候,编译器会检查父类中是否具有一个同样的方法签名。这种解析方式只支持编译器知道的注解,比如JDK内置的注解。
运行期通过反射解析
例如:Spring 框架找到带有 @Autowired 注解的类、字段、构造函数或方法,它会使用反射来解析这些元数据。对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。

  • getAnnotation:返回指定的注解
  • isAnnotationPresent:判定当前元素是否被指定注解修饰
  • getAnnotations:返回所有的注解
  • getDeclaredAnnotation:返回本元素的指定注解
  • getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
public class Test{
	@HelloWord("hw")
	public static void main(String[] args){
    	Class cls = Test.class;
        Method method = cls.getMethod("main",String[].class)
        HelloWord helloWord = method.getAnnotation(HelloWord.class);
	}
}

通过反射调用方法getAnnotation()时,JDK会生成一个实现了HelloWord接口(注解)的动态代理类。实现了HelloWord的所有方法。

public final class Proxy1 extend Proxy implement  HelloWord{
	public Proxy1(InvocationHandler invocationHandler){
		super(invocationHandler);
	}
	public final String value(){
		return (String)super.h.invoke(this,m3,null);
	}
	......
}

AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler AnnotationInvocationHandler的成员变量Map<String, Object> memberValues存放着注解的成员属性的名称和值的映射,注解成员属性的名称实际上就对应着接口中抽象方法的名称

invoke方法 var2指向被调用的方法实例,var4是方法的名称。如果调用的是equals、hashCode、toString、annotationType这四个特定方法,AnnotationInvocationHandler 实例已经定义好了实现,直接调用就可以。 如果不是,从memberValues中获取对应的值

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
	//保存了当前注解的类型
    private final Class<? extends Annotation> type;
	//保存了注解的成员属性的名称和值的映射,注解成员属性的名称实际上就对应着接口中抽象方法的名称
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

    public Object invoke(Object var1, Method var2, Object[] var3) {
		//获取当前执行的方法名称
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }
            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
			    //利用方法名称从memberValues获取成员属性的赋值
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
					//这一步就是注解成员属性返回值获取的实际逻辑
					//需要判断是否数组,如果是数组需要克隆一个数组
					//不是数组直接返回
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }
                    return var6;
                }
            }
        }
    }

元注解

JAVA 中有以下几个『元注解』:

  • @Target:注解的作用目标
  • @Retention:注解的生命周期
  • @Documented:注解是否应当被包含在 JavaDoc 文档中
  • @Inherited:是否允许子类继承该注解
  1. @Target用于指明被修饰的注解可以作用的目标是谁,比如这个注解是修饰方法、类还是字段属性。通过value来传值@Target(value = {}),通过ElementType枚举类指定类别。
  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上
  1. @Retention用于指定注解生命周期
  • RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
  • RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取

列如@Override注解就只在编译期可见,编译结束后丢弃。

组件注册:一旦 Spring 解析了类的元数据,它会根据注解的类型(如 @Component、@Service、@Repository、@Controller 等)创建相应的 Bean 定义。Bean 定义包含了组件的配置信息,包括类名、依赖关系等。