Proxy - 代理设计模式

42 阅读3分钟

什么是代理模式?

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

为什么要用代理模式?

解偶作用:有时可以利用代理模式,将客户端同委托类进行解偶,这样当委托类进行替换或是升级时,客户端(调用端)无需修改代码。

功能增强:比如添加日志,记录方法执行时间等机械重复的操作可以利用代理模式,将功能增强的代码在代理类中实现,这样就可以大大节省代码。 注意:代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

案例

我们以吃面包为例。委托类 Bread 和代理类 BreadProxy 都实现了 IFood 接口,在代理类中实例化委托类并实现真正的业务。代理类中还可以添加其他功能。做功能增强。

静态代理

public class Test {
    public static void main(String[] args) {

        IFood food = new BreadProxy();
        food.eat();
    }
}

interface IFood {
    void eat();
}

class Bread implements IFood {

    @Override
    public void eat() {
        System.out.println("吃面包");
    }
}

class BreadProxy implements IFood {

    Bread bread = new Bread();

    @Override
    public void eat() {
        System.out.println("揉面。。。");
        System.out.println("发酵。。。");
        System.out.println("烘烤。。。");
        bread.eat();
        System.out.println("清扫。。。");
    }
}

JDK 动态代理

试想,如果我们委托类过多,我们岂不是要为每一个委托类都写一个代理类,代码成本太高,这种情况我们可以使用动态代理器来动态创建代理对象。

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

public class Test {
    public static void main(String[] args) {
        // 动态代理测试
        IFood food2 = (IFood) Proxy.newProxyInstance(IFood.class.getClassLoader(), new Class[] {IFood.class}, new DynamicProxyHandler(new Bread()));
        food2.eat();
    }
}

interface IFood {
    void eat();
}

class Bread implements IFood {
    @Override
    public void eat() {
        System.out.println("吃面包");
    }
}

/**
 * 动态代理器可以很好的屏蔽掉委托类,
 */
class DynamicProxyHandler implements InvocationHandler {

    // 委托对象
    private Object object;

    public DynamicProxyHandler(Object object) {
        this.object = object;
    }

    // 在此方法内完成委托方法的增强
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("委托类方法调用前");
        Object result = method.invoke(object, args);
        System.out.println("委托类方法调用后");
        return result;
    }
}

CGLIB 动态代理

JDK 动态代理的模式已经可以帮忙解决大部分情况,但是委托类和代理类有一个限制,必须实现同一个接口。如果没有预先定义接口,则无法实现代理模式。 CGLIB 则可以帮助解决这个问题,CGLIB 是采用了非常底层的字节码技术,其原理是通过字节码为委托类创建一个子类,子类中采用拦截技术拦截所有父类方法的调用,顺势植入切面逻辑。 但因为是通过继承实现,委托类不能有 final 修饰,JDK 动态代理与CGLIB 动态代理均是 Spring AOP 的基础。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;


public class Test {
    public static void main(String[] args) {
        Bread foodProxy = (Bread) new CglibProxy().getInstance(new Bread());
        foodProxy.eat();
    }
}

/**
 * 我们为了区别于上面 JDK 的方式,故意不去构建接口
 */
class Bread {
    public void eat() {
        System.out.println("吃面包");
    }
}

/**
 * 动态代理器可以很好的屏蔽掉委托类
 */
class CglibProxy implements MethodInterceptor {

    // 委托对象
    private Object object;

    // 完成代理类的初始化
    public Object getInstance(Object object) {
        this.object = object;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.object.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    // 在拦截器中完成AOP
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("委托方法调用前。。。");
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("委托方法调用后。。。");
        return result;
    }
}

输出结果:

委托方法调用前。。。
吃面包
委托方法调用后。。。

如果我们把 Bread 这个类加上 final 修饰词,我们会得到如下错误:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot subclass final class com.example.demo1.Bread
	at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:565)
	at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
	at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:332)
	at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)