java 代理技术

199 阅读5分钟
原文链接: www.jianshu.com

什么是Java代理

  1. 相关概念
    1.1 代理
    在某些情况下,我们不希望或是不能直接访问对象 A,而是通过访问一个中介对象 B,由 B 去访问 A 达成目的,这种方式我们就称为代理。
    这里对象 A 所属类我们称为委托类,也称为被代理类,对象 B 所属类称为代理类
    代理优点有:
    • 隐藏委托类的实现
    • 解耦,不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作
      根据程序运行前代理类是否已经存在,可以将代理分为静态代理和动态代理。

1.2 静态代理
代理类在程序运行前已经存在的代理方式称为静态代理。就如Class B里面有一个成员变量是 Class A的实例,这时候通过调用B的方法就可以调用A的方法,这个时候A就是被代理类(委托类),B就是代理类。如:

public class A {
    public void eat() {};

    public void drink() {};

    public void play() {};
}
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }

    public void showEat() {
        a.eat();
    };

    public void showDrink() {
        a.drink();
    };

    // not export play()
}

上面A是委托类,B是代理类,B中的函数都是直接调用A相应函数,并且隐藏了A的play函数。
静态代理中代理类和委托类也常常继承同一父类或实现同一接口。
1.3 动态代理
代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。
在Java中,Java提供了java.lang.reflect.Proxy实现了动态代理功能,可以在运行时刻动态生成代理类。这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改代理类中需要更改操作的每个函数。

Java动态代理实现

  • 实现动态代理包括三步:
    (1). 新建委托类(被代理类)
    (2). 实现InvocationHandler接口,这是负责连接代理类和委托类的中间类必须实现的接口;
    (3). 通过Proxy类新建代理类对象。

  • 新建委托类

public interface People{

    public void eat();

    public void drink();

    public void play();
}
public class GodsMan implements People{

    @Override
    public void eat() {
        System.out.println("Invoke eat");
        sleep(100);
    }

    @Override
    public void drink() {
        System.out.println("Invoke drink");
        sleep(200);
    }

    @Override
    public void play() {
        System.out.println("Invoke play");
        sleep(300);
    }

    private static void sleep(long millSeconds) {
        try {
            Thread.sleep(millSeconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

GodsMan是委托类,实现People接口。动态代理要求委托类必须实现了某个接口,比如这里委托类GodsMan实现了People

  • 通过Proxy.newProxyInstance()新建代理类对象
public class Main {
public static void main(String[] args) {
//创建代理类对象people
TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new GodsMan());
People people = (People)(Proxy.newProxyInstance(People.class.getClassLoader(), new Class[] {People.class},
timingInvocationHandler));
// 调用代理对象的方法
people.eat();
System.out.println();
people.drink();
System.out.println();
people.play();
}
}

这里我们先将委托类对象new GodsMan()作为TimingInvocationHandler构造函数入参创建timingInvocationHandler对象;
然后通过Proxy.newProxyInstance(…)函数新建了一个代理对象people,实际代理类就是在这时候动态生成的。我们调用people的方法就会调用到timingInvocationHandlerinvoke函数(是不是有点类似静态代理),而invoke函数实现中调用委托类对象new GodsMan()相应的 method(是不是有点类似静态代理)。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

解析:
loader表示类加载器
interfaces表示委托类的接口,生成代理类时需要实现这些接口
hInvocationHandler实现类对象,负责连接代理类和委托类的中间类
我们可以这样理解,如上的动态代理实现实际是双层的静态代理,开发者提供了委托类 B,程序动态生成了代理类 A。开发者还需要提供一个实现了InvocationHandler的子类 C,子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。用户直接调用代理类 A 的对象,A 将调用转发给委托类 C,委托类 C 再将调用转发给它的委托类 B。
- 实现InvocationHandler接口
public class TimingInvocationHandler implements InvocationHandler {
private Object target;
public TimingInvocationHandler() {}
public TimingInvocationHandler(Object o) {
this.target = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object obj = method.invoke(target, args);
System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
return obj;
}
}

target属性表示委托类对象。
InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口。其中只有一个

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

函数需要去实现,参数:
  • proxy表示java代码通过 Proxy.newProxyInstance(...)生成的代理类对象 people
  • method表示代理对象被调用的函数。
  • args表示代理对象被调用的函数的参数。
  • 调用代理对象people的每个方法实际最终都是调用了InvocationHandler的invoke方法。这里我们在invoke实现中添加了开始结束计时,然后这个代理类对象就会调用委托类对象target的相应方法,这样便完成了统计执行时间的需求。
    invoke方法中我们也可以通过对method做一些判断,从而对某些函数特殊处理。

参考资料

公共技术点之 Java 动态代理