反射的应用
最近在公司搞一些llt的白盒测试用例,经常需要使用到反射进行实例的替换,将实际的类替换为伪装代理类来进行数据的打桩。这个时候会遇到很多需要访问私有属性的情况,甚至需要访问到final的属性,这个时候,需要进行一些特殊的处理。如果写的不对,欢迎各位在评论区进行提点,试验后,我会进行相应的修改。
先来个示例的bean类:
1 package com.iwuyc.test.powermockito.powermockito.test; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 /** 6 * Hello world! 7 * 8 */ 9 public class DemoBean 10 { 11 private static final AtomicInteger privateStaticFinalField = new AtomicInteger(); 12 13 private static AtomicInteger privateStaticField = new AtomicInteger(); 14 15 private AtomicInteger privateField = new AtomicInteger(); 16 17 public static AtomicInteger getPrivatestaticfinalfield() 18 { 19 return privateStaticFinalField; 20 } 21 22 public static AtomicInteger getPrivateStaticField() 23 { 24 return privateStaticField; 25 } 26 27 public AtomicInteger getPrivateField() 28 { 29 return privateField; 30 } 31 32 33 }
·访问private修饰的属性:
1 @Test 2 public void testGetPrivateField() throws Exception 3 { 4 DemoBean demoBean = new DemoBean(); 5 AtomicInteger valOld = demoBean.getPrivateField(); 6 7 Field privateField = DemoBean.class.getDeclaredField("privateField"); 8 //修改可访问的权限,对于私有的不可访问的,我们可以通过设置改值进行强制访问 9 //该方法不仅仅是只有Field的实力中存在,在Method、Construct中也存在该方法 10 //也就是说,访问私有的Method跟私有的Construct也是可以进行强制访问的 11 privateField.setAccessible(true); 12 13 privateField.set(demoBean, new AtomicInteger()); 14 15 AtomicInteger valNew = demoBean.getPrivateField(); 16 17 Assert.assertFalse("Two values is equals,but that was not my expected.",valOld.equals(valNew)); 18 }
·访问private static修饰的属性
1 @Test 2 public void testGetPrivateStaticField() throws Exception 3 { 4 AtomicInteger valOld = DemoBean.getPrivateStaticField(); 5 6 Field privateField = DemoBean.class.getDeclaredField("privateStaticField"); 7 privateField.setAccessible(true); 8 9 //该方法跟访问私有的属性是类似的,不同的只是,在调用Field实例的set方法的时候,可以不传实例 10 //当然传实例也是可以进行设置的 11 privateField.set(null, new AtomicInteger()); 12 13 AtomicInteger valNew = DemoBean.getPrivateStaticField(); 14 Assert.assertFalse("Two values is equals,but that was not my expected.", valOld.equals(valNew)); 15 }
·访问private static final修饰的属性
1 @Test 2 public void testGetPrivateStaticFinalField() throws Exception 3 { 4 AtomicInteger valOld = DemoBean.getPrivatestaticfinalfield(); 5 6 Field targetField = DemoBean.class.getDeclaredField("privateStaticFinalField"); 7 targetField.setAccessible(true); 8 9 //其实访问final的属性,只需要将这个Field的一个标志位(modifiers)修改为非final的 10 Field modifiersField = Field.class.getDeclaredField("modifiers"); 11 modifiersField.setAccessible(true); 12 //利用位运算,计算出非final的位,这题步是关键 13 modifiersField.set(targetField, targetField.getModifiers() & ~Modifier.FINAL); 14 15 targetField.set(null, new AtomicInteger()); 16 AtomicInteger valNew = DemoBean.getPrivatestaticfinalfield(); 17 Assert.assertFalse("Two values is equals,but that was not my expected.", valOld.equals(valNew)); 18 }
关于这一段代码,我稍微解释下吧。
我在研读Field实例的代码的时候,发现,其中存在一个modifiers的属性,这个属性记录着对应属性的修饰符,通过修改final的位,可以进行修改final这个修饰符,来达到强制替换属性实例的目的,这个修改会导致该属性变为非final的。
~~~~~~~~~~~~~2016-11-30 23:54:36 补充编辑~~~~~~~~~~~~~
今天在调用方法的时候遇到了参数列表中存在值类型的问题。
这个时候,应当使用使用下面的类型进行获取方法:
1 long.class <==> Long.TYPE 2 double.class <==> Double.TYPE 3 float.class <==> Float.TYPE 4 bool.class <==> Boolean.TYPE 5 char.class <==> Character.TYPE 6 byte.class <==> Byte.TYPE 7 void.class <==> Void.TYPE 8 short.class <==> Short.TYPE
上述表示各值类型对应的类类型,在查找方法的时候,需要将相应的值类型传入参数列表中。
范例如下:
public class DemoClass { public void getMethod(int input) { System.out.println("int:" + input); } public void getMethod(Integer input) { System.out.println("Integer:" + input); } }
调用两个方法的方式如下:
public class ReflectiveTest { @Test public void testGetIntMethod() throws Exception { Method getMethod = DemoBean.class.getDeclaredMethod("getMethod", Integer.TYPE/*or use int.class*/); DemoBean demoBean = new DemoBean(); getMethod.invoke(demoBean, 10); } @Test public void testGetIntegerMethod() throws Exception { Method getMethod = DemoBean.class.getDeclaredMethod("getMethod", Integer.class/*Different from int.class*/); DemoBean demoBean = new DemoBean(); getMethod.invoke(demoBean, 10); } }
分别输出:
int:10 Integer:10
~~~~~~~~~~~~~2016年12月6日 22:12:14 补充编辑~~~~~~~~~~~~~
示例代码:
1 package com.iwuyc.test.reflective.tool; 2 3 public class DemoBean 4 { 5 6 class InnerDemoBean 7 { 8 public InnerDemoBean() 9 { 10 11 } 12 13 public InnerDemoBean(String name) 14 { 15 } 16 } 17 }
测试代码:
1 package com.iwuyc.test.reflective.tool; 2 3 import static org.junit.Assert.*; 4 import java.lang.reflect.Constructor; 5 6 import org.junit.Test; 7 8 public class DemoBeanTest 9 { 10 String innerDemoBeanName = DemoBean.class.getName() + "$InnerDemoBean"; 11 12 @Test 13 public void testWrong() throws Exception 14 { 15 // Wrong demo 16 Class<?> clazz = Class.forName(innerDemoBeanName); 17 Constructor<?> constructor = clazz.getDeclaredConstructor(String.class); 18 constructor.newInstance("hello"); 19 } 20 21 @Test 22 public void testValid() throws Exception 23 { 24 // Valid demo 25 DemoBean demoBeanInstance = new DemoBean(); 26 27 Class<?> clazz = Class.forName(innerDemoBeanName); 28 Constructor<?> constructor = clazz.getDeclaredConstructor(DemoBean.class, String.class); 29 Object innerDemoBeanInstance = constructor.newInstance(demoBeanInstance, "hello"); 30 assertTrue(innerDemoBeanName.equals(innerDemoBeanInstance.getClass().getName())); 31 } 32 }
在方法testWrong()中,会报 java.lang.NoSuchMethodException 错误,出现这个异常的原因是参数列表的类型给错了,为什么会出现这样的问题呢?
其实问题出在 InnerDemoBean 中,因为在这个类的声明没有加上static,也就是说,这个类只会在DemoBean进行实例化的时候才会进行加载,并且,会在所有的构造函数中的参数列表中默认加上DemoBean的形参。
也就是说原来的 public InnerDemoBean() public InnerDemoBean(String name) 两个方法,在反射中变成了 public InnerDemoBean(DemoBean demoBean) public InnerDemoBean(DemoBean demoBean,String name) 。
此时,想要找到这两个构造函数,则需要想第28行那样,将DemoBean传入参数列表中以获取对应的构造函数,并且在使用该构造函数的时候,需要将DemoBean的一个实例传入参数列表中,以便参数构造函数使用。