反射

类加载过程的核心:任何一个类被系统使用时候都会将class文件加载进内存并创建一个Class对象,同时会初始化静态成员
类加载时机:

  • 创建类的实例
  • 访问类的静态变量,或者为静态变量赋值
  • 调用类的静态方法
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类
负责人:类加载器,其分类有
BootStrap ClassLoader 根类加载器,负责Java核心类的加载,比如System,String等等,位于jre--lib--rt文件中
Extension ClassLoader 扩展加载器,负责JRE拓展包中的类加载,jre--lib--ext
System ClassLoader 系统加载器,负责在JVM启动时加载由java命令启动的class文件,以及classPath所指定的jar包和类
【反射】反射定义:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.而Class对象是在运行期间才能确定的,所以反射运用在运行期间。
对于每一种类,Class对象只有唯一的一个,但对象实例可以new多个。
获取反射对象有三种方式
Student student = new Student("Leo"); //one Class clazz = student.getClass(); //two Class clazz1 = Student.class; //three try { Class clazz2 = Class.forName("Model_4.Student"); } catch (ClassNotFoundException e) { e.printStackTrace(); }

开发中使用更多的是方式三,因为变化的是字符串,代码设计更灵活。
获取到Class对象后我们就操作该对象中的方法
public Constructor[] getConstructors()//所有公共构造方法 public Constructor[] getDeclaredConstructors()//所有构造方法 public Constructor getConstructor(Class... var1)//获取特定构造方法 public Constructor getDeclaredConstructor(Class... var1) //from Constructor public T newInstance(Object... var1)//创建该构造方法对应类型的实例,相当于new一个对象 public void setAccessible(boolean var1) 将能取消语言访问检查机制eg: Class clazz2 = null; try { clazz2 = Class.forName("Model_4.Student"); Constructor constructor = clazz2.getConstructor(String.class,int.class); Object object = constructor.newInstance("Leo",24); } catch (Exception e) { e.printStackTrace(); }//获取成员变量 public Field getField(String var1) public Field getDeclaredField(String var1) public Field[] getFields() public Field[] getDeclaredFields()eg: try { clazz2 = Class.forName("Model_4.Student"); Constructor constructor = clazz2.getConstructor(String.class); Student object = (Student) constructor.newInstance("Leo"); System.out.println(object.getOfficialName()); //print LeoField field = clazz2.getDeclaredField("officialName"); field.setAccessible(true); field.set(object,"PDDDDD"); System.out.println(object.getOfficialName()); //print PDDDDD } catch (Exception e) { e.printStackTrace(); }//获取方法 public Method getMethod(String var1, Class... var2) public Method getDeclaredMethod(String var1, Class... var2) public Method[] getMethods() public Method[] getDeclaredMethods() try {clazz2 = Class.forName("Model_4.Student"); Constructor constructor = clazz2.getConstructor(String.class); Student object = (Student) constructor.newInstance("Leo"); Method method1 = clazz2.getMethod("setOfficialName",String.class); method1.invoke(object,"PDDDDDD"); Method method2 = clazz2.getMethod("getOfficialName"); String string = (String) method2.invoke(object); System.out.println(string); } catch (Exception e) { e.printStackTrace(); }

常规class加载模式是预先加载需要使用的Class对象(文章开头提到了加载实际),然后我们就可以通过new对象的方式使用对象,而反射是运行过程中才加载Class对象,在此时根据需求创建实例。
其他注意事项
对于方法:
getDeclaredMethods:所有本类中声明的方法,包括继承或实现过来的方法(只返回本类的实现而非父类的)
getMethods:所有声明的公共方法,包括继承或实现过来的公共方法(只返回本类的实现而非父类的),也包括所有父类的公共方法
getDeclaredMethod getMethod同理
invoke只能调用公共方法,如果需要调用default、protected、private方法,需要提前调用method.setAccessible(true);
子类覆盖父类方法时候,方法限定符发生改变,上述规则不失效,即获取方法时候只返回一个最靠近继承链新生末端的方法。
对于域:
getDeclaredFields:所有本类中声明的域
getFields:所有本类和父类和接口中的公共域,包括重名且重类型的(域不存在继承链覆盖)
getDeclaredField getField同理
默认只能操作公共域,如果需要获取或操作default、protected、private域,需要提前调用field.setAccessible(true);
调用getDeclaredField getField如果和父类或接口有重名的域,只返回一个最靠近继承链新生末端的域
反射会降低程序运行效率,主要是寻找方法和成员变量的过程比较缓慢,真正运行方法和操作成员变量的过程是不慢的。
  • 在项目中使用反射
//设置配置文件,在项目中加载配置文件能够灵活实现功能,不需要时刻修改代码并重新编译项目 // 加载键值对数据 Properties prop = new Properties(); FileReader fr = new FileReader("class.txt"); prop.load(fr); fr.close(); // 获取数据 String className = prop.getProperty("className"); String methodName = prop.getProperty("methodName"); // 反射 Class c = Class.forName(className); Constructor con = c.getConstructor(); Object obj = con.newInstance(); // 调用方法 Method m = c.getMethod(methodName); m.invoke(obj);

  • 用反射跳出类型检查
//先看下面的代码 ArrayList list = new ArrayList<>(); list.add(10); //通过编译后,JVM实际采用的class文件通过反编译,得到的是 ArrayList list = new ArrayList(); list.add(Integer.valueOf(10));

本质上,编译器把我们的Java代码转化成了上述形式进行使用,相当于我们本来就是这么写的。
通过查看ArrayList源码,我们发现,ArrayList构建时声明的泛型类型其实是Object,我们在编码时通过泛型声明,编译器会将泛型对象转化为特定的对象,本例子中即为Integer.valueOf(),同样为一个Object。可见泛型类型确认是发生在编译过程中。
但是,ArrayList源码反应,其Class对象始终没有确认泛型对象,保持Object类型,所以我们可以做如下操作:
ArrayList list = new ArrayList<>(); list.add(10); try { Class clz = list.getClass(); Method method = clz.getMethod("add",Object.class); method.invoke(list,"pddddd"); method.invoke(list,"yjj"); method.invoke(list,new Student("leon")); System.out.println(list); //调用的是ArrayList的toString方法,里面是迭代调用元素的toString方法 } catch (Exception e) { e.printStackTrace(); }

  • 暴力操作对象的私有数据
    当项目中的文件没有提供对外操作的接口,,,
public class Tool { public void setProperty(Object obj, String propertyName, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { // 根据对象获取字节码文件对象 Class c = obj.getClass(); // 获取该对象的propertyName成员变量 Field field = c.getDeclaredField(propertyName); // 取消访问检查 field.setAccessible(true); // 给对象的成员变量赋值为指定的值 field.set(obj, value); } }

动态代理 代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象
动态代理:在程序运行过程中产生的这个对象
而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib
Proxy类中的方法创建动态代理类对象
public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
最终会调用InvocationHandler的方法
InvocationHandler
Object invoke(Object proxy,Method method,Object[] args)
Proxy类中创建动态代理对象的方法的三个参数;
ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
public class KaSha implements UserDao {ArrayList skills = new ArrayList<>(); int shuijin = 100; @Override public boolean addSkill(String skill) { return skills.add(skill); }@Override public void deleteAccessory(int number) { shuijin = shuijin - number; }@Override public void login() { System.out.println(this.getClass().getName()+" has log in"); }@Override public void exit() { System.out.println(this.getClass().getName()+" has log out"); } }public class HeroHandler implements InvocationHandler {private UserDao mUserDao; public HeroHandler(UserDao userDao) { mUserDao = userDao; }@Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("权限校验"); return method.invoke(mUserDao,objects); } }public static void main(String[] args) { UserDao userDao = new KaSha(); HeroHandler heroHandler = new HeroHandler(userDao); UserDao userDao1 = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),heroHandler); userDao1.login(); userDao1.addSkill("皇族旗帜"); userDao1.deleteAccessory(2); userDao1.exit(); } //输出结果中每个方法的实现都会转化为HeroHandler的invoke方法实现。这样对于需要批量增加的操作,就不需要在重写子类实现,比如上述的“权限检查”。

    推荐阅读