使用反射搞破坏

  本文主要介绍几个关于反射的使用例子,给大家分享一下有趣的知识点:
    1.使用反射改变自动装箱后的值
    2.改变String的不可变性
    3.破坏泛型的约束
    4.破坏单例
        4.1.反射调用已有的构造函数
        4.2.添加一个构造函数
        4.3.使用Unsafe创建实例

   关于反射的知识点大家应该已经知道,这里就不多说了。

1.使用反射改变自动装箱后的值

对于基本数据类型都有对应的包装类,jdk提供了自动装箱和拆箱的功能,当一个int类型的数据装箱成Integer时实际上是调用了Integer.valueOf(int i)方法:
public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
}

也就是说对于-128到IntegerCache.high的int值在自动装箱时是从IntegerCache.cache里拿到的事先创建好的Integer对象,那么我们可以改变这个cache从而改变自动装箱后的值:
public static void main(String[] args) throws Exception {
		//获取IntegerCache类中的cache字段
		Class integerCache = Class.forName("java.lang.Integer$IntegerCache");
		Field cacheField = integerCache.getDeclaredField("cache");
		cacheField.setAccessible(true);
		//得到实际的cache
		Integer[] cache = (Integer[]) cacheField.get(null);
		Integer integer = -1;
		for (int i = 0; i < cache.length; i++) {//改变cache的内容
			cache[i] = integer;
		}
		
		for (int i = -128; i <= 127; i++) {
			System.out.print((Integer)i);
		}
		
	}

上面这段代码打印的值对于每个i来说都是-1。

2.改变String的不可变性

大部分人都会告诉你String对象是不可变的,因为它定义为final的,而且内部大部分字段也是final的,但是String对象确实是可以变的,先看一下它的定义:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; 
     
    ……
}

对于String str = “abcd”来说,实际上是在常量池中存在一个字面量为"abcd"的String对象,而"abcd"是存储在private final char value[];这个字符数组里面的,所以我们可以拿到这个char[],然后对其进行修改:
public static void main(String[] args) throws Exception {
		
		final String str1 = "abcd";
		final String str2 = new String("abcd");
		
		System.out.println(str2 == str1);
		
		Field valueField = String.class.getDeclaredField("value");//获取私有字段value
		valueField.setAccessible(true);//设置访问权限
		char[] value = (char[]) valueField.get(str1);//得到str1所引用对象的value字段的值
		value[0] = '0';
		value[1] = '1';
		value[2] = '2';
		value[3] = '3';
		
		System.out.println(str1);
		System.out.println(str2);
	}

这里输出的是0123而不是abcd,对于刚创建的str2来说,它是一个引用变量指向内存中的一个字面量也是"abcd"的String对象,只是这个String对象实际上和str1所指向的String对象共用了同一个字符数组,所以当str1所指向的String对象里的字符数组发生改变时str2所指向的String对象的值也发生了改变。

3.破坏泛型的约束

对于List<String> list = new ArrayList<String>();来说,因为加了泛型约束,list.add(..)的时候你只能加入String类型的对象,而通过反射获取add方法后就可以加入你想加入的类型了:
public static void main(String[] args) throws Exception {
		List<String> list = new ArrayList<String>();
		//得到add方法
		Method method = List.class.getMethod("add", Object.class);
		//放入一个Integer类型的对象
		method.invoke(list, new Integer(1));
		
		System.out.println(list.size());
	}


4.破坏单例
4.1.反射调用已有的构造函数

单例的目的是为了内存中只能创建一个实例,它的实现有很多种:懒汉、饿汉、DCL、静态内部类、单个枚举等,不论哪种方式都是可以破坏它只能创建一个实例的目的,以静态内部类为例:
public class Singleton {
	
	private Singleton(){
	}
	
	public static Singleton getInstance() {
		return InstanceHolder.instance;
	}
	
	private static class InstanceHolder {
		private static Singleton instance = new Singleton();
	}
}


单例的一个重要的约束是构造函数私有化,不过我们通过反射还是可以创建对象的:
public static void main(String[] args) throws Exception {
		//得到构造函数
		Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
		//设置访问权限
		constructor.setAccessible(true);
		//创建对象
		Singleton singleton = constructor.newInstance(null);
		
		System.out.println(singleton == Singleton.getInstance());
	}


大家都知道使用已有的构造函数可以创建对象,其实不使用已有的构造函数也是可以创建对象的,这里介绍4.2、4.3两种:

4.2.添加一个构造函数

不使用已有的构造函数,给类新添加一个构造函数用来创建对象:
public static void main(String[] args) throws Exception {
		//得到Object的无参构造函数
	    Constructor javaLangObjectConstructor = Object.class.getConstructor();
	    //给Singleton新加一个构造函数
	    Constructor c = ReflectionFactory.getReflectionFactory()
	    		.newConstructorForSerialization(Singleton.class, javaLangObjectConstructor);
	    c.setAccessible(true);
	    //创建实例
	    Singleton singleton = (Singleton) c.newInstance(null);
	    
	    System.out.println(singleton == Singleton.getInstance());
	}



4.3.使用Unsafe创建实例

Unsafe中提供了一个allocateInstance方法可以用来创建对象,Unsafe其实是一个单例,只是提供的获取Unsafe的方法中有安全监测致使我们不能通过它拿到内部的Unsafe对象,不过我们可以通过反射拿到内部的theUnsafe字段,进而得到已创建好的Unsafe对象:
public static void main(String[] args) throws Exception {
		//通过Unsafe创建对象
		Singleton allocateInstance = (Singleton) getUnsafe().allocateInstance(Singleton.class);

		System.out.println(allocateInstance == Singleton.getInstance());
	}
	//通过反射拿到Unsafe对象
	private static Unsafe getUnsafe() throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        return (Unsafe)f.get(null);
	}



本文例子中使用的jdk版本是hotspot 1.6,文中提到的几个例子在实际开发中可能用不到,但是对学习java还是有帮助的。

猜你喜欢

转载自lidesheng.iteye.com/blog/2256935
今日推荐