Android Java反射与Proxy动态代理详解与使用基础篇(一)

2023-04-12


一、介绍


什么是反射?

反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。


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


为什么要用反射?

Java Reflection API简介

Java Reflection功能非常强大,并且非常有用,比如:


  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
  • 获取任意对象的属性,并且能改变对象的属性
  • 调用任意对象的方法
  • 判断任意一个对象所属的类
  • 实例化任意一个类的对象
  • 通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。

在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中


Class类:代表一个类,位于java.lang包下。


Field类:代表类的成员变量(成员变量也称为类的属性)。


Method类:代表类的方法。


Constructor类:代表类的构造方法。


Array类:提供了动态创建数组,以及访问数组的元素的静态方法。



二、反射的使用


1、创建类对象


正常我们创建类对象都是通过new关键字来创建,但是我们还有其他两种方式


通过 Class 对象的 newInstance() 方法

通过 Constructor 对象的 newInstance() 方法

基础数据:

package com.example.mylibrary.ref;

public class MyPerson {
    private String name = "default";
    public int age = 2;
    protected boolean sex;


    private MyPerson(String name) {
        this.name = name;
    }

    protected MyPerson(int age) {
        this.age = age;
    }

    public MyPerson() {

    }

    public MyPerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private String getPrivateName() {
        return name;
    }

    protected int getProtectAge() {
        return age;
    }
}

1.1通过 Class 对象的 newInstance() 方法



Class cls=Class.forName("com.example.mylibrary.ref.MyPerson"); MyPerson person=(MyPerson) cls.newInstance();




1.2通过 Constructor 对象的 newInstance() 方法



Constructor constructor=cls.getConstructor(); MyPerson myPerson=(MyPerson) constructor.newInstance();




以上都是构造器无默认参数,如果有的话1.1已无法满足,且1.1的方法在java9的时候已过期,接下我们主讲1.2通过Constructor 创作对象


有参构造:



1、public Constructor getConstructor(Class... parameterTypes)




要指定构造器的参数类型,必须一一对其


2、public T newInstance(Object... initargs)


在获取对象时,需要传入默认参数




Constructor constructor=cls.getConstructor(String.class,Integer.TYPE); MyPerson myPerson=(MyPerson) constructor.newInstance("你好",12);




1.3.获得成员变量Field

获取成员变量有四种:



1、Field[] fields = cls.getFields(); 2、Field field = cls.getField("age"); 3、Field[] dcfis = cls.getDeclaredFields(); 4、Field dfied = cls.getDeclaredField("name");




getFields:获取所有public的属性变量


getField:获取指定为public的属性变量对象


getDeclaredFields:获取所有变量,包括public、protect、private


getDeclaredField:获取任性一个属性对象,包括public、protect、private


看如下debug日志:



Field对象API介绍:

Field是获取变量,变量就有修改值和获取变量对象。


1.修改参数

field.set(cls,value)


2.获取变量

Object value=field.get(cls);


value:就是对象本身


3.获取对象类型

field.getType()


注意:

1.通过getDeclared获取的是全部,如果方法前面没有Declared基本获取的就是public


2.如果field设置value异常不生效,需要设置field.setAccessible(true)


1.4构造器的获取

构造器获取区分public和全部,通过如下方式获取




Constructor[] construPublic= cls.getConstructors();//public Constructor[] consrAll=cls.getDeclaredConstructors();//all Log.log(construPublic.length+""); Log.log(consrAll.length+"");





1.5.类的方法Method

Method是class中的方法,方法的获取如下


1.Method[] methods = cls.getMethods();

这种获取到的方法是当前类下可用的所有public方法,包括class自身的一些方法



getMethods=wait
getMethods=equals
getMethods=toString
getMethods=hashCode
getMethods=getClass
getMethods=notify
getMethods=notifyAll


2.Method[] methodDecs= cls.getDeclaredMethods();

这种获取的是当前类下的所有方法,不包括class自身提供的,只有用户定义的所有类型


getDeclaredMethods=getName
getDeclaredMethods=setName
getDeclaredMethods=getAge
getDeclaredMethods=setAge
getDeclaredMethods=getProtectAge
getDeclaredMethods=getPrivateName


1.6. 如何调用Method

先准备一个对象:MyPerson person=new MyPerson();


1.无参数调用



Method method_getAge = cls.getMethod("getAge"); method_getAge.setAccessible(true); Object o = method_getAge.invoke(new MyPerson());




2.有参调用

Method method_setAge = cls.getMethod("setAge", Integer.TYPE); method_setAge.setAccessible(true);


method_setAge.invoke(person,122);


通过Method设置或者获取都一样,方法的调用通过invoke(),必须传入当前对象,后面有多少个参数就一一对应,没有就不传。


这样基本就完成了反射的作用


注意:

Method提供了getDefaultValue(),这个方法是获取注解提供的默认值,不是获取目标类的值。如果不是通过注解设置默认值,返回的是null。


1.7.内部类的获取

我们上面都是介绍单独类,有些人说如果我的类是类部类怎么获取?其实很简单




Class clsParent = Class.forName("com.example.mylibrary.ref.Parent"); Class clsChild = Class.forName(clsParent.getName() + "$Child");




path=父类+$+内部类名称

二、动态代理Proxy


掌握了基本的反射使用,那我们继续了解动态代理。我们经常听到Hook,钩子说法很悬乎,也很科幻,一些公司面试喜欢闻,你会Hook嘛?其实就这些。没有那么悬乎,剩下的就是Hook涉及到的复杂场景,任何复杂的代码都是从基础做起。


动态代理通过Proxy类完成


1、获取一个代理对象:newProxyInstance



public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) { Objects.requireNonNull(h); Class caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass(); Constructor cons = getProxyConstructor(caller, loader, interfaces); return newProxyInstance(caller, cons, h); }




参数介绍

1.1、loader:加载器


1.2、interfaces:接口类接口,代理是通过代理类的接口进行拦截,而不是直接修改方法体


1.3、InvocationHandler:拦截器


拦截器介绍:



public Object invoke(Object proxy, Method method, Object[] objects)




1、proxy:代理对象


2、method:代理对象调用的方法


3、objects:参数


小试牛刀:


public static void main(String[] args) throws Exception {
        final PersonInterface person = new Child("sun");


        Object proxy = Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                String name = method.getName();

                Log.log("Object o, Method method, Object[] objects----------");
                if (method.getName().equals("setName")) {
                    String item=(String) objects[0];
                    objects[0]="modify="+item;
                }

                return method.invoke(person, objects);

            }
        });

        if (proxy instanceof PersonInterface) {
            PersonInterface testPerson = (PersonInterface) proxy;
            testPerson.setName("proxy");
        }



        PersonInterface testPerson = (PersonInterface) proxy;
        testPerson.setName("proxy");

        Log.log(testPerson.getName());
    }
package com.example.mylibrary.ref;

import com.example.mylibrary.Log;

public class Child implements PersonInterface {
    String name = "";

    public Child(String name) {
        this.name = name;
    }

    @Override
    public String getName() {

        return name;
    }

    @Override
    public void setName(String name) {

        this.name = name;
    }


    public void show() {
        Log.log("name=" + name);
    }
}
package com.example.mylibrary.ref;

public interface PersonInterface {
    public String getName();
    public void setName(String name);

}




DEBUG日志






这样,我们就完成了Hook了。记住,动态代理是拦截接口,而不是修改方法体


本文仅代表作者观点,版权归原创者所有,如需转载请在文中注明来源及作者名字。

免责声明:本文系转载编辑文章,仅作分享之用。如分享内容、图片侵犯到您的版权或非授权发布,请及时与我们联系进行审核处理或删除,您可以发送材料至邮箱:service@tojoy.com