【面试高频】Java设计模式-代理模式

⭐️写在前面


  • 这里是温文艾尔的学习之路
  • 如果对你有帮助,给博主一个免费的点赞以示鼓励把QAQ
  • 博客主页 温文艾尔的学习小屋
  • ⭐️更多文章‍请关注温文艾尔主页
  • 文章发布日期:2022.05.025
  • java学习之路!
  • 欢迎各位点赞评论收藏⭐️
  • 冲冲冲
  • ⭐️上一篇内容:【面试高频】Java设计模式-建造者模式

文章目录

  • 1.1概述
  • 1.2结构
  • 1.3静态代理
  • SellTicket卖票接口
  • TrainStation实现类
  • 静态代理
  • 测试
  • 1.4JDK动态代理
  • 1.5CGLIB动态代理
  • 1.6三种代理的对比
  • 1.7代理模式的优缺点


1.1概述

由于某些原因需要给某对象提供一个代理以控制该对象的访问。此时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介

比如我们买电脑是通过地方代理商来买的,地方代理商就是一个代理
【面试高频】Java设计模式-代理模式_第1张图片
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

1.2结构

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

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

1.3静态代理

我们通过案例来感受一下静态代理

【火车站卖票】

如果要买火车票,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了,这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:
【面试高频】Java设计模式-代理模式_第2张图片

SellTicket卖票接口

public interface SellTickets {
    void sell();
}

TrainStation实现类

public class TrainStation implements  SellTickets {
    @Override
    public void sell() {
        System.out.println("卖票");
    }
}

静态代理

public class ProxyPoint implements SellTickets {
    SellTickets sellTickets = new TrainStation();

    public void sell(){
        sellTickets.sell();
    }
}

测试

public class Client {
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
    }
}

从上面的代码中可以看出测试类直接访问的是ProxyPoint类对象,ProxyPoint类对象作为访问对象和目标对象的中介,同时也对sell()方法进行了增强。

静态代理也称为编译时代理,编译时增强,在编译期间就创建出了代理类对象,优点是简单易懂,缺点是代码不易扩展,我们需要对每个目标类都创建一个代理类

1.4JDK动态代理

jdk动态代理的核心是实现InvocationHandler接口,合理利用Proxy类

接下来我们使用jdk动态代理实现上面案例,我们需要获取代理对象,在获取过程中进行增强处理,使用Proxy.newProxyInstance获取代理对象,实现InvocationHandler接口,重写invoke方法,在方法中实现增强处理

public class ProxyFactory {

    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject(){
        //返回代理对象
        /**
         * ClassLoader loader,类加载器,用于加载代理类,通过目标对象获取类加载器
         * Class[] interfaces,代理类实现的接口的字节码对象
         * InvocationHandler h 代理对象的调用处理程序
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {

                    /*
                    Object proxy:代理对象,和proxyObject是同一个对象,在invoke方法中不用
                     Method method:是指proxyObject.sell();中的sell对象
                     Object[] args:调用方法的实际参数

                     返回值:方法的返回值,如果是void,返回null
                     */

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点收取一定的服务费用(jdk动态代理)");
                        Object invoke = method.invoke(station, args);
                        return invoke;
                    }
                }
        );
        return proxyObject;
    }
}

使用动态代理,我们思考以下问题:

  • ProxyFactory是代理类吗?

ProxyFactory不是代理模式所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类

原理:

  1. 在测试类中通过代理对象调用sell方法
  2. 根据多态特性,执行的是代理类($Proxy0)中的sell()方法
  3. 代理类在sell方法中有调用了InvocationHandler接口的子实现类对象的invoke方法
  4. invoke方法通过反射执行了真实对象所属类的sell()方法

1.5CGLIB动态代理

同样是上面的案例,我们再次使用CGLIB代理实现

如果没有定义SellTickets接口,之定义了TranStation(火车站类)。很显然JDK动态代理无法使用,因为JDK动态代理要求必须定义接口,对接口进行代理

CGLIB实际上创建了被代理类的子类,并通过方法拦截器的方式,拦截方法,进行方法增强处理

CGLIB是一个功能强大,高性能的代码生成包。他为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

<dependency>
  <groupId>cglibgroupId>
  <artifactId>cglibartifactId>
  <version>3.3.0version>
dependency>

代码如下

public class ProxyFactory implements MethodInterceptor {

    //在CGLIB中,代理类属于调用类的子类
    public TrainStation getProxyObject(){
        //创建Enhancer对象,代理增强类对象
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(TrainStation.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(TrainStation);
        // 设置方法拦截器
        enhancer.setCallback(this);
        TrainStation peoxyObject = (TrainStation)enhancer.create();
        return peoxyObject;
    }

    /**
     * @param o           代理对象(增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object object = methodProxy.invokeSuper(o, args);
        return object;
    }
}

所以如果被代理类是由final关键字进行修饰,则不能使用CGLIB动态代理,因为无法继承

1.6三种代理的对比

  • jdk代理和CGLIB代理

使用CGLIB实现动态代理,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是如果被代理类是由final关键字进行修饰,则不能使用CGLIB动态代理,因为CGLIB原理是动态生成被代理类的子类

在JDK1.8,JDK动态代理效率高于CGLIB,如果有目标类有实现接口,就使用JDK动态代理,没有借口就使用CGLIB动态代理

  • 动态代理和静态代理

动态代理和静态代理相比较,最大的好处就是接口中声明的所有方法都被转移到调用处理器一个集中地方法中处理,这样即使接口方法数量很多也可以进行灵活处理,不用像静态代理那样每一个方法进行中转

如果接口增加一个方法,静态代理模式除了所有代理类都需要实现这个方法,因为它实现了接口,所以它不易维护也不易扩展

1.7代理模式的优缺点

优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合性

缺点

  • 增加了系统的复杂度

你可能感兴趣的:(设计模式,代理模式,java,面试,后端,设计模式)