1 . 代理模式模式Proxy Pattern

代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

代理(Proxy)模式分为三种角色:

  • 抽象角色(Subject): 通过接口或抽象类声明真实角色和代理对象实现的业务方法。
  • 真实角色(Real Subject): 实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理角色(Proxy) : 提供了与真实角色相同的接口,其内部含有对真实角色的引用,它可以访问、控制或扩展真实角色的功能。

1.1 静态代理

静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展,静态代理需要代理对象和目标对象实现一样的接口。

// 火车站接口,有卖票功能
public interface TrainStation {
    void sellTickets();
}
// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
    @Override
    public void sellTickets() {
        System.out.println("广州火车站卖票啦");
    }
}
// 代售点卖票(代理类)
public class ProxyPoint implements TrainStation {
    // 目标对象(代理火车站售票)
    private GuangzhouTrainStation station = new GuangzhouTrainStation();
    @Override
    public void sellTickets() {
        System.out.println("代售加收5%手续费");
        station.sellTickets();
    }
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        // 代售加收5%手续费
        // 广州火车站卖票啦
        proxyPoint.sellTickets();
    }
}
    // 测试
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        // 代售加收5%手续费
        // 火车站卖票啦
        proxyPoint.sellTickets();
    }

可以从上面代码看到,我们访问的是ProxyPoint对象,也就是说ProxyPoint是作为访问对象和目标对象的中介的,同时也对sellTickets方法进行了增强(代理点收取加收5%手续费)。

静态代理的优点

实现简单,容易理解,只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。

缺点

也比较明显,那就是代理类和目标类必须有共同接口(父类),并且需要为每一个目标类维护一个代理类,当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动,目标对象和代理对象都需要作出调整

1.2 动态代理

代理类在代码运行时创建的代理称之为动态代理。动态代理中代理类并不是预先在Java代码中定义好的,而是运行时由JVM动态生成,并且可以代理多个目标对象。

1.2.1 jdk动态代理

JDK动态代理是Java JDK自带的一个动态代理实现, 位于java.lang.reflect包下。

// 火车站接口,有卖票功能
public interface TrainStation {
    void sellTickets();
}
// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
    @Override
    public void sellTickets() {
        System.out.println("广州火车站卖票啦");
    }
}
// 深圳火车站卖票
public class ShenzhenTrainStation implements TrainStation {
    @Override
    public void sellTickets() {
        System.out.println("深圳火车站卖票啦");
    }
}
// 代售点卖票(代理类)
public class ProxyPoint implements InvocationHandler {
    private TrainStation trainStation;
    public TrainStation getProxyObject(TrainStation trainStation) {
        this.trainStation = trainStation;
        Class<? extends TrainStation> clazz = trainStation.getClass();
        return (TrainStation) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代售火车票收取5%手续费");
        return method.invoke(this.trainStation, args);
    }
}
    // 测试
    public static void main(String[] args) {
        ProxyPoint proxy = new ProxyPoint();
        TrainStation guangzhouTrainStation = proxy.getProxyObject(new GuangzhouTrainStation());
        // 代售火车票收取5%手续费
        // 广州火车站卖票啦
        guangzhouTrainStation.sellTickets();
        TrainStation shenzhenTrainStation = proxy.getProxyObject(new ShenzhenTrainStation());
        // 代售火车票收取5%手续费
        // 深圳火车站卖票啦
        shenzhenTrainStation.sellTickets();
    }

优点:

  • 使用简单、维护成本低。
  • Java原生支持,不需要任何依赖。
  • 解决了静态代理存在的多数问题。

缺点:

  • 由于使用反射,性能会比较差。
  • 只支持接口实现,不支持继承, 不满足所有业务场景。

1.2.2 CGLIB动态代理

CGLIB是一个强大的、高性能的代码生成库。它可以在运行期扩展Java类和接口,其被广泛应用于AOP框架中(Spring、dynaop)中, 用以提供方法拦截。CGLIB比JDK动态代理更强的地方在于它不仅可以接管Java接口, 还可以接管普通类的方法。

<!-- 先引入cglib包 -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>${cglib-version}</version>
        </dependency>
// 代售点卖票(代理类)
public class ProxyPoint implements MethodInterceptor {
    public TrainStation getProxyObject(Class<? extends TrainStation> trainStation) {
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer =new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(trainStation);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象并返回
        return (TrainStation) enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代售火车票收取5%手续费");
        return methodProxy.invokeSuper(o, objects);
    }
}
    // 测试
    public static void main(String[] args) {
        ProxyPoint proxy = new ProxyPoint();
        TrainStation guangzhouTrainStation = proxy.getProxyObject(GuangzhouTrainStation.class);
        // 代售火车票收取5%手续费
        // 广州火车站卖票啦
        guangzhouTrainStation.sellTickets();
        TrainStation shenzhenTrainStation = proxy.getProxyObject(ShenzhenTrainStation.class);
        // 代售火车票收取5%手续费
        // 深圳火车站卖票啦
        shenzhenTrainStation.sellTickets();
    }

1.3 总结

应用场景:

  • 保护目标对象。
  • 增强目标对象。

优点:

  • 代理模式能将代理对象与真实被调用的目标对象分离。
  • 一定程度上降低了系统的耦合程度,易于扩展。
  • 代理可以起到保护目标对象的作用。
  • 增强目标对象的职责。

缺点:

  • 代理模式会造成系统设计中类的数目增加。
  • 在客户端和目标对象之间增加了一个代理对象,请求处理速度变慢。
  • 增加了系统的复杂度。

两种动态代理的对比:

  • JDK动态代理的特点:
  • 需要实现InvocationHandler接口, 并重写invoke方法。
  • 被代理类需要实现接口, 它不支持继承。
  • JDK 动态代理类不需要事先定义好, 而是在运行期间动态生成。
  • JDK 动态代理不需要实现和被代理类一样的接口, 所以可以绑定多个被代理类。
  • 主要实现原理为反射, 它通过反射在运行期间动态生成代理类, 并且通过反射调用被代理类的实际业务方法。
  • cglib的特点:
  • cglib动态代理中使用的是FastClass机制。
  • cglib生成字节码的底层原理是使用ASM字节码框架。
  • cglib动态代理需创建3份字节码,所以在第一次使用时会比较耗性能,但是后续使用较JDK动态代理方式更高效,适合单例bean场景。
  • cglib由于是采用动态创建子类的方法,对于final方法,无法进行代理。