0.Java 注解的定义
0.注解的使用场景
● 提供信息给编译器:利用注解来来探测错误和警告信息
● 编译阶段时的处理:软件工具利用注解来生成代码、Html文档或其他相应处理;
● 运行时的处理:某些注解可以在程序运行的时候接受代码的提取;
1.注解的作用或者意义
注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义;
Java注解(Annotation) 又称Java标注,是JDK5.0引入的一种的一种注释机制,用于为Java代码提供元数据,
即有关于程序但不属于程序本身的数据(用来描述数据的数据),注解对它们注解的代码的操作没有直接影响;
┌---Deprecated
ElementType---------┐ ├---Documented
1..n | ├---Inherited
<<接口>> <-----Override
Annotation ├---Retention
1 | ├---Target 目标注解
RetentionPolicy-----┘ └---...
2.注解的声明
java中所有的注解,默认实现Annotation接口:
package java.lang.annotation;
public interface Annotation{
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
//与声明一个"Class"不同的是,注解的声明使用 @interface 关键字
public @interface AnnotationTest {
}
3.元注解(注解上面的注解)
在定义注解时, 注解类也能够使用其他的注解声明, 对注解类型进行注解的注解类,我们称之为 meta annotation(元注解)
/ˈmetə/ /ˌænəˈteɪʃn/, 一般的我们在定义自定义注解时, 需要指定的元注解有两个:@Target @Retention, 另外还有
@Documented、@Inherited 和 @Repeatable 5种元注解,@Documented用于被javadoc工具提取成文档, @Inherited表示
继承的意思,允许子类继承父类中定义的注解, @Repeatable 可重复应用的意思,注解的值可同时取多个, 在java1.8引入;
1> @Target(作用域)
限制注解作用域的注解(目标作用域)
注解标记另一个注解,以限制可以应用注解的java元素类型,目标注解指定一下元素之一作为其值:
● ElementType.ANNOTATION_TYPE 可应用于注解类型(annotation_type);
● ElementType.CONSTRUCTOR 可用于构造方法(constructor);
● ElementType.FIELD 可用于字段或属性(field);
● ElementType.LOCAL_VARIABLE 可用于局部变量(variable);
● ElementType.METHOD 可用于方法级注解(method);
● ElementType.PACKAGE 可用于包声明 (package);
● ElementType.PARAMETER 可用于方法中的参数 (parameter);
● ElementType.TYPE 可用于类的 任何元素
示例:
//@Target(ElementType.TYPE)//只能在类上面标记该注解
Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})//允许在类与类属性与方法上标记该注解
@Retention(RetentionPolicy.SOURCE)//注解保留在源码中
public @interface Test {
String value(); //无默认值, 注意在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值
int age() default 1;//有默认值
}
注解类型元素
在上文元注解中,允许在使用注解时传递参数,我们也能自定义注解的主体 包含 annotation type element
(注解类型元素)声明, 它们看起来很像方法,可以定义可选的默认值; @AnnotationTest(value="帅",age=2)
@Test("帅") String value();//如果成员变量只存在value元素需要传值的情况,否则可省略:@注解名(元素名=值)
@如果不光有value 还有其他元素 如: int id(); String value(); @Test(id=1 value="") 都要写成key-value
@Test("") //对应 @Target(ElementType.TYPE)
public class AnnotationTest {
@Test("")//对应 @Target(ElementType.FIELD)
int i;
@Test("")//对应 @Target(ElementType.METHOD)
public static void main(String[] args) {
System.out.println("Hello Java");
}
}
2> @Retention(存储方式)
限制注解存储方式的注解
注解指定标记注解的存储方式
● RetentionPolicy.SOURCE -标记的注解仅保留在源级别中,并被编译器忽略; 编译成class就会被擦除掉 只在源码中
● RetentionPolicy.CLASS -标记的注解在编译时有编译器保留,但Java虚拟机(JVM)会忽略; 保留在class 字节码中
● RetentionPolicy.RUNTIME-标记的注解由JVM保留,因此运行时环境可以使用它; 结合反射技术
@Retention(保留) 三个值中 Source<CLass<Runtime,即Class包含了Source,Runtime包含Source和Class
根据@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:
1) Source
Retention.SOURCE,作用于源码级别的注解,可以提供IDE语法检查,APT等场景使用;
在类中使用SOURCE级别的注解,会在其编译后的class中被擦除;
APT注解处理器:
其用于处理注解,编写好Java源文件,需要经过javac的编译,翻译为虚拟机能够加载解析的字节Class文件,注解处理器是
javac自带的一个工具,用来在编译时扫描处理注解信息,你可以为某些注解注册字节的注解处理器,注册的注解处理器由
javac调起,并将注解信息传递给注解处理器进行处理;
注解处理器是对注解应用最广泛的场景,在Glide、EventBus3、ButterKnife、Tinker、ARouter等常用框架中都有
注解处理器的身影,,这些框架中对注解的定义并不是SOURCE级别,更多的是CLASS级别;
com.xzh.compiler.TestProcessor
new Module->compiler/java/com.xzh.compiler/MyProcessor ↓
resources/META-INF/services/javax.annotation.processing.Processor
app/build.gradle/ annotationProcessor project(':compiler')
//采集到所有的注解信息->Element->注解处理程序 .java->javac->.class
//APT 注解处理器 Annotation Processor Tools
@SupportedAnnotationTypes("com.xzh.study.annotation.WeekDay")
public class TestProcessor extends AbstractProcessor {
@Override //获取需要处理的注解对象(编译的程序中使用的注解)
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement typeElement : annotations) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE,"================>");
}
return false;
}
}
//IDE语法检查 在Android开发中,support-annotations与androidx.annotation中均有提供@IntDef注解,此注解的定义如下
@Retention(SOURCE) //源码级别注解
@Target({ANNOTATION_TYPE})
public @interface IntDef{
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
Java中Enum(枚举)的实质是单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中,比常量多5到10倍的内存占用,一个对象占用: 12对象头+成员所占内存+8字节对齐;此注解的意义是能够取代枚举,实现如方法入参限制; 枚举是对象;如:我们定义方法setCurrentDay, 此方法接收参数Day 需要在:SUNDAY和MONDAY中选一个,如果使用枚举能够实现为
public enum WeekDay{
SUNDAY, MONDAY,TUESDAY,Wednesday,ThursDay,Firday,Saturday
}
public class AnnotationTest {
static final int SUNDAY = 0;
static final int MONDAY = 1;
static final int TUESDAY = 2;
static final int Wednesday = 3;
static final int ThursDay = 4;
static final int Firday = 5;
static final int Saturday = 6;
@WeekDay //定义的时候初始化时可检测
private static int currentIntDay;//在这里检测 比如只能赋值0-6;
//语法检查 调用setCurrentDay方法由于参数是采用基本数据类型int,
//将无法进行类型限定,此时使用@IntDef增加自定义注解:
@IntDef({SUNDAY, MONDAY,TUESDAY,Wednesday,ThursDay,Firday,Saturday})
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
@interface WeekDay {}
public static void setCurrentDay(@WeekDay int currentDay) {
currentIntDay = currentDay;
}
public static void main(String[] args) {
System.out.println("Hello Java");
setCurrentDay(SUNDAY);
}
}
2) CLASS
定义为Class的注解,会保留在class文件中,但会被虚拟机忽略(即无法在运行期反射获取注解),如字节码操作,AspectJ
热修复Robust中应用此场景,所谓的字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的;
示例:是否登录--[已登录(通过验证)|未登录(进入登录)]
如果使用不同的编程方式,需要在代码中进行if-else的判断,若存在10个判断点,需要在每个判断点加入此项判断;此时
我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 和 无需登录两种类型,即两个切面,
对于切面的区分即可采用注解
注解的提取
注解与反射,注解通过该反射获取,首先可以通过Class对象的isAnnotationPresent(Class<? extends Annotation> cls)
判断是否用了某注解,然后通过 getAnnotation(Class<A> aCls) 获取注解的对象,获取的注解不为null,就可调用属性了;
示例:实现intent传值自动写入对应成员中
//注解类
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWrite {
String value() default "";
}
//该工具主要实现intent传值自动写入对应成员中
public class InjectUtils {
public static void injectExtra(Activity activity) {
//获取总数据
Intent intent = activity.getIntent();
Bundle extras = intent.getExtras();
if (extras == null) return;
//获取成员
Class<? extends Activity> cls = activity.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(AutoWrite.class)) {
AutoWrite autoWrite = field.getAnnotation(AutoWrite.class);
if (autoWrite == null) return;
//获得注解中的key
String key = autoWrite.value();
if (extras.containsKey(key)) {
Object value = extras.get(key);
//Parcelable数组类型不能直接设置,其他都可以
//获得单个元素类型
Class<?> type = field.getType().getComponentType();
//获得数组单个元素类型
if (field.getType().isArray() && Parcelable.class.isAssignableFrom(type)) {
Object[] array = (Object[]) value;
if (array != null)
value = Arrays.copyOf(array, array.length, (Class<? extends Object[]>) field.getType());
}
field.setAccessible(true);//开放访问权限
try {
//反射设置属性的值 传实例对象
field.set(activity, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
//进行传值操作
public class MainActivity extends AppCompatActivity {
public void test(View view) {
Intent intent = new Intent(this, TestActivity.class);
intent.putExtra("name", "NorthStar");
intent.putExtra("age", 18);
intent.putExtra("isMen", true);
startActivity(intent);
}
}
//使用注解赋值
public class TestActivity extends AppCompatActivity {
@AutoWrite(value = "name")
String name;
@AutoWrite(value = "age")
int age;
@AutoWrite(value = "isMen")
boolean isMen;
public static final String TAG = "reflex";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
InjectUtils.injectExtra(this);
initData();
}
public void initData() {
Log.d(TAG, "name==>" + name + " ,age==>" + age + " ,isMen==>" + isMen);
}
}