重走JAVA之路(三):你逃不掉的动态代理

1,326 阅读3分钟

前言

说道代理大家应该都很熟悉,在日常生活中也有很多例子,比如当我们无法对真实目标无法直接访问时,需要一个代理替代我们去做这些事情,比如国内如果要访问google网站,一般就需要翻墙了,这就是一种代理模式。

Java中分为静态代理和动态代理模式,静态代理比较简单,在编译期就直接定义好代理类,有代理对象去访问真正对象,本文主要就讲讲动态代理,其实之前一篇Hook文章有大概说了下动态代理,有兴趣的可以看下这篇Hook文章
Activity不用注册?那就来Hook吧,今天详细说明下动态代理中的细节地方。

先来个栗子

抽象对象接口,面对接口编程,抽象出定义方法,定义两个方法

/**
 * @FileName: com.example.hik.lib.proxy
 * @Anthor: taolin
 */
public interface ISubject {
    void setInfo(int age,String name);
    void sayHello();
}

具体对象,实现接口方法:

/**
 * @FileName: com.example.hik.lib.proxy
 * @Anthor: taolin
 */
public class RealSubject implements ISubject{
    private int age;
    private String name;

    @Override
    public void setInfo(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public void sayHello() {
        System.out.println("i'm "+name+" age "+age);
    }
}

也很明了,实现方法,实现自己逻辑,一个作为赋值操作,一个把赋值给打印出来。

接着来,定义一个类,实现InvocationHandler

/**
 * @FileName: com.example.hik.lib.proxy
 * @Anthor: taolin
 */
public class HookHandler implements InvocationHandler {
	//真实对象,这里代表是realSubject
    private Object mObject;

    public HookHandler(Object mObject) {
        this.mObject = mObject;
    }

    @Override
    public Object invoke(Object mO, Method mMethod, Object[] mObjects) throws Throwable {
        if (mMethod.getName().startsWith("setInfo")){
            for (int i = 0; i < mObjects.length; i++) {
                if (mObjects[i] instanceof Integer){
                    int age = (int) mObjects[i];
                    System.out.println("get age :"+age);
                    age = 20;
                    mObjects[i] = age;
                }else if (mObjects[i] instanceof String){
                    String name = (String) mObjects[i];
                    System.out.println("get name :"+name);
                    name = "lisi";
                    mObjects[i] = name;
                }
            }
        }
        return mMethod.invoke(mObject,mObjects);
    }
}

主要看invoke这个方法,三个参数

  • 第一个Object是表示生成的代理对象,内部通过拼接字节码的方式来创建代理类
  • 第二个Method,很明显就是真实对象自身被调用的方法
  • 同理第三个就是方法中的参数

具体实现逻辑,当代理对象拦截到setInfo()方法时,我们拿到其中的参数,进行修改,再赋值回去,最终通过mMethod.invoke(mObject,mObjects)调用原有方法

主代码

public class MainClass {
    public static void main(String[] args) {
    	//生成真实对象
        ISubject subject = new RealSubject();
        //生成一个方法委托类对象
        HookHandler hookHandler = new HookHandler(subject);
        //生成代理对象,传递进去实现HookHandler对象
        ISubject proxy = (ISubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), hookHandler);
        //这里实质就是代理对象调用的方法
        proxy.setInfo(10,"zhangsan");
        proxy.sayHello();
    }
}

我们主要看看其中的一个方法Proxy.newProxyInstance(),三个参数

  • 第一个,指定一个动态加载代理类的类加载器,这里传入classLoader即可
  • 指明被代理类实现的接口,之后我们通过拼接字节码生成的类才能知道调用哪些方法,因为代理对象和真实对象实现同一个接口,所以这里传入subject.getClass().getInterfaces()即可
  • 这是一个方法委托类,我们通过代理调用被代理类的方法时,就可以将方法名和方法参数都委托给这个委托类。这样我们就可以在invoke方法中,拦截到真实调用的方法以及参数

我们Run一下,看看输出

get age :10
get name :zhangsan
i'm lisi age 20

Process finished with exit code 0

可以看到,我们在invoke方法中,拦截到了setInfo方法中参数,改变之后,再赋值回去,所以,我们在调用proxy.sayHello()的时候,输出的是改变之后的值

总结

  • 定义一个委托类,实现InvocationHandler,实现invoke方法,在其中实现自己想要的逻辑
  • 调用Proxy.newProxyInstance()方法,将委托类对象以及代理对象实现接口和类加载器传递进去,生成一个代理proxy对象
  • 调用代理对象方法,实质是代理对象去调用真实对象的方法,我们可以操作的就是在委托类中拦截方法,实现想要的逻辑

请帮顶 / 评论点赞!因为你的鼓励是我写作的最大动力!