欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

代理模式

程序员文章站 2022-04-04 17:56:05
代理(Proxy),顾名思义,就是不用自己去做,而是让别人代替你去做。它在程序开发中起到了非常重要的作用,比如传说中的 AOP(面向切面编程),就是针对代理的一种应用。此外,在设计模式中,针对它也有一个代理模式。 代理模式分为“静态代理” 和“动态代理” 两种。我们先来看静态代理。 先来一个Hell ......

 

  代理(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 中都运用到了。大家有时间可以去研究一下。

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