反射

类加载过程的核心:任何一个类被系统使用时候都会将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方法实现。这样对于需要批量增加的操作,就不需要在重写子类实现,比如上述的“权限检查”。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容