[Java]反射和注解

tech2023-10-19  92

注解的定义

注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他的反射、插桩等才有意义。 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") } }

运行结果非常简单,就不再贴出。

最新回复(0)