Java设计模式:Proxy(代理)模式

概念定义

代理模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。

使用代理模式的原因有:

  • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理对象可以在客户类和委托对象之间起到中介的作用(代理类和委托类实现相同的接口)。以现实生活为例,经纪人就是明星的代理,外界可以通过联系经纪人来间接与明星沟通。
  • 开放封闭原则:可以通过给代理类增加额外的功能来扩展委托类的功能,这样只需要修改代理类而不需要再修改委托类,符合开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。使用代理模式,可以在调用委托类业务功能的前后加入一些公共的服务(例如鉴权、计时、缓存、日志、事务处理等),甚至修改委托类的业务功能。

代理可以分为静态代理和动态代理,前者更接近代理模式的本质。

  • 静态代理是由程序员编写代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就已确定。
  • 动态代理是代理类的源码是在程序运行期间由编译器动态的生成(如JVM根据反射等机制生成代理类)。代理类和委托类的关系在程序运行时确定。

应用场景

  • 需要修改或屏蔽一个或若干类的部分功能,复用另外一部分功能,可使用静态代理。
  • 需要拦截一批类中的某些方法,在方法的前后插入一些公共的操作,可使用动态代理。

示例代码

本节根据场景和实现的不同,依次介绍静态代理、JDK动态代理、Cglib动态代理(也称子类代理)以及Spring AOP代理。

静态代理

静态代理:在程序运行前就已经存在代理类的字节码文件。

示例代码如下:

// 共同接口 //
public interface ISubject {
    void doAction();
    void byebye();
}

// 真实对象(委托类) //
public class RealSubject implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action Here!"); }
    @Override
    public void byebye() { System.out.println("Wave goodbye!"); }
}

// 代理对象(代理类) //
public class SubjectProxy implements ISubject {
    private ISubject subject;

    public SubjectProxy() {
        // RealSubject实例可根据环境变量、配置等创建不同类型的实例(多态)
        subject = new RealSubject(); // 此处仅简单地new实例
    }

    @Override
    public void doAction() {
        System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
        subject.doAction();
        System.out.println("doWhatever end <<");   // 扩展进行额外的功能操作(如鉴权、计时、日志等)
    }
    @Override
    public void byebye() {
        System.out.println("Say goodbye"); // 改变委托类行为(例如实现数据库连接池时避免close关闭连接)
    }
}

// 验证代码 //
public class StaticProxyDemo {
    public static void main(String[] args) {
        SubjectProxy subject = new SubjectProxy();
        subject.doAction();
        subject.byebye();
    }
}

执行结果如下:

>> doWhatever start
Real Action Here!
doWhatever end <<
Say goodbye

静态代理的特点如下:

  • 使用静态代理时,通常客户类不需要感知RealSubject。
  • 静态代理的缺点:代理对象需要与目标对象实现一样的接口,因此接口较多时需要定义和维护大量的代理类代码。
  • 与适配器的差异:适配器通常考虑改变接口形态,而代理则不会也不能改变接口形态。
  • 与装饰器的差异:被代理对象由代理对象创建,客户端甚至不需要知道被代理类的存在;被装饰对象则由客户端创建并传给装饰对象,可以层层嵌套,层层装饰。代理模式常用于控制被代理对象的访问,而装饰模式是增加被装饰者的功能。

JDK动态代理

JDK动态代理所用到的代理对象,在程序运行阶段调用到代理类对象时才由JVM真正创建。JVM根据传进来的业务实现类对象及方法名,在内存中动态地创建一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。具体而言,每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接口的实现。当使用者调用代理对象所代理的接口中的方法时,这个调用的信息会被传递给InvocationHandler的invoke方法。

示例代码如下:

// 业务接口 //
public interface ISubject {
    void doAction();
}

// 业务实现类 //
public class RealSubject implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action Here!"); }
}
public class RealSubject2 implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action2 Here!"); }
}

// 动态代理类 //
public class SubjectJdkProxyHandler implements InvocationHandler {
    private Object realSubject;

    public SubjectJdkProxyHandler(Object realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
        Object result = method.invoke(realSubject, args); // 执行目标对象方法
        System.out.println("doWhatever end <<");   // 扩展进行额外的功能操作(如鉴权、计时、日志等)
        return result;
    }
}

// 验证代码 //
public class JdkProxyDemo {
    public static void main(String[] args) {
        ISubject subject = (ISubject) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {ISubject.class}, // 或RealSubject.class.getInterfaces()
                new SubjectJdkProxyHandler(new RealSubject())); // RealSubject必须实现Subject接口,否则无法强转后调用业务方法
        subject.doAction();

        // 使用同一个SubjectProxyHandler类,可代理不同的类型
        ISubject subject2 = (ISubject) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {ISubject.class}, new SubjectJdkProxyHandler(new RealSubject2())); // 可使用工厂模式创建代理对象
        subject2.doAction();
    }
}

执行结果如下:

>> doWhatever start
Real Action Here!
doWhatever end <<
>> doWhatever start
Real Action2 Here!
doWhatever end <<

JDK动态代理的特点如下:

  • 通过实现InvocationHandler接口完成代理逻辑。
  • 通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
  • 代理类必须实现接口

Cglib动态代理

Cglib(Code Generation Library)是个功能强大、高性能、开源的代码生成包,它可以为没有实现接口的类提供代理。具体而言,Cglib继承被代理的类,覆写其业务方法来实现代理。因为采用继承机制,所以不能对final修饰的类进行代理。

示例代码如下:

// 业务实现类 //
public class RealSubject {
    public void doAction() { System.out.println("Real Action Here!"); }
}

// 动态代理类(实现方法拦截器接口) //
// MethodInterceptor接口来自net.sf.cglib.proxy.MethodInterceptor或org.springframework.cglib.proxy.MethodInterceptor
public class SubjectMethodInterceptor implements MethodInterceptor {
    public Object createCglibProxy(Class<?> targetClass) {
        Enhancer enhancer = new Enhancer();  // 创建增强器,用来创建动态代理类
        enhancer.setSuperclass(targetClass); // 为增强器指定要代理的业务类,即为生成的代理类指定父类
        enhancer.setCallback(this);          // 设置回调(对于代理类上所有方法的调用,都会调用CallBack)
        return enhancer.create(); // 创建动态代理类对象并返回

        // 以上语句可简化为:return Enhancer.create(targetClass, this); //
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("doWhatever end <<");   // 扩展进行额外的功能操作(如鉴权、计时、日志等)
        return result;
    }
}

// 验证代码 //
public class CglibProxyDemo {
    // 还可使用CGLib + JDK InvocationHandler接口实现动态代理
    public static Object createCglibProxyWithHandler(Class<?> targetClass) {
        MethodInterceptor interceptor = null;
        try {
            InvocationHandler invocationHandler = new SubjectJdkProxyHandler(targetClass.newInstance());
            interceptor = (Object o, Method method, Object[] objects,
                           MethodProxy methodProxy) -> invocationHandler.invoke(o, method, objects);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return Enhancer.create(targetClass, interceptor);
    }

    public static void main(String[] args) {
        RealSubject subject = (RealSubject) new SubjectMethodInterceptor().createCglibProxy(RealSubject.class);
        subject.doAction();

        RealSubject subject2 = (RealSubject) createCglibProxyWithHandler(RealSubject.class);
        subject2.doAction();
    }
}

执行结果如下:

>> doWhatever start
Real Action Here!
doWhatever end <<
>> doWhatever start
Real Action Here!
doWhatever end <<

Cglib动态代理的特点如下:

  • 需要引入Cglib的jar文件,但Spring的核心包内已包含Cglib功能,所以直接引入spring-core-xxx.jar即可。
  • Cglib动态代理虽然不需要接口信息,但是它拦截并包装被代理类的所有方法。
  • 委托类不能为final,否则报错java.lang.IllegalArgumentException: Cannot subclass final class xxx。
  • 不会拦截委托类中无法重载的final/static方法,而是跳过此类方法只代理其他方法。

Spring AOP动态代理

示例代码如下:

public interface ISubject {
    void doAction();
}

public class RealSubject implements ISubject {
    @Override
    public void doAction() { System.out.println("Real Action Here!"); }
}

public class SubjectSpringAopInvoker implements MethodInterceptor { // 来自org.aopalliance.intercept.MethodInterceptor
    private RealSubject target;

    public SubjectSpringAopInvoker(RealSubject realSubject) {
        this.target = realSubject;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println(">> doWhatever start"); // 扩展进行额外的功能操作(如鉴权、计时、日志等)
        Object result = methodInvocation.getMethod().invoke(this.target, methodInvocation.getArguments());
        System.out.println("doWhatever end <<");   // 扩展进行额外的功能操作(如鉴权、计时、日志等)
        return result;
    }
}

public class SpringAopProxyDemo {
    public static void main(String[] args) {
        ISubject proxy = ProxyFactory.getProxy(ISubject.class, new SubjectSpringAopInvoker(new RealSubject()));
        proxy.doAction();
    }
}

执行结果如下:

>> doWhatever start
Real Action Here!
doWhatever end <<

关于Spring实现动态代理的详细介绍,可参考《Spring学习总结(二)——静态代理、JDK与CGLIB动态代理、AOP+IoC》一文。

业界实践

  • org.apache.ibatis.binding.MapperProxy(mybatis-3.4.6.jar)
  • org.springframework.aop.framework.ProxyFactoryBean
posted @ 2019-10-30 22:31  clover_toeic  阅读(...)  评论(...编辑  收藏