结合案例浅析反射机制


反射机制是什么?反射机制到底能做什么?反射机制的应用场景是什么?

反射机制的概念

说起反射机制就不得不提到java的类加载机制,类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种通过对象动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制(照镜子)。
要想解析一个类,必须先要获取到该类的字节码文件对象。而解析使用的就是Class类中的方法.所以先要通过类加载机制获取到每一个字节码文件对应的Class类型的对象.

获取Class对象的三种方法

1、Class。forName(类路径)
2、类名.class
3、对象.getClass()

问题引入

1、如何在不使用new关键字的情况下创建对象?
2、如何在只知道实例方法名的情况下执行方法?

反射实现对象拷贝

请看代码

测试类(Dod)


public class Dog {

	private int id;
	private String name;
	private int age;
	private String type;
	private boolean marry;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public boolean isMarry() {
		return marry;
	}
	public void setMarry(boolean marry) {
		this.marry = marry;
	}
	@Override
	public String toString() {
		return "Dog [id=" + id + ", name=" + name + ", age=" + age + ", type=" + type + ", marry=" + marry + "]";
	}
	public Dog() {
		super();
		
	}
	public Dog(int id) {
		super();
		this.id = id;
	}
}

实现拷贝(CopyDemo)

/**
 * 通过反射机制实现对任意对象的拷贝
 *
 */
public class CopyDemo {
	
	//实现任意对象的拷贝,返回的是Object对象
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Object clone(Object source) {
		Object target = null;
		try {
			//获得原对象的Class对象
			
			Class clz = source.getClass();
			target = clz.newInstance();
			//获取目标对象对应的类中的所有属性
			Field[] fields = clz.getDeclaredFields();
			//遍历所有属性
			for (Field f : fields) {
				//获取每一个属性的名称
				String fname = f.getName();
				System.out.println(f.getName());
				//分别获取属性对应的setter/getter方法名称
				String sname = "set"+fname.substring(0, 1).toUpperCase()+fname.substring(1);
				String gname = "get"+fname.substring(0, 1).toUpperCase()+fname.substring(1);
				
				//如果属性是boolean类型,则get改为is
				if ("boolean".equals(f.getType().getCanonicalName())) {
					 gname = "is"+fname.substring(0, 1).toUpperCase()+fname.substring(1);
				}
				
				//根据方法名以及属性类型获取方法对象
				Method methodSet= clz.getMethod(sname, f.getType());
				Method methodGet = clz.getMethod(gname);
				
				//执行原对象的get方法获取返回值
				Object returnVal = methodGet.invoke(source);
				
				//执行目标对象的set方法完成属性的赋值
				methodSet.invoke(target, returnVal);
				
			}
	
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	
		return target;
	}
public static void main(String[] args) {
		Dog d = new Dog();
		d.setId(1);
		d.setName("旺财");
		d.setAge(5);
		d.setType("二哈");
		Dog dog = (Dog) CopyDemo.clone(d); 
		}

通过给定的类名称,对其进行解析得到二进制字节码文件对象(java.lang.class),就可以得到目标对象对应的类中的所有属性,并存入到一个集合之中。
接下来遍历这个集合(如果输出就可以看到每一个属性的名称),为每一个属性名进行处理以及字符串的拼接就可以分别获取属性对应的setter&getter方法名称。 如果是布尔类型的,则前面的拼接符需要改为is。
紧接着根据拼接好的字符串方法名称以及属性类型来获取方法对象。执行原对象的get方法获取返回值,最后执行目标对象的set方法完成属性的赋值。这样就得到了一个一模一样的对象。

从以上的案例中可以看到我们仅仅知道类的名称,就可以获取到类中的各种信息。
以上代码还存在一定的局限性,在调用方法时需要强转为目标对象类型。
于是加入泛型进行优化
下面请看代码

	//任意的对象拷贝,返回具体的类型对象
	public static <T> T clone(Object source,Class<T> t) {
		T obj = null;
		try {
			//基于目标类型实例化对象
			obj = t.newInstance();
			Field[] fields = t.getDeclaredFields();
			for (Field f : fields) {
				//获取每一个属性的名称
				String fname = f.getName();
				
				//分别获取属性对应的setter/getter方法名称
				String sname = "set"+fname.substring(0, 1).toUpperCase()+fname.substring(1);
				String gname = "get"+fname.substring(0, 1).toUpperCase()+fname.substring(1);
				
				//如果属性是boolean类型,则get改为is
				if ("boolean".equals(f.getType().getCanonicalName())) {
					 gname = "is"+fname.substring(0, 1).toUpperCase()+fname.substring(1);
				}
				//根据方法名以及属性类型获取方法对象
				Method methodSet= t.getMethod(sname, f.getType());
				Method methodGet =t.getMethod(gname);
				
				//执行原对象的get方法获取返回值
				Object returnVal = methodGet.invoke(source);
				
				//执行目标对象的set方法完成属性的赋值
				methodSet.invoke(obj, returnVal);	
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}	
		return obj;
	}
public static void main(String[] args) {
		Dog d = new Dog();
		d.setId(1);
		d.setName("旺财");
		d.setAge(5);
		d.setType("二哈");

Dog dog = CopyDemo.clone(d, Dog.class);
}

反射+注解模拟测试框架(Jnit)

注解类(Text)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {

}

测试类

public class UserTest {

	@Test
	public void testInsert() {
		User user = null;
		System.out.println(user.getUsername());
	}
	
	@Test
	public void testQuery() {
		Blog b = new Blog();
		b.setTips(new String[] {"技术","java","多线程"});
		String[] tips = b.getTips();
		System.out.println(tips[3]);
	}
	
	@Test
	public void divide() {
		System.out.println(10/5);
	}
	
	@Test
	public void testClassCast() {
		String s = (String)new Object();
		System.out.println(s);
	}
}

反射单元实现

public class MyJunit {

    public static void main(String[] args) {
        try {
            //记录方法总数
            int methodCount = 0;
            //记录错误方法总数
            int expCount = 0;
            //准备一个文件的输出流,用于记录程序执行过程中的异常信息
            BufferedWriter bw = new BufferedWriter(new FileWriter("log.txt"));
            //获取类的Class对象
            Class clz = UserTest.class;
            //创建目标类型的实例对象
            Object obj = clz.newInstance();
            //获取所有的方法对象
            Method[] methods = clz.getMethods();
            //统计总共有多少方法需要被测试
            for (Method m : methods) {
                if(m.isAnnotationPresent(Test.class)) {
                    methodCount++;
                }
            }
            bw.write("测试方法总数:"+methodCount);
            bw.newLine();
            bw.write("====================================");
            bw.newLine();
            for (Method m : methods) {
                try {
                    //如果方法上面包含了Test注解则作为测试方法进行测试
                    if(m.isAnnotationPresent(Test.class)) {						
                        m.invoke(obj);
                    }
                }catch (Exception e) {
                    //异常方法计数器递增
                    expCount++;
                    bw.write(m.getName()+"出现异常");
                    bw.newLine();
                    bw.write("类型:"+e.getCause().getClass());
                    bw.newLine();
                    bw.write("原因:"+e.getCause().getMessage());
                    bw.newLine();
                    bw.write("--------------------------------");
                    bw.newLine();
                }
            } 
            bw.write("==============================");
            bw.newLine();
            bw.write("已测试方法数:"+methodCount+",其中异常方法:"+expCount);
            bw.flush();
            bw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然而这只是反射机制中的一个小应用

回到最初的问题

反射机制能做什么

1、在运行时判断任意一个对象所属的类;
2、在运行时构造任意一个类的对象;
3、在运行时判断任意一个类所具有的成员变量和方法;
4、在运行时调用任意一个对象的方法;

反射机制的应用场景

1、逆向代码 ,例如反编译
2、与注解相结合的框架 例如Retrofit
3、单纯的反射机制应用框架 例如EventBus
4、动态生成类框架 例如Gson

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值