1、概述
熟悉android开发的都会了解到Android的xUtils框架的ViewUtils模块,简化了findViewById的编写,通过完全注解方式就可以进行UI,资源和事件绑定。实现基本原理就是通过java中的注解和反射实现,本文主要介绍java中的反射机制和自定义注解的原理和实例编写。
2、反射机制
定义:反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
(1)反射机制提供的主要功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 生成动态代理。
3、反射机制应用案例
(1)获取某个类的属性
package com.chunsoft.testReflect;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class TestReflect{
private String testField1 = "testField1";
public int value = 2017;
public static void main(String[] args) throws Exception {
//获取当前类的字节码
Class<?> clazz = Class.forName("com.chunsoft.testReflect.TestReflect");
System.out.println("===============本类所有属性===============");
//获取本类的所有属性
Field[] declaredFields = clazz.getDeclaredFields();
for(int i = 0;i < declaredFields.length;i ++) {
//权限修饰符
int mo = declaredFields[i].getModifiers();
String priv = Modifier.toString(mo);
//属性类型
Class<?> type = declaredFields[i].getType();
String name = type.getName();
System.out.println(priv + " " +
name + " " + declaredFields[i].getName() + ";");
}
System.out.println("==========获取public的属性==========");
// 取得public的属性
Field[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
//权限修饰符
int mod = fields[i].getModifiers();
String priv = Modifier.toString(mod);
//属性类型
Class<?> type = fields[i].getType();
String name = type.getName();
System.out.println(priv + " " +
name + " " + fields[i].getName() + ";");
}
}
}
(2)获取某个类的全部方法
package com.chunsoft.testReflect;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestReflectMothod {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.chunsoft.testReflect.TestReflectMothod");
//获取Public类型方法和实现的接口或者父类的方法
//Method method[] = clazz.getMethods();
//获取所有方法
Method method[] = clazz.getDeclaredMethods();
for (int i = 0; i < method.length; i++) {
//获取返回类型
Class<?> returnType = method[i].getReturnType();
//获取参数类型
Class<?>[] parameterTypes = method[i].getParameterTypes();
//获取权限修饰符
int temp = method[i].getModifiers();
System.out.print(Modifier.toString(temp) + " ");
System.out.print(returnType.getName() + " ");
System.out.print(method[i].getName() + " ");
System.out.print("(");
for (int j = 0; j < parameterTypes.length; ++j) {
System.out.print(parameterTypes[j].getName() + " " + "arg" + j);
if (j < parameterTypes.length - 1) {
System.out.print(",");
}
}
//获取异常类型
Class<?> exce[] = method[i].getExceptionTypes();
if (exce.length > 0) {
System.out.print(") throws ");
for (int k = 0; k < exce.length; ++k) {
System.out.print(exce[k].getName() + " ");
if (k < exce.length - 1) {
System.out.print(",");
}
}
} else {
System.out.print(")");
}
System.out.println();
}
}
private String testMethod(int age ,String name) {
return name+":"+age;
}
}
(3)通过反射机制调用某个类的方法
package com.chunsoft.testReflect;
import java.lang.reflect.Method;
public class TestReflectUse {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.chunsoft.testReflect.TestReflectUse");
// 调用TestReflectUse类中的reflect1方法
Method method = clazz.getMethod("reflect1");
method.invoke(clazz.newInstance());
// Java 反射机制 - 调用某个类的方法1.
// 调用TestReflect的reflect2方法
method = clazz.getMethod("reflect2", int.class, String.class);
method.invoke(clazz.newInstance(), 20, "张三");
// Java 反射机制 - 调用某个类的方法2.
// age -> 20. name -> 张三
}
public void reflect1() {
System.out.println("Java 反射机制 - 调用某个类的方法1.");
}
public void reflect2(int age, String name) {
System.out.println("Java 反射机制 - 调用某个类的方法2.");
System.out.println("age -> " + age + ". name -> " + name);
}
}
更多用法示例
4、自定义注解
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在java.lang.annotation 包中。
(1)注解的原理
JDK5.0中提供了注解的功能,允许开发者定义和使用自己的注解类型。该功能由一个定义注解类型的语法和描述一个注解声明的语法,读取注解的API,一个使用注解修饰的class文件和一个注解处理工具组成。
目前已被广泛应用于各种Java框架,如Hibernate,Jersey,Spring。注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定注解在编译期或运行期有效。
元注解是指注解的注解。包括 @Retention @Target @Document @Inherited四种。
(2)创建自定义注解
使用@interface自定义注解时,自动继承java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
- 自定义注解:
public @interface MyAnnotation {
}
- 自定义注解使用
public class AnnotationTest {
@MyAnnotation
public void execute(){
System.out.println("method");
}
}
- 添加变量
public @interface MyAnnotation {
String value1();
}
当注解中使用的属性名为value时,对其赋值时可以不指定属性的名称而直接写上属性值接口;除了value意外的变量名都需要使用name=value的方式赋值。
- 自定义注解使用
public class AnnotationTest {
@MyAnnotation(value1="abc")
public void execute(){
System.out.println("method");
}
}
- @Retention注解可以在定义注解时为编译程序提供注解的保留策略。
CLASS
编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释。
RUNTIME
编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
SOURCE
编译器要丢弃的注释。 - @Target – 表示支持注解的程序元素的种类,一些可能的值有TYPE, METHOD, CONSTRUCTOR, FIELD等。如果Target元注解不存在,那么该注解就可以使用在任何程序元素之上。
- Java提供3种内置注解
- @Override – 当我们想要覆盖父类的一个方法时,需要使用该注解告知编译器我们正在覆盖一个方法。这样的话,当父类的方法被删除或修改了,编译器会提示错误信息。大家可以学习一下为什么我们总是应该在覆盖方法时使用Java覆盖注解。
- @Deprecated – 当我们想要让编译器知道一个方法已经被弃用(deprecate)时,应该使用这个注解。Java推荐在javadoc中提供信息,告知用户为什么这个方法被弃用了,以及替代方法是什么。
- @SuppressWarnings – 这个注解仅仅是告知编译器,忽略它们产生了特殊警告,比如:在java泛型中使用原始类型。它的保持性策略(retention policy)是SOURCE,在编译器中将被丢弃。
5、反射和自定义注解结合
大部分java框架的实现通过反射机制和自定义的结合,本文介绍反射和自定义注解的简单结合。
(1)实现自定义注解类
新建Annotation方法,ViewInject.class:
package com.chunsoft.AnnotationTest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解类
@Target(ElementType.FIELD) //用于限制当前自定义注解类的作用的对象
//@Retention(RetentionPolicy.SOURCE) //该注解类只会在源码中出现,当将源码编译成注解码时,注解信息就会被清除
//@Retention(RetentionPolicy.CLASS) //该注解类会被编译到注解码中,但是当虚拟机加载字节码时,注解信息会被清除
@Retention(RetentionPolicy.RUNTIME) //该注解类,永远保留到被加载到虚拟机中
public @interface ViewInject {
int age();
String name();
}
(2)在实体类中使用注解
新建一个实体类并在属性上使用注解,User.class:
package com.chunsoft.AnnotationTest;
public class User {
@ViewInject(age=21,name="chunsoft")
private String name;
private int age;
private String eat(String eat) {
System.out.println("eat:"+eat);
return eat + " 真好吃";
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
(3)反射和自定义注解测试
package com.chunsoft.AnnotationTest;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AnnotationMainTest {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//需求:获取User类中name字段上的自定义注解的值,
//然后将该值的age通过反射设置给User对象的age属性,将name设置给User对象的name属性
User user = new User();
/**
* 1.获取User类的字节码
*/
// user.getClass();
// User.class;
// Class.forName("");
Class clazz= User.class;
/**
* 2.将字节码中的name字段获取到
*/
// clazz.getField("");//只能获取声明为public的字段
Field declaredField = clazz.getDeclaredField("name");
Field declaredFieldAge = clazz.getDeclaredField("age");
/**
* 3.将当前字段上的注解对象获取到
*/
ViewInject viewInject = declaredField.getAnnotation(ViewInject.class);
if (viewInject != null) {
/**
* 4.获取自定义注解对象的参数
*/
int age = viewInject.age();
String name = viewInject.name();
System.out.println("name="+name+",age="+age);
/**
* 5.通过反射将这两个值反射给User对象
*/
declaredField.setAccessible(true); //设置允许访问,其实就是允许暴力反射
declaredFieldAge.setAccessible(true);
//将user对象的declaredField设置为name
declaredField.set(user, name);
declaredFieldAge.set(user, age);
System.out.println(user.toString());
}else {
System.out.println("字段上面没有自定义注解");
}
//通过反射调用eat对象的方法
Method declaredMethod = clazz.getDeclaredMethod("eat", String.class);
//设置允许访问
declaredMethod.setAccessible(true);
//暴力反射调用该方法
Object result = declaredMethod.invoke(user, "牛肉拉面");
System.out.println(result);
}
}
本文简单介绍了java反射机制和自定义注解原理分析和实例,下篇文章将实现基本的ViewUtils基本功能,减少重复编码。