과거에는 "XML"이 주요 프레임워크에서 선호되었습니다. 그러나 프로젝트가 점점 더 커지면서 "XML"의 내용은 점점 더 복잡해지고 필요해졌습니다. 유지 관리 비용이 높아집니다.
그래서 누군가가 표시되고 고도로 결합된 구성 방법인 "주석"을 제안했습니다. 메소드에 주석을 달 수 있고, 클래스에 주석을 달 수 있으며, 필드 속성에도 주석을 달 수 있습니다. 어쨌든 구성이 필요한 거의 모든 곳에 주석을 달 수 있습니다.
"주석"과 "XML"의 두 가지 구성 모드에 대해 수년 동안 논쟁이 있었습니다. 각 모드에는 고유한 장점과 단점이 있습니다. 주석은 더 큰 편의성을 제공하고 유지 관리 및 수정이 쉽지만 수준이 높습니다. 결합의 경우, XML의 경우 주석의 경우 그 반대입니다.
낮은 결합을 추구한다는 것은 높은 효율성을 포기한다는 것을 의미하며, 효율성을 추구하다 보면 필연적으로 결합을 만나게 됩니다. 이 글의 목적은 둘을 구별하는 것이 아니라, Annotation과 관련된 기본 내용을 가장 간단한 언어로 소개하는 것입니다.
Annotation의 본질
"java.lang.annotation.Annotation" 인터페이스에 "annotation"을 기술하는 문장이 있습니다.
The common interface extended by all annotation types 所有的注解类型都继承自这个普通的接口(Annotation)
이 문장은 다소 추상적이지만 Annotation의 본질을 표현하고 있습니다. JDK 내장 주석의 정의를 살펴보겠습니다.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
이것은 @Override 주석의 정의입니다. 실제로는 다음과 같습니다.
public interface Override extends Annotation{ }
예, 주석의 본질은 주석을 상속하는 인터페이스입니다. 인터페이스. 이와 관련하여 모든 주석 클래스를 디컴파일하면 결과를 얻을 수 있습니다.
정확한 의미에서 주석은 특별한 주석에 지나지 않습니다. 파싱할 코드가 없으면 주석만큼 좋지 않을 수도 있습니다.
클래스나 메서드의 구문 분석 주석에는 두 가지 형태가 있는 경우가 많습니다. 하나는 컴파일 타임에 직접 스캔하는 것이고, 다른 하나는 런타임 반영입니다. 리플렉션에 대해서는 나중에 다루겠습니다. 컴파일러 스캐닝이란 컴파일러가 Java 코드용 바이트코드를 컴파일하는 과정에서 특정 클래스나 메소드가 일부 주석에 의해 수정되었음을 감지한다는 의미입니다. .
일반적인 것은 @Override 주석입니다. 컴파일러가 @Override 주석으로 메서드가 수정되었음을 감지하면 컴파일러는 현재 메서드의 메서드 서명이 실제로 상위 클래스의 메서드를 재정의하는지 여부를 확인합니다. , 상위 클래스를 비교합니다. 클래스에 동일한 메소드 서명이 있는지 여부입니다.
이 상황은 JDK의 여러 내장 주석과 같이 컴파일러에 이미 익숙한 주석 클래스에만 적용됩니다. 사용자 정의 주석의 경우 컴파일러는 주석의 기능을 알지 못합니다. 함수를 모르는 경우 이를 처리하는 방법은 주석의 범위에 따라 바이트코드 파일로 컴파일할지 여부를 선택하는 것뿐입니다.
Meta-annotation
"Meta-annotation"은 주석을 수정하는 데 사용되는 주석으로, 일반적으로 주석 정의에 사용됩니다. 예:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
이것은 @Override 주석의 정의입니다. @Target을 볼 수 있습니다. , @ Retention의 두 가지 주석은 우리가 "메타 주석"이라고 부르는 것입니다. "메타 주석"은 일반적으로 주석의 수명 주기 및 대상과 같은 정보를 지정하는 데 사용됩니다. . 나는 2018년에 최신 기본 입문 및 고급 튜토리얼 세트를 편집했습니다. Java를 추가하여 q-u-n: 678, 241 및 563을 학습하여 얻을 수 있습니다. 포함된 내용은 다음과 같습니다: 개발 도구 및 설치 패키지, 및 시스템 학습 로드맵
JAVA에는 다음과 같은 "메타 주석"이 있습니다.
@Target:注解的作用目标 @Retention:注解的生命周期 @Documented:注解是否应当被包含在 JavaDoc 文档中 @Inherited:是否允许子类继承该注解
그 중 @Target은 수정된 주석이 궁극적으로 대상을 지정할 수 있는 사람, 즉 주석이 어떤 용도로 사용되는지 나타내는 데 사용됩니다. 수정 방법? 수정 유형? 또한 필드 속성을 수정하는 데에도 사용됩니다.
@Target은 다음과 같이 정의됩니다.
다음 방법으로 이 값에 값을 전달할 수 있습니다.
@Target(value = {ElementType.FIELD})
이 @Target 주석으로 수정된 주석은 구성원 필드에만 적용되며 수정에는 사용할 수 없습니다. 메소드나 클래스. 그중 ElementType은 다음 값을 갖는 열거형입니다.
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上 ElementType.FIELD:允许作用在属性字段上 ElementType.METHOD:允许作用在方法上 ElementType.PARAMETER:允许作用在方法参数上 ElementType.CONSTRUCTOR:允许作用在构造器上 ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上 ElementType.ANNOTATION_TYPE:允许作用在注解上 ElementType.PACKAGE:允许作用在包上
@Retention은 현재 주석의 수명 주기를 나타내는 데 사용됩니다. 기본 정의는 다음과 같습니다.
마찬가지로 value 속성도 있습니다. :
@Retention(value = RetentionPolicy.RUNTIME
여기서 RetentionPolicy는 다음 열거형 값을 갖습니다.
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件 RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件 RetentionPolicy.RUNTIME:永久保存,可以反射获取
@Retention 주석은 수정된 주석의 수명 주기를 지정하며 컴파일 후에 표시됩니다. 클래스, 메소드, 필드 등 하나의 유형은 모두 속성 테이블을 가지며, JAVA 가상 머신은 주석 정보를 저장하기 위한 여러 주석 속성 테이블도 정의합니다. 그러나 이 가시성은 메소드 영역으로 가져올 수 없으며 클래스가 로드될 때 삭제됩니다. 마지막 가시성은 영구 가시성입니다.
剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,你只需要知道他们各自的作用即可。@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
JAVA 的内置三大注解
除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:
@Override @Deprecated @SuppressWarnings @Override 注解想必是大家很熟悉的了,它的定义如下: @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。
所以你看,它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
@Deprecated 的基本定义如下:
依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。
当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。
@SuppressWarnings 主要用来压制 java 的警告,它的基本定义如下:
它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:
public static void main(String[] args) { Date date = new Date(2018, 7, 11); }
这么一段代码,程序启动时编译器会报一个警告。
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。
@SuppressWarning(value = "deprecated") public static void main(String[] args) { Date date = new Date(2018, 7, 11); }
这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。
当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。
自定义注解的相关内容就不再赘述了,比较简单,通过类似以下的语法即可自定义一个注解。
public @interface InnotationName{ }
当然,自定义注解的时候也可以选择性的使用元注解进行修饰,这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。
注解与反射
上述内容我们介绍了注解使用上的细节,也简单提到,「注解的本质就是一个继承了 Annotation 接口的接口」,现在我们就来从虚拟机的层面看看,注解的本质到底是什么。
首先,我们自定义一个注解类型:
这里我们指定了 Hello 这个注解只能修饰字段和方法,并且该注解永久存活,以便我们反射获取。
之前我们说过,虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
RuntimeVisibleAnnotations:运行时可见的注解 RuntimeInVisibleAnnotations:运行时不可见的注解 RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解 RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解 AnnotationDefault:注解类元素的默认值
给大家看虚拟机的这几个注解相关的属性表的目的在于,让大家从整体上构建一个基本的印象,注解在字节码文件中是如何存储的。
所以,对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。
getAnnotation:返回指定的注解 isAnnotationPresent:判定当前元素是否被指定注解修饰 getAnnotations:返回所有的注解 getDeclaredAnnotation:返回本元素的指定注解 getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
方法、字段中相关反射注解的方法基本是类似的,这里不再赘述,我们下面看一个完整的例子。
首先,设置一个虚拟机启动参数,用于捕获 JDK 动态代理类。
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
然后 main 函数。
我们说过,注解本质上是继承了 Annotation 接口的接口,而当你通过反射,也就是我们这里的 getAnnotation 方法去获取一个注解类实例的时候,其实 JDK 是通过动态代理机制生成一个实现我们注解(接口)的代理类。
프로그램을 실행한 후 출력 디렉터리에 이러한 프록시 클래스가 표시됩니다. 디컴파일 후 다음과 같습니다.
프록시 클래스는 Hello 인터페이스를 구현하고 다시 작성합니다. Annotation 인터페이스에서 상속된 값 메소드와 인터페이스 Hello 메소드를 포함한 모든 메소드.
이 핵심 InvocationHandler 인스턴스는 누구입니까?
AnnotationInvocationHandler는 JAVA에서 주석을 처리하는 데 특별히 사용되는 핸들러입니다. 이 클래스의 디자인도 매우 흥미롭습니다.
여기에 Map 키-값 쌍인 memberValues가 있습니다. 키는 주석 속성의 이름이고 값은 속성에 할당된 값입니다.
이 호출 메서드는 매우 흥미롭습니다. 프록시 클래스는 Hello 인터페이스의 모든 메서드를 프록시하므로 프록시 클래스의 모든 메서드에 대한 호출이 여기로 전송됩니다.
var2는 호출된 메서드 인스턴스를 가리키며 여기서는 먼저 변수 var4를 사용하여 메서드의 간결한 이름을 얻은 다음 스위치 구조를 사용하여 현재 호출 메서드가 네 가지 메서드 중 하나인지 확인합니다. Annotation에서 var7을 특정 값에 할당합니다.
현재 호출된 메서드가 toString, equals, hashCode, annotationType인 경우 이러한 메서드의 구현은 AnnotationInvocationHandler 인스턴스에 미리 정의되어 있으며 직접 호출할 수 있습니다.
그런 다음 var7이 이 네 가지 메서드와 일치하지 않으면 현재 메서드가 Hello 주석의 값 메서드와 같이 사용자 지정 주석 바이트에서 선언된 메서드를 호출한다는 의미입니다. 이 경우 이 주석 속성에 해당하는 값은 주석 맵에서 가져옵니다.
사실 저는 개인적으로 JAVA의 주석 디자인이 약간 반인간적이라고 생각합니다. 이는 분명히 속성 작업이므로 메서드를 사용하여 구현해야 합니다. 물론, 서로 다른 의견이 있으신 경우에는 메시지를 남겨서 논의해 주시기 바랍니다.
마지막으로 전체 반사 주석의 작동 원리를 요약해 보겠습니다:
먼저 다음과 같이 키-값 쌍의 형태로 주석 속성에 값을 할당할 수 있습니다. @Hello (value = "hello ").
다음으로, 주석을 사용하여 요소를 수정합니다. 컴파일러는 컴파일 중에 각 클래스나 메서드에 대한 주석을 스캔하여 현재 위치에 주석을 적용할 수 있는지 확인합니다. 주석은 다음과 같습니다. 정보는 요소의 속성 테이블에 기록됩니다.
그런 다음 리플렉션을 수행하면 가상 머신은 RUNTIME 수명 주기의 모든 주석을 꺼내어 맵에 넣고 AnnotationInvocationHandler 인스턴스를 생성한 다음 여기에 맵을 전달합니다.
마지막으로 가상 머신은 JDK 동적 프록시 메커니즘을 사용하여 대상 주석이 달린 프록시 클래스를 생성하고 프로세서를 초기화합니다.
이렇게 하면 기본적으로 프록시 클래스인 Annotation 인스턴스가 생성되는데, 핵심인 AnnotationInvocationHandler의 호출 메소드 구현 로직을 이해해야 합니다. 한 문장에서 는 메서드 이름을 통해 주석 속성 값 을 반환합니다.
위 내용은 JAVA 개발 시 주석의 기본 원리에 대한 심층 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!