java反射及应用

11 篇文章 0 订阅

1.反射概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。

反射就是把java类中的各种成分映射成一个个的Java对象

例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

(其实:一个类中这些成员方法、构造方法、在加入类中时,都有一个类来描述,称为class类)

如图是类的正常加载过程:反射的原理在于class对象。

熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

 

 其中这个Class对象很特殊。我们先了解一下这个Class类

2.反射的使用

2.1 获取class对象的三种方式

1)Object.getClass()

2) 任何数据类型(包括基本数据类型)都有一个静态的class属性

3) Class类的ForName(String name) 方法,这个常用,因为不用导包,只需要传入包名+类名

package fanshe;
/**
 * 获取Class对象的三种方式
 * 1 Object ——> getClass();
 * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
 * 3 通过Class类的静态方法:forName(String  className)(常用)
 *
 */
public class Fanshe {
    public static void main(String[] args) {
        //第一种方式获取Class对象  
        Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
        Class stuClass = stu1.getClass();//获取Class对象
        System.out.println(stuClass.getName());

        //第二种方式获取Class对象
        Class stuClass2 = Student.class;
        System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个

        //第三种方式获取Class对象
        try {
            Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
            System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

 在运行期间,只有一个class对象产生。

2.2 通过反射获取构造,成员方法,属性

public class Student {
    private int age;//年龄
    private String name;//姓名
    private String address;//地址
     private static String sTest;
    public Student() {
         throw new IllegalAccessError("Access to default Constructor Error!");
    }
    private Student(int age, String name, String address) {
        this.age = age;
        this.name = name;
        this.address = address;
         sTest = "测试反射";
    }
    private int getAge() {
        return age;
    }
    private void setAge(int age) {
        this.age = age;
    }
    private String getName() {
        return name;
    }
    private void setName(String name) {
        this.name = name;
    }
    private String getAddress() {
        return address;
    }
    private void setAddress(String address) {
        this.address = address;
    }
    private static String getTest() {
        return sTest;
    }
}

刻意用了private来修饰成员变量和方法 下面代码用构造器,方法和属性和静态方法分别来获取一下:

public class StudentClient {
    public static void main(String[] args) throws Exception{
        Class<?> clazz=Class.forName("ClassLoader.Student");
        Constructor constructors=clazz.getDeclaredConstructor(int.class,String.class,String.class);
        constructors.setAccessible(true);
        //利用构造器生成对象
        Object mStudent=constructors.newInstance(27,"小文","北京市海定区XX号");
        System.out.println(mStudent.toString());
        //获取隐藏的int属性
        Field mAgeField=clazz.getDeclaredField("age");
        mAgeField.setAccessible(true);
        int age= (int) mAgeField.get(mStudent);
        System.out.println("年龄为:"+age);
        //调用隐藏的方法
        Method getAddressMethod=clazz.getDeclaredMethod("getAge");
        getAddressMethod.setAccessible(true);
        int newage= (int) getAddressMethod.invoke(mStudent);
        System.out.println("年龄为:"+newage);
        //调用静态方法
        Method getTestMethod=clazz.getDeclaredMethod("getTest");
        getTestMethod.setAccessible(true);
        String result= (String) getTestMethod.invoke(null);
        System.out.println("调用静态方法:"+result);
    }
}

getDeclared***获取的是仅限于本类的所有的不受访问限制的;

而get***获取的是包括父类的但仅限于public修饰符的。

Field和method时一样的道理。

最后一个需要注意的是调用静态方法和调用实例方法有点区别,调用实例方法一定需要一个类的实例,而调用静态方法不需要实例的引用,其实这是JVM的在执行方法上的有所区别。

3.反射的原理

我们知道,每个java文件最终都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。

 

当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象,而我们一般情况下用new来创建对象,实际上本质都是一样的,有了class对象的引用,就相当于有了Method,Field,Constructor的一切信息,在Java中,有了对象的引用就有了一切,剩下怎么发挥是开发者自己的想象力所能决定的了。

4.反射的作用及使用场景

反射是一种具有与Java类进行动态交互能力的一种机制,在Java和Android开发中,一般情况下下面几种场景会用到反射机制.

 

● 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法

● 自定义注解,注解就是在运行时利用反射机制来获取的。

●在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射。

5. 利用反射来进行hook的例子:

hook view的点击事件,避免重复点击,或者发送埋点等。

 

import android.app.Activity;

import android.os.Bundle;

import android.support.annotation.Nullable;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.Toast;

 

import chihane.jdaddressselector.demo.R;

 

public class HookListenerActivity extends Activity {

 

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_hook_listener);

 

        Button button = (Button) this.findViewById(R.id.id_btn);

        button.setOnClickListener(new View.OnClickListener() {

            @Override public void onClick(View v) {

                Toast.makeText(getApplicationContext(), "点击事件", Toast.LENGTH_SHORT).show();

            }

        });

 

        getWindow().getDecorView().post(new Runnable() {

            @Override public void run() {

                View decorView = getWindow().getDecorView();

                HookClickListenerUtils.getInstance().hookDecorViewClick(decorView);

            }

        });

    }

 

 

    @Override public void onAttachedToWindow() {

        super.onAttachedToWindow();

        Log.e("onAttachedToWindow", "onAttachedToWindow");

        // 第二种方式

//        View decorView = getWindow().getDecorView();

//        HookClickListenerUtils.getInstance().hookDecorViewClick(decorView);

    }

 

    @Override public void onWindowFocusChanged(boolean hasFocus) {

        super.onWindowFocusChanged(hasFocus);

        Log.e("onWindowFocusChanged", "onWindowFocusChanged中" + hasFocus);

 

    }

 

 

}
 

import android.view.View;

import android.view.ViewGroup;

import android.widget.Toast;

 

import java.lang.reflect.Field;

import java.lang.reflect.Method;

 

 

public class HookClickListenerUtils {

 

    private static HookClickListenerUtils mHookClickListenerUtils;

 

    private HookClickListenerUtils() {

    }

 

    public static HookClickListenerUtils getInstance() {

        synchronized ("getInstance") {

            if (mHookClickListenerUtils == null) {

                mHookClickListenerUtils = new HookClickListenerUtils();

            }

        }

        return mHookClickListenerUtils;

    }

 

    /***

     * 递归调用

     * @param decorView

     */

    public void hookDecorViewClick(View decorView) {

        if (decorView instanceof ViewGroup) {

            int count = ((ViewGroup) decorView).getChildCount();

            for (int i = 0; i < count; i++) {

                if (((ViewGroup) decorView).getChildAt(i) instanceof ViewGroup) {

                    hookDecorViewClick(((ViewGroup) decorView).getChildAt(i));

                } else {

                    hookViewClick(((ViewGroup) decorView).getChildAt(i));

                }

            }

        } else {

            hookViewClick(decorView);

        }

    }

 

    public void hookViewClick(View view) {

        try {

            Class viewClass = Class.forName("android.view.View");

            Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");

            if (!getListenerInfoMethod.isAccessible()) {

                getListenerInfoMethod.setAccessible(true);

            }

            Object listenerInfoObject = getListenerInfoMethod.invoke(view);// 反射view中的getListenerInfo方法

 

            // 第二步:获取到view中的ListenerInfo中的mOnClickListener属性

            Class mListenerInfoClass = Class.forName("android.view.View$ListenerInfo");

            Field mOnClickListenerField = mListenerInfoClass.getDeclaredField("mOnClickListener");

            mOnClickListenerField.setAccessible(true);

            // 这里为何传入的View.OnClickListener为Field.get()的值

            mOnClickListenerField.set(listenerInfoObject, new HookClickListener((View.OnClickListener) mOnClickListenerField.get(listenerInfoObject)));

 

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

 

    public static class HookClickListener implements View.OnClickListener {

 

        private View.OnClickListener onClickListener;

 

        public HookClickListener(View.OnClickListener onClickListener) {

            this.onClickListener = onClickListener;

        }

 

        @Override public void onClick(View v) {

            Toast.makeText(v.getContext(), "hook住点击事件了,禽兽", Toast.LENGTH_SHORT).show();

            if (onClickListener != null) {

                onClickListener.onClick(v);

            }

        }

    }

 

}

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

              android:layout_width="match_parent"

              android:layout_height="match_parent"

              android:orientation="vertical">

 

    <Button

        android:id="@+id/id_btn"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="点击事件"

        />

 

</LinearLayout>

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值