代理那些事

1.无代理程序员

凡是都要由浅入深,学习也不例外。先来一个 Hello World 吧: 架构

public interface Hello {

    void say(String name);
}

这是一个 Hello 接口,不用解释了,你们都懂的。赶忙上实现类吧:框架

public class HelloImpl implements Hello {

    @Override
    public void say(String name) {
        System.out.println("Hello! " + name);
    }
}

 可是若是要在 println() 方法前面和后面分别须要处理一些逻辑,怎么作呢?把这些逻辑写死在 say() 方法里面吗 ?这时就须要代理了ide

2.静态代理模式函数

我要用代理!写一个 HelloProxy 类,让它去调用 HelloImpl 的 say() 方法,在调用的先后分别进行逻辑处理不就好了吗?赶忙搞一个吧:工具

public class HelloProxy implements Hello {

    private HelloImpl helloImpl;

    public HelloProxy() {
        helloImpl = new HelloImpl();
    }

    @Override
    public void say(String name) {
        before();
        helloImpl.say(name);
        after();
    }

    private void before() {
        System.out.println("Before");
    }

    private void after() {
        System.out.println("After");
    }
}

我将 HelloProxy 类实现了 Hello 接口(和 HelloImpl 实现相同的接口),而且在构造方法中 new 出一个 HelloImpl 类的实例。这样一来,我就能够在 HelloProxy 的 say() 方法里面去调用 HelloImpl 的 say() 方法了。更重要的是,我还能够在调用的先后分别加上 before() 与 after() 方法,在这两个方法里去实现那些先后逻辑。学习

用一个 main 方法来测试一下吧:测试

public static void main(String[] args) {
    Hello helloProxy = new HelloProxy();
    helloProxy.say("Jack");
}

运行后,打印出:this

Before
Hello! Jack
After.net

3.动态代理模式

因而我就是用 JDK 给咱们提供的动态代理方案,写了一个 DynamicProxy:

public class DynamicProxy implements InvocationHandler {

    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    ...
}

在 DynamicProxy 类中,我定义了一个 Object 类型的 target 变量,它就是被代理的目标对象,经过构造函数来初始化(如今流行叫“注入”了,我以为叫“射入”也不错哦!构造函数初始化叫“正着射”,因此 reflect 方式就叫“反着射”,简称“反射”)。

言归正传,DynamicProxy 实现了 InvocationHandler 接口,那么必须实现该接口的 invoke 方法,参数不作解释,望文生义吧,是 JRE 给咱们“射”进来的。在该方法中,直接经过反射去 invoke method,在调用先后分别处理 before 与 after,最后将 result 返回。

写一个 main() 方法看看实际怎么用吧:

public static void main(String[] args) {
    Hello hello = new HelloImpl();

    DynamicProxy dynamicProxy = new DynamicProxy(hello);

    Hello helloProxy = (Hello) Proxy.newProxyInstance(
        hello.getClass().getClassLoader(),
        hello.getClass().getInterfaces(),
        dynamicProxy
    );

    helloProxy.say("Jack");
}

没错,意思就是,用我写的这个通用的 DynamicProxy 类去包装 HelloImpl 实例,而后再调用 JDK 给咱们提供的 Proxy 类的工厂方法 newProxyInstance() 去动态地建立一个 Hello 接口的代理类,最后调用这个代理类的 say() 方法。

运行一下,结果和之前同样,动态代理成功了。其实,动态代理就是帮咱们自动生成 XxxProxy 类的法宝啊!

要注意的是,Proxy.newProxyInstance() 方法的参数实在是让我“蛋碎一地”!

参数1:ClassLoader
参数2:该实现类的全部接口
参数3:动态代理对象

调用完了还要来一个强制类型转换一下。

wocao!这一坨 shi 必定要想办法封装一下,避免再次发生处处都是 Proxy.newProxyInstance(),这样架构师又要骂我了。因而我将这个 DynamicProxy 重构了:

public class DynamicProxy implements InvocationHandler {

    ...

    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }

    ...
}

我在 DynamicProxy 里添加了一个 getProxy() 方法,无需传入任何参数,将刚才所说的那一坨 shi,放在这个方法中,而且该方法返回一个泛型类型,就不会强制类型转换了。方法头上加那个 @SuppressWarnings("unchecked") 注解表示忽略编译时的警告(由于 Proxy.newProxyInstance() 方法返回的是一个 Object,这里我强制转换为 T 了,这是向下转型,IDE 中就会有警告,编译时也会出现提示,很烦)。

好了,这下子使用 DynamicProxy 就简单了吧:

public static void main(String[] args) {
    DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
    Hello helloProxy = dynamicProxy.getProxy();

    helloProxy.say("Jack");
}

确实简单用 2 行代理就去掉了前面的 7 行代码(省了 5 行),架构师看到了这样的代码确定会表扬我!

通过一番代码重构后,我提交了全部的代码,架构师看到了,没有吱声…… 可我总算学会了动态代理。

用了这个 DynamicProxy 之后,我以为它仍是很是爽的,爽的地方是,接口变了,这个动态代理类不用动。而静态代理就不同了,接口变了,实现类还要动,代理类也要动。但我也发现动 态代理并非“万灵丹”,它也有搞不定的时候,好比说,我要代理一个没有任何接口的类,它就没有勇武之地了!这就是 JDK 给咱们提供的动态代理,让我不知道该说什么了。

因而我又开始调研,可否代理没有接口的类呢?终于让我找到了这颗“银弹”!那就是 CGLib 这个类库。虽然它看起来不太起眼,但 Spring、Hibernate 这样牛逼的开源框架都用到了它。它就是一个在运行期间动态生成字节码的工具,也就是动态生成代理类了。提及来好高深,实际用起来一点都不难。我再搞一个 CGLibProxy 吧:

4. CGLib 动态代理

public class CGLibProxy implements MethodInterceptor {

    public <T> T getProxy(Class<T> cls) {
        return (T) Enhancer.create(cls, this);
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }

    ...
}

须要实现 CGLib 给咱们提供的 MethodInterceptor 实现类,并填充 intercept() 方法。方法中最后一个 MethodProxy 类型的参数 proxy,值得注意!CGLib 给咱们提供的是方法级别的代理,也能够理解为对方法的拦截(这不就是传说中的“方法拦截器”吗?)。这个功能对于咱们这群屌丝程序员而言,如同雪中送炭 啊,此乃神器也!咱们直接调用 proxy 的 invokeSuper() 方法,将被代理的对象 obj 以及方法参数 args 传入其中便可。

与 DynamicProxy 相似,我在 CGlibProxy 中也添加了一个泛型的 getProxy() 方法,便于咱们能够快速地获取自动生成的代理对象。仍是用一个 main() 方法来描述吧:

public static void main(String[] args) {
    CGLibProxy cgLibProxy = new CGLibProxy();
    HelloImpl helloProxy = cgLibProxy.getProxy(HelloImpl.class);

    helloProxy.say("Jack");
}

仍然经过 2 行代码就能够返回代理对象了,与 JDK 动态代理不一样的是,这里不须要任何的接口信息,对谁均可以生成动态代理对象(无论它是“屌丝”仍是“高富帅”)。说它是神器,过度吗?

我一贯都是以追求完美而著称,2 行代码返回代理对象,我以为仍是有些多余,我不想老是去 new 这个 CGLibProxy 对象,最好 new 一次,之后随时拿随时用。因而我想到了“单例模式”:

public class CGLibProxy implements MethodInterceptor {

    private static CGLibProxy instance = new CGLibProxy();
    
    private CGLibProxy() {
    }

    public static CGLibProxy getInstance() {
        return instance;
    }

    ...
}

我加了以上几行代码,就搞定了!须要说明的是:这里有一个 private 的构造方法,就是为了限制外界不能再去 new 它了,换句话说,我在这里把它给“阉”了。

用一个 main() 方法来证实个人简单主义思想:

public static void main(String[] args) {
    HelloImpl helloImpl = CGLibProxy.getInstance().getProxy(HelloImpl.class);

    helloImpl.say("Jack");
}

没错吧?只需 1 行代码就能够获取代理对象了!

总结一下,咱们今天谈到了无代理、静态代理、JDK 动态代理、CGLib 动态代理。

相关文章
相关标签/搜索