一.注解的作用
注解本身没有任何意义,单纯的注解就是一种注释,它需要结合其他如反射、插桩等技术才有意义。
二.注解的基本使用
1.特点
Java中所有的注解,默认实现Annotation接口
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
2.声明:在interface前面加上@
@Retention(RetentionPolicy.SOURCE)
public @interface DiDiWei {
String DiDiWei() default "xxx";//定义一个默认值,这样在使用的时候就不用传参了
}
最上面的是元注解(说白了就是:注解上的注解。总共就四个,常用的就Target和Retention两个)
3.注意:
如果注解里面有多个元素(一定要注意每个元素后面要加()),那么在使用的时候就要以key-value的形式传参
比如,定义的某注解
public @interface DiDiWei {
int num();//一定要注意元素后面要加()
String DiDiWei() default "xxx";//定义一个初始值
}
使用的时候这样传入参数
@DiDiWei(num = 1,DiDiWei = "敌敌畏")
三.元注解
在定义注解时,注解类也能够使用其他的注解声明,对注解类型进行注解的注解类,我们称之为meta-annotation(元注解)。
一般的,我们在定义自定义注解时,需要指定的元注解有两个 :
1.@Target
标记另一个注解,以限制可以应用注解的 Java 元素类型。
具体为:
ElementType.ANNOTATION_TYPE 可以应用于注解类型。
ElementType.CONSTRUCTOR 可以应用于构造函数。
ElementType.FIELD 可以应用于字段或属性。
ElementType.LOCAL_VARIABLE 可以应用于局部变量。
ElementType.METHOD 可以应用于方法级注解。
ElementType.PACKAGE 可以应用于包声明。
ElementType.PARAMETER 可以应用于方法的参数。
ElementType.TYPE 可以应用于类的任何元素。
源码是这样的:
public enum ElementType {
/** Class, interface (including annotation type), enum, or record
* declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
*
* @since 9
*/
MODULE,
/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This constant is associated with <i>records</i>, a preview
* feature of the Java language. Programs can only use this
* constant when preview features are enabled. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* Record component
*
* @jls 8.10.3 Record Members
* @jls 9.7.4 Where Annotations May Appear
*
* @since 14
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=true)
RECORD_COMPONENT;
}
2.@Retention
保留级别由@Retention声明,或者说指定标记注解的存储方式:
保留级别:
RetentionPolicy.SOURCE:标记的注解仅保留在源级别中,并被编译器忽略
RetentionPolicy.CLASS:标记的注解在编译时由编译器保留,但Java虚拟机(JVM)会忽略
RetentionPolicy.RUNTIME:标记的注解由JVM保留,因此运行时环境可以使用它。一般结合反射技术。
SOURCE<CLASS<RUNTIME:即CLASS包含了SOURCE,RUNTIME包含了SOURCE、CLASS。
源码是这样的
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
3.使用示例
//@Target(ElementType.TYPE) 只能在类上标记该注解
@Target({ElementType.TYPE,ElementType.FIELD}) // 允许在类与类属性上标记该注解
@Retention(RetentionPolicy.SOURCE) //注解保留在源码中
public @interface Lance { }
四.注解的应用场景
1.1:源码级别:APT
APT:注解处理器(Annitation Processor Tools)
(1)APT怎么运行的呢?
APT全称为:“Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。
编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。
注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
我们要知道程序的执行过程:.java->javav->.class。也就是说.java文件由javac处理之后就变成了.class文件。在解析java类的时候会采集到所有的注解信息,将他们封装为一个节点Element,由javac调取指定的注解处理程序。执行完注解处理程序后才会把.java编译成.class文件。所以注解处理程序是javac调用的而不是我们手动调用的,我们只需要给javac指明让他去执行注解处理程序就行了。
如果想让注解处理程序处理指定的注解,那就在类上面写@SupportedAnnotationTypes(“注解全类名”)
注解处理程序有啥用?干啥都行,但是一般用于生成额外的辅助类。
使用示例
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes("Fanxing.DiDiWei")//表示对哪个注解起作用
public class DiDiProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// TODO Auto-generated method stub
return false;
}
}
这里对APT技术了解个大概就好,后面还要专门学习一下。
除了APT,源码级别还有一个用处,就是
1.2:源码级别:IDE语法检查。(了解即可)
语法检查是通过IDE/IDE插件实现的。
比如在安卓中,我们使用setDrawable方法,一开始参数为int id。像这样
public static void setDrawable(int id){}
public static void main(String... args){
setDrawable(111);
}
我们传入111不会报错,因为111也是整数。但是111并不是有效的drawable,我们希望传入的是一个R.drawable.xxx的数据,所以就可以在int id前面加上注解@DrawableRes 像这样
public static void setDrawable(@DrawableRes int id){}
这样就不能简简单单地传入111了。因为传入之后就会提示语法错误。
类似地,使用自定义注解,进行int类型的检查
public class one {
@WeekDay private static int mCurrentDay;
public static final int SUNDAY = 0;
public static final int MONDAY = 1;
@IntDef({SUNDAY,MONDAY})//元注解
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
@interface WeekDay{
}
public static void setWeekDay(@WeekDay int currentDay) {
mCurrentDay = currentDay;
}
public static void main(String[] args) {
setWeekDay(12121);//这样写就会提示语法错误
setWeekDay(SUNDAY);//这样写就OK
}
}
因为我们不是研发IDEA的,所以知道这个事就可以了
2.字节码级别:字节码增强技术、字节码插桩技术
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合
此种注解的应用场景为字节码操作。
其实字节码增强技术说白了就是在字节码当中写代码。字节码文件有自己的格式(数据按照特定的方式记录与排列),我们要想学习字节码增强技术,首先就要了解class文件的格式。class文件是可读性很差的16进制数据,所以这里先不讲(手动滑稽)。后面会详细说。
3.运行时级别:反射
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息
(1)反射是什么?
反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有方法和属性;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。反射是Java被视为动态语言的关键。
(2)Java反射机制主要提供了以下功能:
在运行时构造任意一个类的对象
在运行时获取或者修改任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的属性或方法
(3)Class
反射始于Class:Class是一个类,封装了当前对象所对应的类的信息。一个类中有属性,方法,构造器等。也就是说:Class是用来描述类的类
(3).1 获取Class对象的三种方式
①. 通过类名获取 类名.class
②. 通过对象获取 对象名.getClass()
③. 通过全类名获取 Class.forName(全类名)或者 classLoader.loadClass(全类名)
(3).2 Class对象的两个方法区别:
getField和getDeclaredField
前者是获得自己和父类的成员(不包括private,只能是public)
后者是只能获得自己的成员(不包括父类)
(4)注解和反射结合修改成员变量使用示例
class Temp{
@DiDiWei(num = 0) private static int old;
}
public class InjectUtils {
//下面这些都应当写入一个方法当中,第一次就没有这个意识,结果一直提示语法错误
public static void injectUtils() {
Temp temp = new Temp();
//获得这个类对象
Class cls = temp.getClass();
//得到一个变量,与Temp中即将要更改的变量类型一致
int toBeChanged = 1;
//获得此类的所有成员
Field[] declaredFields = cls.getDeclaredFields();
for(Field declaredField: declaredFields) {
//判断属性是否被DiDiWei注解声明
if(declaredField.isAnnotationPresent(DiDiWei.class)) {
DiDiWei ddw = declaredField.getAnnotation(DiDiWei.class);
//获得了注解中设置的id(如果在安卓中就可以直接使用findViewById()找到
int id = ddw.num();
//反射 设置属性的值
declaredField.setAccessible(true);//设置访问权限,允许操作private的属性
try{
//反射赋值
declaredField.set(temp, toBeChanged);
}catch(IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DiDiWei {
int num();//一定要注意元素后面要加()
}
本文深入探讨Java注解的原理与应用场景,包括注解的基本使用、元注解、注解处理器及与反射技术的结合。

16万+

被折叠的 条评论
为什么被折叠?



