Java设计模式之代理模式

目录

代理模式分类

静态代理

动态代理

JDK动态代理

分析JDK代理执行流程

CGLIB动态代理

三种代理对比

JDK动态代理与CGLIB动态代理

动态代理与静态代理

代理模式优缺点

优点:

缺点:

使用场景


代理模式分为三种角色:

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

代理模式分类

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

静态代理

抽象主题类

public interface SellTickets {
    void SellTick();
}

真实主题类 

public class TrainStation implements SellTickets {
    @Override
    public void SellTick() {
        System.out.println("车站售票");
    }
}

代理类 

public class ProxyPoint implements SellTickets {
    private static TrainStation trainStation = new TrainStation();

    @Override
    public void SellTick() {
        System.out.println("做增强处理");
        trainStation.SellTick();
    }
}

测试 

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

 运行结果为:


做增强处理
车站售票

作用就是对真实主题类的功能做了增强。 

动态代理

JDK动态代理

并不存在代理类,而是在程序运行中动态创建代理存储在内存中。对静态代理进行改造,不需要更改TrainStation类与SellTickets类

创建ProxyFactory

public class ProxyFactory {
    private TrainStation trainStation = new TrainStation();

    public SellTickets getSellTicketsProxy(){
        //生成代理类对象
        /*
        ClassLoader loader, 类加载器,根据代理类字节码对象获取
        Class[] interfaces, 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
        InvocationHandler h  代理对象的调用处理程序

        前两个参数就是说,
        一、你要创建哪个实体类的代理对象,就用哪个实体类的类加载器去加载代理类
        二、你的实体类要实现哪个接口,通过实体类获取
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(trainStation.getClass().getClassLoader()
                , trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                    Object proxy, 这是生成的代理对象
                    Method method,
                    Object[] args 客户端调用代理对象中的方法时参数封装为数组
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("做增强处理");
                        //通过反射来执行对应的方法
                        Object invoke = method.invoke(trainStation, args);
                        return invoke;
                    }
                });
        return sellTickets;
    }

}

 测试

public class Client {
    public static void main(String[] args) {
        //创建工厂对象
        ProxyFactory proxyFactory = new ProxyFactory();
        SellTickets sellTicketsProxy = proxyFactory.getSellTicketsProxy();
        sellTicketsProxy.SellTick();
        System.out.println(sellTicketsProxy.getClass().getName());
    }
}

以上代码运行结果为:

做增强处理
车站售票
com.sun.proxy.$Proxy0

分析JDK代理执行流程

根据测试代码中的输出结果可以看出来,$Proxy0是JVM在运行时帮我们生成的代理类。通过工具可以拿到$Proxy0在内存中的结构,将Object自动生成的方法删去后,可以得到接下来的代码 

public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m3;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
    }

    public final void sell() {
        //这个h是$Proxy0的父类Proxy中的属性
        //这个this指的是代理对象
        this.h.invoke(this, m3, null);
    }
}

Java设计模式之代理模式_第1张图片

流程分析:

根据Proxy.newProxyInstance()方法中的参数创建出对应实体类的代理对象。当创建出代理对象后,加载静态代码块将要实现的方法封装为Method对象。当调用对应要实现的方法时。调用InvocationHandler接口中的子实现方法invoke()方法(我们实现的invoke()方法)

CGLIB动态代理

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。而CGLIB代理可以为没有实现接口的类创建代理对象

创建代理类

public class ProxyFactory implements MethodInterceptor {
    private TrainStation trainStation  = new TrainStation() ;

    public TrainStation getProxyObject(){
        //与JDK中的newProxyInstance相似
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(TrainStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation proxy = (TrainStation) enhancer.create();

        return proxy;
    }


    //当调用代理对象方法时,调用intercept()方法。
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法增强");
        Object obj = method.invoke(trainStation, objects);
        return obj;
    }
}

三种代理对比

JDK动态代理与CGLIB动态代理

CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。而被final修饰的类不能被继承而被修饰的方法不能被重写。

在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

动态代理与静态代理

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

代理模式优缺点

优点:

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

缺点:

增加了系统的复杂度

使用场景

        远程(Remote)代理本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。即RPC思想
        防火墙(Firewall)代理当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。例如VPN
        保护(Protect or Access)代理控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。在执行被代理对象的功能时进行权限控制

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