注解的定义
注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他的反射、插桩等才有意义。
Java注解(Annotation)又称Java标注,是JDK1.5引入的一种注释机制。是元数据的一种形式,提供有关于程序本身的数据。注解对他们注解的代码的操作没有直接影响。
先定义一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
String value() default "test";
}
注解的使用:
@MyAnnotation("1111")
public class AnnotationTest {
}
注意点:
- 当注解里面定义的元素只有一个,并且命名为value时,可以忽略名字,多个或者是命名不为value时,需要手动注明元素的名字。
- java用 @interface Annotation{ } 定义一个注解 @Annotation,一个注解是一个类。
- 当注解里面的元素默认值时,使用时可以不填入值
元注解
在定义注解时,注解类也能够使用其他的注解声明。对于注解类型进行注解的类,我们称之为meta-annotation(元注解)。
@Target表示注解允许可以作用于那些节点,如果没有指明作用位置,将作用于java类上面,一起看一下ElementType的值:
public enum ElementType {
TYPE, //接口、类、枚举、注解
FIELD,//变量
METHOD,//方法
PARAMETER,//方法参数
CONSTRUCTOR,//构造方法
LOCAL_VARIABLE,//局部变量
ANNOTATION_TYPE,//注解类型
PACKAGE,//包
TYPE_PARAMETER,//输入参数声明
TYPE_USE;//使用一种类型
private ElementType() {
}
}
@Retention表示注解的保留级别,一起看一下RetentionPolicy类:
public enum RetentionPolicy {
/**
* 标记的注解仅保留在源码级别,并被编译器忽略
*/
SOURCE,
/**
* 标记的注解在编译时由编译器保留,但Java虚拟机(JVM)会忽略
*/
CLASS,
/**
* 标记的注解由JVM保留,因此运行时环境可以使用它
*/
RUNTIME
}
它们三者是包含关系:
SOURCE<CLASS<RUNTIME,即CLASS包含了SOURCE,RUNTIME包含了SOURCE和CLASS。
反射
一般情况下,我们使用某个类必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
发射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创造对象了。这时候,我们使用JDk提供的反射API进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类额所有属性和方法;对于一个对象,都能够调用它的任意方法和属性,并且能改变它的属性。是Java被视为动态语言的关键。
反射的基本使用
getClass方法
- 类的 class 属性
Class<Test> c1 = Test.class;
- Object 对象 的 getClass() 方法
Test test = new Test();
Class<Test> c2 = (Class<Test>) test .getClass();
- 通过 Class 类的 forName() 方法(最常用)
try {
Class c3 = Class.forName("com.mfw.Test");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
获取类的构造函数
Test test = new Test();
Class c4 = test.getClass();
Constructor[] constructors ;
constructors = c4.getDeclaredConstructors();
获取类的成员属性
try {
Test test = new Test();
Class<Test> cl = (Class<Test>) test.getClass();
//根据字段名获取字段,能获取父类的
Field f1 = cl.getField("test");
//获取类public的,包括从父类继承来的字段
Field[] fields1 = cl.getFields();
//根据字段名获取字段,不能获取父类的
Field f2 = cl.getDeclaredField("test");
//可以获取本类所有的字段,包括private的,但是不能获取继承来的字段
Field[] fields2 = cl.getDeclaredFields();
}catch (Exception e){
e.printStackTrace();
}
获取类的成员方法
//根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
Method m1 = cl.getMethod("test", String.class);
//获取所有的public修饰的成员方法,包括父类中
Method[] methods1 = cl.getMethods();
//根据方法名和参数类型获得一个方法对象,包括private修饰的
Method m2 = cl.getDeclaredMethod("test", String.class);
//获取当前类中所有的方法,包含私有的,不包括父类中
Method[] declaredMethods = cl.getDeclaredMethods();
实战部分
用自定义注解和反射实现页面跳转的参数注入(Kotlin版本):
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectIntent {
String value();
}
注解使用工具类:
object InjectUtils {
public fun injectIntent(activity: Activity) {
val javaClass = activity.javaClass
val intent = activity.intent
val extras = intent.extras
val fields = javaClass.declaredFields
fields.forEach { field ->
if (field.isAnnotationPresent(InjectIntent::class.java)) {
val annotation = field.getAnnotation(InjectIntent::class.java)
val key = if (TextUtils.isEmpty(annotation.value)) field.name else annotation.value
if (extras != null) {
if (extras.containsKey(key)) {
var value = extras[key]
val componentType = field.type.componentType
if (componentType!!.isArray && Parcelable::class.java.isAssignableFrom(
componentType
)
) {
val objects = value as Array<Any>
val temp = Arrays.copyOf(objects, objects.size,field.type as Class<Array<out Any>>)
value = temp
}
field.isAccessible = true
field.set(activity, value)
}
}
}
}
}
}
使用:
class SecondActivity : AppCompatActivity() {
@InjectIntent("name")
private lateinit var name:String
@InjectIntent("age")
private lateinit var age:String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
InjectUtils.injectIntent(this)
Log.i("SecondActivity","==========$name==============$age")
}
}
运行结果非常简单,就不再贴出。