代理模式

Posted 海小鑫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代理模式相关的知识,希望对你有一定的参考价值。

 

  代理(Proxy),顾名思义,就是不用自己去做,而是让别人代替你去做。它在程序开发中起到了非常重要的作用,比如传说中的 AOP(面向切面编程),就是针对代理的一种应用。此外,在设计模式中,针对它也有一个代理模式。

  代理模式分为“静态代理” 和“动态代理” 两种。我们先来看静态代理。

  先来一个HelloWorld 吧。

package com.hd.proxy;

public interface IHello {
    void sayHello(String name);
}

  再来一个实现类:

package com.hd.proxy;

public class HelloImpl implements IHello {

    @Override
    public void sayHello(String name) {
        System.out.println("hello, "+name);
        
    }
}

  

  现在我想在 sayHello 方法中加一些逻辑怎么弄,但不可以改变原来的类代码,很容易想到的就是再写一个代理类,然后在代理类中调用原来的类。想好就赶快写:

package com.hd.proxy;

public class HelloProxy implements IHello {

    private IHello hello;
    public HelloProxy(IHello hello){
        this.hello = hello;
    }
    
    @Override
    public void sayHello(String name) {
        before();
        hello.sayHello(name);
        after();
        
    }
    
    private void before(){
        System.out.println("before...");
    }
    
    private void after(){
        System.out.println("after...");
    }
}

  我创建了一个代理类 HelloProxy ,让它也实现了IHello接口,并定义了一个IHello的成员变量,这样我就可以在构造函数中作为参数给它赋值了。然后在代理类中的sayHello类里调用被代理类的方法。更重要的是,我还可以在调用的前后分别加上 before() 与 after() 方法,在这两个方法里去实现那些前后逻辑。

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

package com.hd.proxy;

public class TestProxy {
    public static void main(String[] args) {
        
        IHello hello = new HelloImpl();
        IHello helloProxy = new HelloProxy(hello);
        helloProxy.sayHello("jack");
    }
}

  运行后结果为:

before...
hello, jack
after...

  一个代理模式就这样被我写好了,有没有感觉 so easy。

  不过细心的同学,会发现这里有点小问题。就是如果只是代理一个类的话没有问题,但是你想想,如果我现在想要给 HelloImpl2,HelloImpl3 。。。等等这些类都要设置代理类怎么办,按照现在的做法,就是我们需要再定义 HelloProxy2,HelloProxy3.。。。等等同样多的代理类出来,这样会造成工程里的类数量激增,就是我们熟知的类爆炸。对于程序的维护很不好。

  因此下面我就介绍了动态代理,看看它是怎么实现的。

  动态代理分为两种,JDK动态代理和CGLIB动态代理。

  a) JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

  下面我用 JDK 给我们提供的动态代理方案,写了一个 DynamicProxy:

package com.hd.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

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;
    }
    
    private void before(){
        System.out.println("before...");
    }
    
    private void after(){
        System.out.println("after...");
    }
}

  在 DynamicProxy 类中,定义了一个 Object 类型的 target 变量,它就是被代理的目标对象,通过构造函数来初始化。然后在invoke方法里通过反射调用被代理类(target)的方法(method.invoke(target, args))。

  下面写一个main方法,看看它是怎么实现动态代理的:

    public static void main(String[] args) {
        
        IHello hello = new HelloImpl();
        DynamicProxy handler = new DynamicProxy(hello);
        IHello h2 = (IHello)Proxy.newProxyInstance(
                hello.getClass().getClassLoader(), 
                hello.getClass().getInterfaces(), handler);
        h2.sayHello("Lee");

    }

  运行一下,结果和以前一样。 我写的这个 DynamicProxy 就是一个通用的代理逻辑类,不管什么类,只要它实现了某个接口,都可以 DynamicProxy 类来实现它定义的代理逻辑。是不是很方便。

 

  b)如果目标类没有实现接口,我们可以选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行期间动态生成字节码,运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

  说起来好高深,实际用起来一点都不难。下面再搞一个 CGLibProxy。

package com.alimama.proxy;

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        
        before();
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }
    
    private void before(){
        System.out.println("before...");
    }
    
    private void after(){
        System.out.println("after...");
    }
}

  我们只需要实现 CGLib 给我们提供的 MethodInterceptor 实现类,并填充 intercept() 方法就可以了。

  下面写个main方法,看看怎么使用这个代理类的:

package com.hd.proxy;
import net.sf.cglib.proxy.Enhancer;

public class TestProxy {

    public static void main(String[] args) {

        CglibProxy cglibProxy = new CglibProxy();
        HelloImpl helloProxy = (HelloImpl)Enhancer.create(HelloImpl.class, cglibProxy);
        helloProxy.sayHello("bruce");
    }

}

  我们只需要调用 Enhancer 的create方法,传入被代理类的class对象,以及我们创建的 CglibProxy 对象即可。

 

  总结一下,这篇我们谈到了 静态代理,指出了静态代理模式的弊端,进而介绍了JDK动态代理和Cglib 动态代理两种实现方式。它们俩兄弟在Spring AOP 中都运用到了。大家有时间可以去研究一下。

  感谢阅读,未完待续。。。

以上是关于代理模式的主要内容,如果未能解决你的问题,请参考以下文章

scrapy按顺序启动多个爬虫代码片段(python3)

用于从 cloudkit 检索单列的代码模式/片段

java代码实现设计模式之代理模式

代理模式(静态代理动态代理)代码实战(详细)

Java设计模式-代理模式之动态代理(附源代码分析)

代理模式(静态代理)