美文网首页Android开发Android技术知识Android开发
反射与类加载之反射基本概念与Class(一)

反射与类加载之反射基本概念与Class(一)

作者: Alvin老师 | 来源:发表于2019-11-05 10:53 被阅读0次

更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
本篇文章将从以下几个内容来阐述反射与类加载:

  • [三种获取Class对象的方式]
  • [获取构造器实例化对象与属性信息]
  • [Android 配置打包签名信息的两种方法]
  • [Hook动态注入代码]

一、反射基本概念与三种获取Class对象的方式

Class类是一切的反射根源。
Class类表示什么?
很多的人--可以定义一个Person类(有年龄,性别,姓名等)
很多的车--可以定义一个Car类(有发动机,颜色,车轮等)
很多的类--Class类(类名,构造方法,属性,方法)

得到Class类的对象有三种方式:
第一种:Object类中的getClass()方法
第二种:类.class
第三种:通过Class类的forName()方法

为什么要学习反射?
反射可以通过一个Class类的对象反过来获取目标类的类信息(私有的公有的属性,方法等);
javaEE框架源码大多是采用反射的方式实现,学习此可为学习javaEE框架做铺垫
代码如下:

import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

public class ReflectionDemo {
    /**
     * 三种获取Class对象的的方式:
     * 1、对象.getClass()
     * 2、类名.class
     * 3、Class.forName("完整的类路径")
     */
    @Test
    public void getClassTest() {

        //第一种:对象.getClass()
        Dog dog = new Dog("wangwang", 2, "yellow");
        Class<? extends Dog> aClass = dog.getClass();
        
        //第二种:类名.class
        Class<Dog> dogClass = Dog.class;

        //第三种:Class.forName("完整的类路径")
        try {
            Class<?> aClass1 = Class.forName("com.vince.Dog");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

二、获取构造器实例化对象与属性信息

代码如下:

import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

public class ReflectionDemo {
    /**
     * 通过反射来实例化目标类对象
     */
    @Test
    public void test1(){
        Class<Dog> dogClass = Dog.class;
        try {
            //通过Class对象(dogClass)来实例化类对象,调用了该类默认的无参的构造方法(所以创建Dog类的时候无参的构造方法要保留呢);此时可以正常使用dog对象了
            Dog dog = ((Dog) dogClass.newInstance());  //所以这里是没有参数的newInstance()
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }


    @Test
    public void test2(){
        Class<Dog> dogClass = ( Dog.class);
        //获取目标类的所有构造方法
        Constructor<?>[] constructors = dogClass.getConstructors(); //通过Class对象(dogClass),得到该类Dog的所有构造方法。返回的是一个数组
        for (int i = 0; i <constructors.length ; i++) {
            System.out.println(constructors[i].getName());           //循环遍历,获取每个构造方法的名称
            System.out.println(constructors[i].getParameterCount()); //获取每个构造方法的参数数量
            System.out.println(constructors[i].getParameterTypes()); //获取构造方法的参数类型
        }


        //获取指定的构造器
        try {
            //获取指定的构造器;因为参数要传入原类的属性,所以用String.class 也是一种“类名.class”的方式
            Constructor<Dog> constructor = dogClass.getConstructor(String.class, int.class, String.class);
            //调用带参数的构造器来实例化对象,因为有参所以这里newInstance()需要传入具体的参数
            Dog dog = constructor.newInstance("haha", 3, "white");

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }


    //获取属性
    @Test
    public void test3(){
        Class<Dog> dogClass = Dog.class;
        //获取目标类的所有属性的的一个抽象对象,返回的是一个数组;且这种只能获取公有的属性
        Field[] fields = dogClass.getFields();
        //System.out.println(fields.length);

        //获取私有的以及公有的属性;即所有的属性
        Field[] declaredFields = dogClass.getDeclaredFields();
        //System.out.println(declaredFields.length);

        int len = declaredFields.length;
        for (int i = 0; i <len ; i++) {
            int modifiers = declaredFields[i].getModifiers();   //获取每个属性的修饰符,但是这么获取的是修饰符的整数值(JVM自动给转换了)
            String modifilesName = Modifier.toString(modifiers); //因此可用修饰符的一个类Modifier.toString()方法再转换成字符串
            System.out.println(modifilesName+" "+declaredFields[i].getType()+" "+declaredFields[i].getName());

        }

    }



}

三、Android 配置打包签名信息的两种方法

目录结构如下:

有2种方式:

第一种,直接配置:

signingConfigs { 
  debug { 
    storeFile file("app/keystore.properties") 
    storePassword "111111"
    keyAlias "key"
    keyPassword "111111"
  } 
  release { 
    storeFile file("app/keystore.properties") 
    storePassword "111111"
    keyAlias "key"
    keyPassword "111111"
  } 
} 
buildTypes { 
  debug { 
    signingConfig signingConfigs.debug 
  } 
  release { 
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    signingConfig signingConfigs.release 
  } 
}

第二种,通过读取文件

新建keystore.properties文件

storeFile=keyStore.jks 
storePassword=123456
keyAlias=encrypt 
keyPassword=123456
build.gradle配置
signingConfigs { 
  // 从keystore.properties文件中读取信息 
  def keystorePropertiesFile = rootProject.file("app/keystore.properties") 
  def keystoreProperties = new Properties() 
  keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 
  debug { 
    println("======== debug mode: set key ========") 
    storeFile file(keystoreProperties['storeFile']) 
    storePassword keystoreProperties['storePassword'] 
    keyAlias keystoreProperties['keyAlias'] 
    keyPassword keystoreProperties['keyPassword'] 
  } 
  release { 
    println("======== release mode: set key ========") 
    storeFile file(keystoreProperties['storeFile']) 
    storePassword keystoreProperties['storePassword'] 
    keyAlias keystoreProperties['keyAlias'] 
    keyPassword keystoreProperties['keyPassword'] 
  } 
} 
buildTypes { 
  debug { 
    signingConfig signingConfigs.debug 
  } 
  release { 
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    signingConfig signingConfigs.release 
  } 
}

四、Hook动态注入代码

Hook机制是回调机制的一种,普通的回调是静态的,我们必须提前写好回调接口;而Hook机制在Java中则可以利用反射,针对切入点(通常是一个成员变量),采用替换的手段,使代码在运行时改变,听起来有些抽象,下面简单介绍下,然后我看代码。

寻找适合Hook点,它应该是一个成员变量,并且应该在我们需要注入的方法中调用过它的方法,或者使用了它的的值;
创建继承自Hook点的对象的子类,根据需求修改其相应的方法;
使用反射将我们自己创建的对象替换对象实例中的对象,达到偷梁换柱的目的。

public class Hero {
  private Weapon weaponMain;

  public Hero(Weapon weaponMain) {
    this.weaponMain = weaponMain;
  }

  public void attack(){
     weaponMain.attack();
  }
}

public class Weapon {
  int damage = 10;

  public void attack(){
    System.out.println(String.format("对目标造成 %d 点伤害",damage));
  }
}

public class Game{
    public static void main(String[] args){
        Hero hero = new Hero(new Weapon());
        hero.attack();
    }
}
//对于上面这段程序,游戏对我们隐藏了Weapon的伤害值,但现在我们想要在每次攻击的时候知道这个伤害值是多少。
//下面看看使用Hook机制如何来实现。

//首先我们通过观察,发现切入点就是weaponMain,我们要对它下手。
//创建一个Weapon的复制品WeaponHook,我们需要用自己的人WeaponHook打入内部。
//WeaponHook一切看起来都和Weapon那么相似,但是我们给它留了一个后门,使得我们可以进行监控。
public class WeaponHook extends Weapon{
  private OnUseWeaponAttackListener onUseWeaponAttackListener;

  @Override
  public void attack(){
    super.attack();
    if (onUseWeaponAttackListener != null){
      onUseWeaponAttackListener.onUseWeaponAttack(damage);
    }
  }

  public void setOnUseWeaponAttackListener(OnUseWeaponAttackListener onUseWeaponAttackListener) {
    this.onUseWeaponAttackListener = onUseWeaponAttackListener;
  }

//这就是我们的后门
  public static interface OnUseWeaponAttackListener {
    int onUseWeaponAttack(int damage);
  }
}

//下面看看如何来进行“偷天换日”
public class Game{
    public static void main(String[] args){
    Hero hero = new Hero(new Weapon());
    try {
      Field weapon = ReflectUtils.getVariable(hero.getClass(), "weaponMain");
      weapon.setAccessible(true);
      Weapon weaponHook = new WeaponHook();
      ((WeaponHook) weaponHook).setOnUseWeaponAttackListener(damage -> {
        //通过后门进行操作,这其实就是我们注入的代码
          System.out.println("damage = " + damage);
          return damage;
      });
      weapon.set(hero, weaponHook); //tou tian偷天换日
      hero.attack();
    } catch (NoSuchFieldException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }Hero hero = new Hero(new Weapon());
        hero.attack();
    }
}
//看输出
对目标造成 10 点伤害
damage = 10   //我们获得了Weapon的伤害值

总结
由于内容不多,总结我就不回顾前面了,我们来看看一种防止Hook入侵的一种思路。
我们在Hero类中加入一个检查机制。

public class Hero {
  private Weapon weaponMain;
  private final int weaponMainId; 

  public Hero(Weapon weaponMain) {
    this.weaponMain = weaponMain;
    weaponMainId = this.weaponMain.hashCode();//记录原始Weapon对象的Id,hashCode对于每个对象而言都是唯一的。
  }

  public void attack() {
    if (this.weaponMain.hashCode() != weaponMainId) { //关键位置检查是否遭到替换
      throw new IllegalAccessError(String.format("警告!遭到入侵!入侵者身份:%d", this.weaponMain.hashCode()));
    }
    weaponMain.attack();
  }
}

现在再次运行程序,输出如下:

java.lang.IllegalAccessError: 警告!遭到入侵!入侵者身份:1288141870

更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
参考:https://www.jianshu.com/p/8bf24de75a7a
https://blog.csdn.net/qq_31370269/article/details/85780165
https://www.cnblogs.com/danew/p/11511952.html
https://www.jianshu.com/p/1a0c368da1b8

相关文章

  • 反射与类加载之反射基本概念与Class(一)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/4743806...

  • 2020-07-04【反射】

    类加载 类加载器 反射概述 获取Class类的对象 反射获取构造方法并使用 反射获取成员遍历并使用 反射获取成员方...

  • java反射机制与类加载机制

    java反射机制与类加载机制 Class (Java SE 9 & JDK 9 ) - https://docs....

  • Java基础之反射

    Java-Reflect Class类的使用 方法的反射 成员变量的反射 构造函数的反射 Java类加载机制 一、...

  • java基础-反射

    知识点 反射的基本概念 反射中class类的获取 反射中field类属性的获取 反射中method类方法的获取 反...

  • 反射Class

    class类的使用 方法的反射 成员变量的反射 构造函数的反射 Java类加载机制 反射帮我们做一些程序运行时刻的...

  • Class类与反射

    Java的Class类是java反射机制的基础,通过Class类我们可以获得关于一个类的相关信息,下面我们来了解一...

  • Class类与JAVA反射

    Class类与JAVA反射通过反射可以访问的主要信息 包路径 getPackage() Package对象 ...

  • CoreJava笔记 - 范型程序设计(5)

    反射与范型 由于类型擦除,反射无法得到关于范型类型参数的信息。 范型的Class类在Java的反射库中,Class...

  • Java 类型系统

    类型系统与反射 JVM会为每个加载到内存的类创建了一个Class对象,而我们可以通过Class对象来获取类中的Fi...

网友评论

    本文标题:反射与类加载之反射基本概念与Class(一)

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