反射那些事
一直以为只要把类的成员变量设置为private,或者方法设置为private,那么他就对外完全隐藏,类外部无法直接对该成员变量或者方法进行直接访问。但是java的反射,拥有十分强大的功能,它可以访问类中的任意成员变量和方法,我们可以通过反射直接入侵类的私有变量和私有方法,私有构造器。我们来了解一下反射的一些用法吧。
package com.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
class Person{
public final Integer id=1001;
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
private void say(String message){
System.out.println("You want to say: "+message);
}
}
public class Reflect {
@Test
public void testGetField() throws ClassNotFoundException {
Class<?> clazz=Class.forName("com.reflect.Person");
//获得声明为public域的字段
System.out.println("Fields: "+Arrays.toString(clazz.getFields()));
//获取所有的字段
System.out.println("Declared Fields: "+Arrays.toString(clazz.getDeclaredFields()));
}
//通过类型标签我们只能调用无参的构造器
//以下是如果通过反射调用带参数的构造器
@Test
public void test() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz=Class.forName( "com.reflect.Person" );
Constructor c=clazz.getConstructor(String.class);
Person person= (Person) c.newInstance("viscu");
System.out.println(person.getName());
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
Person person=new Person();
Class<?> classType=person.getClass();
//访问私有方法
//getDeclaredMethod可以获取到所有方法,而getMethod只能获取public
Method method=classType.getDeclaredMethod("say", String.class);
//设置为true 可以忽略对类相关访问权限的检查
method.setAccessible(true);
//通过反射调用person类中的private方法
method.invoke(person,"check");
//入侵类内部的私有成员变量
Field field=classType.getDeclaredField("name");
field.setAccessible(true);
field.set(person,"walk");
System.out.println(person.getName());
}
}
以上就是通过反射对类的私有域进行访问的一些例子,利用反射我们可以轻易地入侵到类的内部,举个通过入侵改变类的运行机制的例子。
单例模式(顾名思义:就是该类型的类只有一个实例)
public class SingletonTest {
private static final SingletonTest INSTANCE = new SingletonTest();
private SingletonTest(){
//todo
}
public static SingletonTest getInstance(){
return INSTANCE;
}
public void show(){
System.out.println("Singleton using static initialization in Java");
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
SingletonTest demo=SingletonTest.getInstance();
demo.show();
Class<?> clazz=SingletonTest.class;
SingletonTest o= (SingletonTest) clazz.newInstance();
o.show();
System.out.println(demo==o);
}
}
虽然我们把Singleton的构造器设置为private了,可是我们还是可以通过反射轻易地构造出第二个SingletonTest 实例,第三个,第四个.....等,那么就违背了单例模式的本意了。
至于如何预防
- 我们可以在私有构造器中抛出一个异常即可防止构建新的实例。
private SingletonTest(){
if(INSTANCE!=null){
throw new RuntimeException("you can't create another one");
}
}
或者
我们可以通过枚举来实现Singleton,也可以防止被反射。
public enum EnumTest {
INSTANCE;
public void show(){
System.out.println("Singleton");
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<?> clazz=EnumTest.class;
EnumTest demo= (EnumTest) clazz.newInstance();
demo.show();
}
}
//我们利用反射来创建EnumTest的实例会抛出java.lang.InstantiationException: com.reflect.EnumTest异常
//我们也可以用这种方式来预防反射。
反射,让我们原本以为安全的类变得不那么安全了,别人可以轻易入侵你的类内部改变你的原有数据,至于如何预防,有待研究。
研究了一下反射,发现即使利用final修饰加饿汉式好像也可以被修改的,具体如下:
public class SingletonTest {
private static final SingletonTest INSTANCE=new SingletonTest();
private SingletonTest(){
if(INSTANCE!=null){
throw new RuntimeException("xxx");
}
}
public void show(){
System.out.println("Singleton using static initialization in Java");
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
//获得INSTANCE字符
Field field=SingletonTest.class.getDeclaredField("INSTANCE");
//忽略对访问权限进行检查
field.setAccessible(true);
//这部分是用来忽略final关键字
Field modifiersField=Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field,field.getModifiers()&~Modifier.FINAL);
//我们把INSTANCE设置为null.
field.set(null,null);
System.out.println(INSTANCE);
SingletonTest test=SingletonTest.class.newInstance();
SingletonTest test1=SingletonTest.class.newInstance();
System.out.println(test==test1); //false
}
}
即使使用final修饰,我们依然可以修改INSTANCE的值,使构造器中的判断失效导致我们可以构造大量不同的实例。
反射真的是无所不能啊,感慨一下。
不过也有反射修改不了的值,看例子。
class Bean{
private static final boolean INT_VALUE=false;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
System.out.println(INT_VALUE);
Class<?> clazz=Bean.class;
Field field=clazz.getDeclaredField("INT_VALUE");
field.setAccessible(true);
//去除final修饰符的影响,将字段设为可修改的
Field modifiersField=Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field,field.getModifiers()&~Modifier.FINAL);
field.set(null,true);
System.out.println(INT_VALUE);
}
}
虽然调试显示为true,可是最终打印出来为false,反射在基本数据类型(int,short,boolean等)的常量上就失效了。
那么针对这些基本数据类型的常量,我们真的无法修改了吗,其实不然。
我们先讲一下上面的为啥无法再进行修改了。
因为java在编译时会为我们进行优化,假设有
if(INT_VALUE){
//todo
}
//java会直接优化成
if(false){
//todo
}
//显然这样子做可以提高效率,但是我们对INT_VALUE的值无法进行修改了。
那么,我们有办法修改基本数据类型的常量吗,当然有,我们只需要避开jvm编译时的优化即可。
class BeanTest{
private final int anInt; //不在声明常量的地方赋值 这样就可以避开jvm对普通数据类型常量进行优化
private final int bInt;
//你也可以不在构造器中进行复制
//private final int bInt=null==null?66:null; 利用三目运算法来避开编译时的优化
//private static final int bInt=null==null?66:null; //静态常量不可以利用构造器,但是可以利用三目运算法来避开编译时的优化
public BeanTest(int anInt, int bInt) { //在构造其中赋值
this.anInt = anInt;
this.bInt = bInt;
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
BeanTest test=new BeanTest(1024,1024);
Field field=test.getClass().getDeclaredField("bInt");
field.setAccessible(true);
Field modifiers=Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers()&~Modifier.FINAL);
field.set(test,502);
System.out.println(test.anInt+" "+test.bInt);
//可以看到 普通数据类型的静态常量是可以被修改的
}
}
大概就这些吧,有不对的地方,请多指教,谢谢。
参考 -JAVA反射修改常量,以及其局限
参考 -Java 反射由浅入深 | 进阶必备
参考 -Modifying final fields in Java
参考 -How to Access Private Field and Method Using Reflection in Java