反射

作者: LeoFranz | 来源:发表于2019-08-11 10:24 被阅读0次

    类加载过程的核心:任何一个类被系统使用时候都会将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<T> getConstructor(Class... var1)//获取特定构造方法
            public Constructor<T> 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 Leo
    
                Field 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<Integer> 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<Integer> 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<String> 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方法实现。这样对于需要批量增加的操作,就不需要在重写子类实现,比如上述的“权限检查”。
    

    相关文章

      网友评论

          本文标题:反射

          本文链接:https://www.haomeiwen.com/subject/seldjctx.html