「设计模式」代理模式

115 阅读6分钟

一、概述

代理模式(Proxy Pattern)是对象的结构模式,是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。在代理模式(Proxy Pattern)中,由于客户端无法直接或者不想直接引用或使用一个对象,所以通过“中间件”起到代理目标对象功能的作用,为其他对象提供一种代理以控制对这个对象的访问。

代理模式的三种角色:

  • 抽象对象类(Abstract Object):该类或接口负责定义具体目标类和代理类之间的共同接口方法。
  • 具体对象类(Real Object):该类也称为被代理类或被委托类,定义了一个真实的对象,最终执行代理类的代理方法中的业务逻辑,客户端通过代理类间接调用真实主题类中定义的方法。
  • 代理对象类(Proxy Object):该类也被称为委托类或代理类,该类持有一个对真实主题类的引用,在它的方法中调用与真实主题类对应的接口方法。

二、实现方式

代理模式有两种实现方式:静态代理动态代理

静态代理:程序执行前就已经存在的编译好的代理类。

动态代理:程序运行时动态生成,根据java的反射机制动态生成。

静态代理

静态代理比较简单。它是在代码编译时就确定了具体的被代理类,由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

接口类

public interface IUserDao {
    public void save();
}

目标对象类

public class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("保存数据");
    }
}

静态代理类

public class UserDaoProxy implements IUserDao{

    private IUserDao target;

    public UserDaoProxy(IUserDao target){
        this.target = target;
    }

    @Override
    public void save() {
        System.out.println("开始代理:先执行业务逻辑");
        target.save();
        System.out.println("完成代理:执行保存后逻辑");
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        IUserDao target = new UserDao();
        UserDaoProxy proxy = new UserDaoProxy(target);
        proxy.save();
    }
}

输出结果

开始代理:先执行业务逻辑
保存数据
完成代理:执行保存后逻辑

从上述代码,可以看出,静态代理方式需要代理对象和目标对象实现一样的接口。

优点:可以再不修改目标对象的前提下扩展目标对象的功能。

缺点:

(1)由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。

(2)一旦接口增加方法,目标对象与代理对象都要进行修改。

动态代理(JDK代理)

为解决静态代理必须实现接口的所有方法的问题,java给出了JDK动态代理。

动态代理是什么呢?

  1. 动态代理是指代理类对象在程序运行时由JVM根据反射机制动态生成的。动态代理不需要定义代理类的,java源文件。
  1. 动态代理其实就是jdk运行期间,动态创建class字节码并加载到JVM。
  1. 动态代理的实现方式常用的有两种:使用JDK代理,与通过CGLlB动态代理。

需要注意:动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

JDK中生成代理对象主要涉及的类有:

  • java.lang.reflect.Proxy:主要用于生成动态代理类Class、创建代理类实例。
  • java.lang.reflect.InvocationHandler:接口只有一个方法invoke,定义了代理对象在执行真实对象的方法时所希望执行的动作。

动态代理对象类

public class ProxyFactory {

    private Object target;

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

    public Object getInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK代理开始");

                        Object invoke = method.invoke(target, args);
                        System.out.println("执行:" + method.getName()+"方法");

                        System.out.println("JDK代理结束");
                        return invoke;
                    }
                });
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        IUserDao target = new UserDao();
        System.out.println("目标对象信息:" + target.getClass());
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getInstance();
        System.out.println("代理对象信息:" + proxy.getClass());
        proxy.save();
    }
}

输出结果

目标对象信息:class proxy.m1.UserDao
代理对象信息:class com.sun.proxy.$Proxy0
JDK代理开始
保存数据
执行:save方法
JDK代理结束

Cglib动态代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。它广泛的被许多AOP的框架使用,例如Spring AOP,为他们提供方法的interception(拦截)。

cglib代理和上述静态代理和jdk代理有啥区别呢?

静态代理和JDK代理模式都要求目标对象时实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可以使用目标对象子类来实现代理,便是Cglib 代理。

JDK动态代理必须实现InvocationHandler接口,通过反射代理方法。cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。

如何使用cglib代理呢?

如果你的项目中已经有spring-core的jar包,则无需引入。

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>

目标对象类

public class UserDao {
    public void save(){
        System.out.println("保存数据");
    }
}

cglib代理对象类

public class ProxyFactory implements MethodInterceptor{

    private Object target;

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

    //获取目标对象的代理对象
    public Object getProxyInstance() {
        //1. 实例化工具类
        Enhancer en = new Enhancer();
        //2. 设置父类对象
        en.setSuperclass(this.target.getClass());
        //3. 设置回调函数
        en.setCallback(this);
        //4. 创建子类,也就是代理对象
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib代理开始");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);
        System.out.println("Cglib代理结束");
        return returnValue;
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        UserDao userDaoProxy = (UserDao) new ProxyFactory(new UserDao()).getProxyInstance();
        userDaoProxy.save();
    }
}

输出结果

Cglib代理开始
保存数据
Cglib代理结束

三、常见应用场景

(1) 防火墙代理: 内网通过代理穿透防火墙,实现对公网的访问。

(2) 缓存代理: 比如当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。

(3) 远程代理: 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和 真正的远程对象沟通信息。

(4) 同步代理:主要使用在多线程编程中,完成多线程间同步工作。