Android 浅谈反射与注解
一、使用反射获取类的信息
现在有两个用于测试的类,代码很简单,我们直接贴出来:
- FatherClass.java
public class FatherClass {
public String mFatherName;
public int mFatherAge;
public void printFatherMsg(){}
}
- SonClass.java
public class SonClass extends FatherClass{
private String mSonName;
protected int mSonAge;
public String mSonBirthday;
public void printSonMsg(){
System.out.println("Son Msg - name : "
+ mSonName + "; age : " + mSonAge);
}
private void setSonName(String name){
mSonName = name;
}
private void setSonAge(int age){
mSonAge = age;
}
private int getSonAge(){
return mSonAge;
}
private String getSonName(){
return mSonName;
}
}
1、获取类的所有变量信息
/**
* 通过反射获取类的所有变量
*/
private static void printFields(){
//1.获取并输出类的名称
Class mClass = SonClass.class;
System.out.println("类的名称:" + mClass.getName());
//2.1 获取所有 public 访问权限的变量
// 包括本类声明的和从父类继承的
Field[] fields = mClass.getFields();
//2.2 获取所有本类声明的变量(不问访问权限)
//Field[] fields = mClass.getDeclaredFields();
//3. 遍历变量并输出变量信息
for (Field field :
fields) {
//获取访问权限并输出
int modifiers = field.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
//输出变量的类型及变量名
System.out.println(field.getType().getName()
+ " " + field.getName());
}
}
以上代码注释很详细,就不再解释了。需要注意的是注释中 2.1 的 getFields() 与 2.2的 getDeclaredFields() 之间的区别,下面分别看一下两种情况下的输出。看之前强调一下:SonClass extends FatherClass extends Object
- 调用 getFields() 方法,输出 SonClass 类以及其所继承的父类( 包括 FatherClass 和 Object ) 的 public 方法。注:Object 类中没有成员变量,所以没有输出
类的名称:obj.SonClass
public java.lang.String mSonBirthday
public java.lang.String mFatherName
public int mFatherAge
- 调用 getDeclaredFields() , 输出 SonClass 类的所有成员变量,不问访问权限
类的名称:obj.SonClass
private java.lang.String mSonName
protected int mSonAge
public java.lang.String mSonBirthday
2、获取类的所有方法信息
/**
* 通过反射获取类的所有方法
*/
private static void printMethods(){
//1.获取并输出类的名称
Class mClass = SonClass.class;
System.out.println("类的名称:" + mClass.getName());
//2.1 获取所有 public 访问权限的方法
//包括自己声明和从父类继承的
Method[] mMethods = mClass.getMethods();
//2.2 获取所有本类的的方法(不问访问权限)
//Method[] mMethods = mClass.getDeclaredMethods();
//3.遍历所有方法
for (Method method :
mMethods) {
//获取并输出方法的访问权限(Modifiers:修饰符)
int modifiers = method.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
//获取并输出方法的返回值类型
Class returnType = method.getReturnType();
System.out.print(returnType.getName() + " "
+ method.getName() + "( ");
//获取并输出方法的所有参数
Parameter[] parameters = method.getParameters();
for (Parameter parameter:
parameters) {
System.out.print(parameter.getType().getName()
+ " " + parameter.getName() + ",");
}
//获取并输出方法抛出的异常
Class[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes.length == 0){
System.out.println(" )");
}
else {
for (Class c : exceptionTypes) {
System.out.println(" ) throws "
+ c.getName());
}
}
}
}
- 调用 getMethods() 方法 获取 SonClass 类所有 public 访问权限的方法,包括从父类继承的。打印信息中,printSonMsg() 方法来自 SonClass 类, printFatherMsg() 来自 FatherClass 类,其余方法来自 Object 类
类的名称:obj.SonClass
public void printSonMsg( )
public void printFatherMsg( )
public final void wait( ) throws java.lang.InterruptedException
public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
public final native void wait( long arg0, ) throws java.lang.InterruptedException
public boolean equals( java.lang.Object arg0, )
public java.lang.String toString( )
public native int hashCode( )
public final native java.lang.Class getClass( )
public final native void notify( )
public final native void notifyAll( )
- 调用 getDeclaredMethods() 方法
类的名称:obj.SonClass
private int getSonAge( )
private void setSonAge( int arg0, )
public void printSonMsg( )
private void setSonName( java.lang.String arg0, )
private java.lang.String getSonName( )
3、访问或操作类的私有变量和方法
都知道,对象是无法访问或操作类的私有变量和方法的,但是,通过反射,我们就可以做到。没错,反射可以做到!下面,让我们一起探讨如何利用反射访问 类对象的私有方法 以及修改 私有变量或常量。
- TestClass.java
public class TestClass {
private String MSG = "Original";
private void privateMethod(String head , int tail){
System.out.print(head + tail);
}
public String getMsg(){
return MSG;
}
}
1 访问私有方法
以访问 TestClass 类中的私有方法 privateMethod(...) 为例
/**
* 访问对象的私有方法
* 为简洁代码,在方法上抛出总的异常,实际开发别这样
*/
private static void getPrivateMethod() throws Exception{
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有方法
//第一个参数为要获取的私有方法的名称
//第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
//方法参数也可这么写 :new Class[]{String.class , int.class}
Method privateMethod =
mClass.getDeclaredMethod("privateMethod", String.class, int.class);
//3. 开始操作方法
if (privateMethod != null) {
//获取私有方法的访问权
//只是获取访问权,并不是修改实际权限
privateMethod.setAccessible(true);
//使用 invoke 反射调用私有方法
//privateMethod 是获取到的私有方法
//testClass 要操作的对象
//后面两个参数传实参
privateMethod.invoke(testClass, "Java Reflect ", 666);
}
}
需要注意的是,第3步中的 setAccessible(true) 方法,是获取私有方法的访问权限,如果不加会报异常 IllegalAccessException,因为当前方法访问权限是“private”的
正常运行后,打印如下,调用私有方法成功:
Java Reflect 666
2、修改私有变量
以修改 TestClass 类中的私有变量 MSG 为例,其初始值为 "Original" ,我们要修改为 "Modified"。老规矩,先上代码看注释
/**
* 修改对象私有变量的值
* 为简洁代码,在方法上抛出总的异常
*/
private static void modifyPrivateFiled() throws Exception {
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有变量
Field privateField = mClass.getDeclaredField("MSG");
//3. 操作私有变量
if (privateField != null) {
//获取私有变量的访问权
privateField.setAccessible(true);
//修改私有变量,并输出以测试
System.out.println("Before Modify:MSG = " + testClass.getMsg());
//调用 set(object , value) 修改变量的值
//privateField 是获取到的私有变量
//testClass 要操作的对象
//"Modified" 为要修改成的值
privateField.set(testClass, "Modified");
System.out.println("After Modify:MSG = " + testClass.getMsg());
}
}
此处代码和访问私有方法的逻辑差不多,就不再赘述,从输出信息看出 修改私有变量 成功:
Before Modify:MSG = Original
After Modify:MSG = Modified
二、注解
1、自定义注解
举个栗子, 结合例子讲解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TestAnnotation {
String value();
String[] value2() default "value2";
}
元注解的的意义参考上面的讲解, 不再重复, 这里看注解值的写法,其中默认值是可选的, 可以定义, 也可以不定义:
类型 参数名() default 默认值;
2、处理运行时注解
Retention的值为RUNTIME时, 注解会保留到运行时, 因此使用反射来解析注解.
使用的注解就是上一步的@TestAnnotation, 解析示例如下:
public class Demo {
@TestAnnotation("Hello Annotation!")
private String testAnnotation;
public static void main(String[] args) {
try {
// 获取要解析的类
Class cls = Class.forName("myAnnotation.Demo");
// 拿到所有Field
Field[] declaredFields = cls.getDeclaredFields();
for(Field field : declaredFields){
// 获取Field上的注解
TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
if(annotation != null){
// 获取注解值
String value = annotation.value();
System.out.println(value);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
此处只演示了解析成员变量上的注解, 其他类型与此类似
2、解析编译时注解
解析编译时注解需要继承AbstractProcessor类, 实现其抽象方法
public boolean process(Set annotations, RoundEnvironment roundEnv)
该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理.
举个例子:
// 指定要解析的注解
@SupportedAnnotationTypes("myAnnotation.TestAnnotation")
// 指定JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcesser extends AbstractProcessor {
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
for (TypeElement te : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
TestAnnotation testAnnotation = element.getAnnotation(TestAnnotation.class);
// do something
}
}
return true;
}
}
三、Android中使用编译时注解
- 1、自定义编译时注解
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface TestAnnotation {
String value() default "Hello Annotation";
}
- 2、解析编译时注解
// 支持的注解类型, 此处要填写全类名
@SupportedAnnotationTypes("myannotation.TestAnnotation")
// JDK版本,一般推荐java8
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
// 类名的前缀后缀
public static final String SUFFIX = "AutoGenerate";
public static final String PREFIX = "My_";
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
for (TypeElement te : annotations) {
for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
// 准备在gradle的控制台打印信息
Messager messager = processingEnv.getMessager();
// 打印
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString());
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getSimpleName());
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getEnclosingElement().toString());
// 获取注解
TestAnnotation annotation = e.getAnnotation(TestAnnotation.class);
// 获取元素名并将其首字母大写
String name = e.getSimpleName().toString();
char c = Character.toUpperCase(name.charAt(0));
name = String.valueOf(c+name.substring(1));
// 包裹注解元素的元素, 也就是其父元素, 比如注解了成员变量或者成员函数, 其上层就是该类
Element enclosingElement = e.getEnclosingElement();
// 获取父元素的全类名, 用来生成包名
String enclosingQualifiedName;
if(enclosingElement instanceof PackageElement){
enclosingQualifiedName = ((PackageElement)enclosingElement).getQualifiedName().toString();
}else {
enclosingQualifiedName = ((TypeElement)enclosingElement).getQualifiedName().toString();
}
try {
// 生成的包名
String genaratePackageName = enclosingQualifiedName.substring(0, enclosingQualifiedName.lastIndexOf('.'));
// 生成的类名
String genarateClassName = PREFIX + enclosingElement.getSimpleName() + SUFFIX;
// 创建Java文件
JavaFileObject f = processingEnv.getFiler().createSourceFile(genarateClassName);
// 在控制台输出文件路径
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + f.toUri());
Writer w = f.openWriter();
try {
PrintWriter pw = new PrintWriter(w);
pw.println("package " + genaratePackageName + ";");
pw.println("\npublic class " + genarateClassName + " { ");
pw.println("\n /** 打印值 */");
pw.println(" public static void print" + name + "() {");
pw.println(" // 注解的父元素: " + enclosingElement.toString());
pw.println(" System.out.println(\"代码生成的路径: "+f.toUri()+"\");");
pw.println(" System.out.println(\"注解的元素: "+e.toString()+"\");");
pw.println(" System.out.println(\"注解的值: "+annotation.value()+"\");");
pw.println(" }");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
}
return true;
}
}
看似代码很长, 其实很好理解. 只做了两件事, 1.解析注解并获取需要的值 2.使用JavaFileObject类生成java代码.
- 3、向JVM声明解析器
在java的同级目录新建resources目录, 新建META-INF/services/javax.annotation.processing.Processor文件, 文件中填写你自定义的Processor全类名
第一步 第二步然后打出jar包以待使用
Android中使用
- 使用apt插件
项目根目录gradle中buildscript的dependencies添加
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
module目录的gradle中, 添加
apply plugin: 'android-apt'
- 代码中调用
将之前打出的jar包导入项目中, 在MainActivity中写个测试方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
@TestAnnotation("hehe")
public void test(){
}
运行一遍项目之后, 代码就会自动生成.
以下是生成的代码, 在路径yourmodule/build/generated/source/apt/debug/yourpackagename中:
public class My_MainActivityAutoGenerate {
/** 打印值 */
public static void printTest() {
// 注解的父元素: com.example.pan.androidtestdemo.MainActivity
System.out.println("代码生成的路径: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/My_MainActivityAutoGenerate.java");
System.out.println("注解的元素: test()");
System.out.println("注解的值: hehe");
}
}
然后在test方法中调用自动生成的方法
@TestAnnotation("hehe")
public void test(){
My_MainActivityAutoGenerate.printTest();
}
会看到以下打印结果:
代码生成的路径: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/com/example/pan/androidtestdemo/MainActivityAutoGenerate.java
注解的元素: test()
注解的值: hehe