代理模式(结构型模式)

1.代理模式

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

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

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

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

1.3 实现
1.3.1 静态代理

静态代理就是让代理类和被代理类实现同一个接口,然后在代理类中聚合被代理类。在代理类对方法增强后再通过聚合的被代理类对象调用目标方法;

//卖票接口
public interface SellTickets {
    void sell();
}

//火车站(被代理类)  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

//代售点(代理类)
public class ProxyPoint implements SellTickets {

    private TrainStation station = new TrainStation();

    public void sell() {
        //此处对代理方法做一些增强
        System.out.println("代理点收取一些服务费用");
        station.sell();
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        ProxyPoint pp = new ProxyPoint();
        pp.sell();
    }
}
1.3.2 jdk动态代理

Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

用jdk代理来实现上述需求

//代理工厂,用来创建代理对象
public class ProxyFactory {

    //被代理对象
    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject() {
        //使用Proxy获取代理对象
        /*
            newProxyInstance()方法参数说明:
                ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
                Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
                InvocationHandler h : 代理对象的调用处理程序
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        InvocationHandler中invoke方法参数说明:
                            proxy : 代理对象
                            method : 对应于在代理对象上调用的接口方法的 Method 实例
                            args : 代理对象调用接口方法时传递的实际参数
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("jdk动态代理实现方法增强");
                        //执行真实对象
                        Object result = method.invoke(station, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        ProxyFactory factory = new ProxyFactory();
        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

以下为jdk动态生成的代理类反编译后的代码(删掉了tostring等无用方法代码)

package com.sun.proxy;

import com.guo.structural_patterns.proxy.static_proxy.SellTicket;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements SellTicket {
    //要代理的方法
    private static Method m3;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    //代理类中的sell方法
    public final void sell() throws  {
        try {
            //此处调用我们再InvocationHandler类中重写的invoke方法
            //super代表proxy类,h表示InvocationHandler即我们在newProxyInstance方法中传入的对象
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            //通过静态代码块拿到sell方法(method)实例
            m3 = Class.forName("com.guo.structural_patterns.proxy.static_proxy.SellTicket").getMethod("sell");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从上面的类中,我们可以看到以下几个信息:

  • 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
  • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

jdk动态代理执行流程如下:

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

JDK的动态代理机制只能代理实现了接口的类。而不能实现接口的类就不能使用JDK的动态代理,CGLIB是针对类来实现代理的,它的原理是对指定目标类生成一个子类,并覆盖其中的方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>${version}</version>
</dependency>

cglib代理实现

public class CglibProxyFactory implements MethodInterceptor {

    private Object target;

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

    public Object getProxyInstance(){
        Enhancer enhancer = new Enhancer();
        //设置父类,即设置被代理对象
        enhancer.setSuperclass(target.getClass());
        //设置回调方法对象,即增强方法
        enhancer.setCallback(this);
        //设置是否缓存,当多次重复调用的时候,利用缓存就不用重新创建代理类 提高效率,默认开启缓存
        enhancer.setUseCache(true);//默认为true;

        Object o = enhancer.create();

        return o;
    }


    /**
     * intercept 参数介绍:
     * o 代理对象实例
     * method 当前要执行的方法
     * objects  执行方法的参数
     * methodProxy  方法的代理对象,每一个普通的方法在代理类中都有两个对应的方法,如sell()和CGLIB$sell$0();
     *              methodProxy对象代理了这两个方法,methodProxy.invoke()就是执行sell()方法,而methodProxy.invokeSuper()就行执行
     *              CGLIB$sell$0()方法。我们通常使用methodProxy.invokeSuper(o,objects)来执行被代理对象的方法,注意实例o是代理对象的实例,
     *              而不是被代理对象,是代理对象的直接调用。不需要反射,所以效率较反射高;
     **/
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        //增强逻辑
        System.out.println("cgLib动态代理方法增强");

        //利用反射执行,需要被代理对象target实例,效率低,不推荐使用
        //method.invoke(target,objects);

        //用此方法直接执行目标方法,不需要反射,效率高,也不需要被代理对象(target)的实例。推荐使用
        methodProxy.invokeSuper(o,objects);
        return null;
    }
}
1.4 Java动态代理和cglib比较
  • 生成代理类技术不同
    jdk动态代理:jdk自带类ProxyGenerator生成class字节码
    cglib:通过ASM框架生成class字节码文件
  • 生成代理类的方式不同
    jdk动态代理:代理类继承java.lang.reflect.Proxy,实现被代理类的接口
    cglib:代理类继承被代理类,实现net.sf.cglib.proxy.Factory
  • 生成类数量不同
    jdk动态代理:生成一个proxy类
    cglib:生成一个proxy,两个fastclass类
  • 调用方式不同
    jdk动态代理:代理类->InvocationHandler->反射调用被代理类方法
    cglib:代理类->MethodInterceptor->调用索引类invoke->直接调用被代理类方法
  • 性能比较
    在 jdk6之前比使用 Java反射效率要高,在 jdk6、jdk7、jdk8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率 高于 CGLIB 代理效率。只有当进行大量调用的时候,jdk6 和 jdk7 比 CGLIB 代理效率低一点,但是到 jdk8 的时候,jdk 代理效率高于 CGLIB 代理,总之,每一次 jdk 版本升级,JDK 代理效率 都得到提升,而 CGLIB 代理效率 确有点跟不上步伐。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值