java代理模式

代理模式

代理模式(Proxy Pattern)的定义:为其他对象提供一种代理以控制对这个对象的访问。
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。


生活中,比较常见的代理场景如:火车票代售点代卖火车票了。
这里写图片描述

在软件设计中,Spring的AOP就是使用代理模式的原理来实现的。


在代理模式中主要有四种角色:

  1. 客户端(client):使用代理对象和真实对象完成一些工作的调用方。
  2. 抽象对象(interface):代理对象和真实对象的公共对外方法(如订火车票)
  3. 代理对象(proxy):用来代理、封装代理对象,代理对象内包含真实对象的引用,从而能够操作真实对象。
  4. 真实对象(target):真正实现业务逻辑的对象;
    这里写图片描述

业务场景

假设有这样一个业务场景:动物园中有许多小动物,小动物每天都要吃饭,现在需要记录动物园中所有动物的吃饭时间。
抽象对象(Animal),真实对象(Cat,SingleDog),代理对象(xxProxy),接口(吃饭:eat());

public interface Animal {
    /**
     * 吃饭方法
     */
    void eat();
}

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("吃猫粮");
    }
}

public class SingleDog implements  Animal{
    @Override
    public void eat() {
        System.out.println("撒狗粮");
    }
}

java实现代理的方式有许多,接下来我们着重学习下

  • 静态代理:继承方式 + 聚合方式
  • 动态代理:jdk动态代理 + cglib动态代理

静态代理

静态代理是指,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。

1. 继承方式

public class CatTimeProxy extends Cat {

    @Override
    public void eat() {
        long startTime = System.currentTimeMillis();
        System.out.println("start eat-----------------");
        super.eat();
        long endTime = System.currentTimeMillis();
        System.out.println("end eat--------------------"+ (endTime - startTime) +"ms");
    }
}

public class SingleDogTimeProxy extends SingleDog {

    @Override
    public void eat() {
        long startTime = System.currentTimeMillis();
        System.out.println("start eat-----------------");
        super.eat();
        long endTime = System.currentTimeMillis();
        System.out.println("end eat--------------------"+ (endTime - startTime) +"ms");
    }
}

测试类

    //继承方式
    public void testCatTimeProxy(){
        //1.记录cat的吃饭时间
        Animal animal = new CatTimeProxy();
        animal.eat();

        //2.记录SingleDog的吃饭时间
        animal = new SingleDogTimeProxy();
        animal.eat();
    }

2. 聚合方式

public class AnimalTimeProxy implements Animal {
    private Animal target;

    public AnimalTimeProxy(Animal animal) {
        this.target = animal;
    }

    @Override
    public void eat() {
        long startTime = System.currentTimeMillis();
        System.out.println("start eat-----------------");
        target.eat();
        long endTime = System.currentTimeMillis();
        System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
    }
}

测试类:

    //聚合的方式(较灵活,因为实现了接口)
    public void tesAnimalTimeProxy(){
        //1.记录cat的吃饭时间
        Animal animal = new AnimalTimeProxy(new Cat());
        animal.eat();

        //2.记录SingleDog的吃饭时间
        animal = new AnimalTimeProxy(new SingleDog());
        animal.eat();
    }

3. 继承与聚合的比较
聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。继承式的实现方式则不够灵活。
举个极端的例子:动物园中有1000中动物, 现在要记录这1000种动物的吃饭时间,如果采用继承,则要为这1000种动物分别创建一个代理类…..


4. 静态代理的劣势

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。(ps:jdk8中,支持接口有default方法,可避免此问题)

动态代理

动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。
动态代理类使用字节码动态生成加载技术,在运行时生成加载类。生成动态代理类的方法很多,如,JDK 自带的动态处理、CGLIB、Javassist 或者 ASM 库。JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱。CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。
这里着重介绍下jdk自带的动态代理,和CGLIB的动态代理


jdk动态代理

1.实现步骤
实现jdk动态代理,通常有两种常用的方法,但是最终的步骤都如下所示:
a. 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

2.代码示例
首先定义InvocationHandler实现类

public class JvmTimeHandler implements InvocationHandler {
    private Object target;

    public JvmTimeHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("start eat-----------------");

        //!!注意proxy为当前调用类即JvmTimeHandler, method.invoke(xxx,zzz)第一个参数不能为proxy,会出现死循环
        Object returnVal = method.invoke(target, args);

        long endTime = System.currentTimeMillis();
        System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
        return returnVal;


    }
}

方法(一)

    public void testTimeHandler() {
        Animal target = new SingleDog();
        Class clazz = target.getClass();

        //通过 classloader,代理类的inteface[] ,以及调用处理器对象 直接生成代理对象
        Animal proxy = (Animal) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new JvmTimeHandler(target));
        proxy.eat();
    }

方法(二)

public void testTimeHandler2() throws Exception {
        //a.建动态代理类
        Class proxyClass = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), Animal.class);

        //b.动态代理类的构造函数
        Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);

        //c.构造调用处理器对象
        Animal target = new SingleDog();
        InvocationHandler handler = new JvmTimeHandler(target);

        //d.通过构造函数创建动态代理类对象
        Animal proxy = (Animal) constructor.newInstance(handler);
        proxy.eat();
    }

3.源码解析
1)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

2)java.lang.reflect.Proxy
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

//构造函数
  protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }


//查找或生成指定的代理类
    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
      //......省略部分代码......

        //如果代理类已经通过实现给定接口的类加载器创建了,则返回缓存中的该类的副本;否则将通过ProxyClassFactory创建代理类 
        return proxyClassCache.get(loader, interfaces);
    }


//newProxyInstance()
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,                     InvocationHandler h)    {
        //......省略部分代码......

        final Class<?>[] intfs = interfaces.clone();

        //查找或生成指定的代理类
        Class<?> cl = getProxyClass0(loader, intfs);

        //获得类的构造函数  
        final Constructor<?> cons = cl.getConstructor(constructorParams);  

        //newInstance
       return cons.newInstance(new Object[]{h});
    }

3)java.lang.reflect.Proxy.ProxyClassFactory(内部类)
通过ProxyClassFactory创建代理类.

// 根据给定的类加载器和接口数组生成代理类的工厂类 
private static final class ProxyClassFactory  
    implements BiFunction
{  
 // 所有代理类名称的前缀  
        private static final String proxyClassNamePrefix = "$Proxy";

       //用于生成唯一代理类名称的下一个序号  
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            //验证interfaces合法性
            //......省略部分代码......

            //获取代理类序号
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            //生成代理类字节码
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

         //加载代理类字节码.并返回实例化对象
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
        }
}

4)生成的代理对象$Proxy0
目前我们只能使用代理对象,但是却获取不到代理对象的字节码,我们可以通过如下两种方式获取:
a.通过ProxyGenerator.generateProxyClass来获取,会在项目根目录下生成com.sun.proxy.$Proxy0.class文件

    public static void craeteProxyClazzFile(String clazzName , Class<?> ... interfaces) {
        byte[] classFile = ProxyGenerator.generateProxyClass(clazzName, interfaces);
        try (FileOutputStream out = new FileOutputStream(System.getProperty("user.dir") + File.separator+clazzName+".class");){
            out.write(classFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

b.在Proxy.newInstance之前,增加如下配置,会在项目根目录下创建com/sun/proxy/$Proxy0.class

    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

接下来我们来看下$Proxy0.class的代码构成:

public final class $Proxy0 extends Proxy implements Animal {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

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


    public final void eat() throws  {
        //调用父类的InvocationHandler.invoke()方法
        super.h.invoke(this, m3, (Object[])null);
    }



    static {
       //......省略部分代码......
            m3 = Class.forName("pattern.proxy.beans.Animal").getMethod("eat", new Class[0]);

    }
}

4.jdk动态代理小结
jdk动态代理必须只针对接口方法的代理,如果业务类没有实现interface则无法使用jdk动态代理。如果业务类实现了接口,在接口新增了方法时,(jdk8中,支持接口default方法,否则会报错)如果代理类么有同步更新则是无法代理的,给之后的维护带来了麻烦。


cglib动态代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,我们可以使用cglib来实现动态代理。
cglib动态代理的原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。—这里可以得知final类是无法被代理的。


1.实现步骤
a.定义自己的回调函数类,通常我们使用实现了callback的子接口MethodInterceptor,InvocationHandler
这里写图片描述

b.enhance设置真实对象.class,设置callback(),生成代理对象;常用的拦截器有:
c.获取代理对象,执行业务方法


2.代码示例
方法(一)

//实现MethodInterceptor接口
public class TimeInterceptorFactory implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method m, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("start eat-----------------");

        //注意调用的方法!! methodProxy.invokeSuper()
        Object returnVal = methodProxy.invokeSuper(obj, objects);

        long endTime = System.currentTimeMillis();
        System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
        return returnVal;
    }
}

测试类:

public static void testTimeInterceptorFactory() {
        TimeInterceptorFactory interceptor = new TimeInterceptorFactory();
        SingleDog proxy = (SingleDog) interceptor.getProxy(SingleDog.class);
        proxy.eat();

    }

方法(二)


    public static void testMethodInterceptor2() throws Exception {
        Enhancer enhancer = new Enhancer();
        Animal target = new SingleDog();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new net.sf.cglib.proxy.InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.currentTimeMillis();
                System.out.println("start eat-----------------");

                //调用的方法
                Object returnVal = method.invoke(target, args);

                long endTime = System.currentTimeMillis();
                System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
                return returnVal;
            }
        });

        SingleDog proxy = (SingleDog) enhancer.create();
        proxy.eat();
    }

3.源码解析
如何查看cglib动态代理产生的类呢?我们可以在生成代理类之前增加一个系统变量,即可在工程根目录/net/sf/cglib/proxy 获得产生的代理类

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "net/sf/cglib/proxy");

cglib动态代理会产生多个class文件:

MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.class

Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.class

SingleDog$$EnhancerByCGLIB$$ab95b44d$$FastClassByCGLIB$$5613c2ba.class

SingleDog$$EnhancerByCGLIB$$ab95b44d.class //关注这个

SingleDog$$FastClassByCGLIB$$47f8b6eb.class

1) SingleDog$$EnhancerByCGLIB$$ab95b44d
命名规则:package.真实对象class+$$EnhancerByCGLIB$$+key

//静态语句块
static {
        //静态语句中调用
        CGLIB$STATICHOOK1();
    }

static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d");
        Class var1; //实际调用类为:SingleDog
     //....省略部分.....
        CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0");
    }

//MethodProxy
final void CGLIB$eat$0() {
    super.eat();
}

//重写后的eat方法
public final void eat() {
    /**
     * callback子接口的类型:常见的还有net.sf.cglib.proxy.InvocationHandler;
     */
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if(this.CGLIB$CALLBACK_0 == null) {
        //设置this.CGLIB$CALLBACK_0 为 enhance.callbacks[0]
        CGLIB$BIND_CALLBACKS(this);

        //ca
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if(var10000 != null) {
        /**
         * 执行callback_0.intercept
         * this == 即代理类SingleDog$$EnhancerByCGLIB$$ab95b44d
         * CGLIB$eat$0$Method即 被代理的业务方法 == SingleDog.eat();
         *  CGLIB$eat$0$Method = ReflectUtils.findMethods(new String[]{"eat", "()V"}, (var1 = Class.forName("pattern.proxy.beans.SingleDog")).getDeclaredMethods())[0];
         * CGLIB$emptyArgs = 参数
         * CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0");
         */
        var10000.intercept(this, CGLIB$eat$0$Method, CGLIB$emptyArgs, CGLIB$eat$0$Proxy);
    } else {
        super.eat();
    }
}

//由enhance.registerCallbacks(Class generatedClass, Callback[] callbacks)  调用此方法设置CGLIB$STATIC_CALLBACKS[]
    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    //设置callback
    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        SingleDog$$EnhancerByCGLIB$$ab95b44d var1 = (SingleDog$$EnhancerByCGLIB$$ab95b44d)var0;

        //第一次进来时 var1.CGLIB$BOUND = false
        if(!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;

            //CGLIB$THREAD_CALLBACKS = new ThreadLocal();
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();

            //第一次进来时var10000 = null;
            if(var10000 == null) {
                /**
                 * CGLIB$STATIC_CALLBACKS 由 CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) 设置
                 */
                var10000 = CGLIB$STATIC_CALLBACKS;
                if(CGLIB$STATIC_CALLBACKS == null) {
                    return;
                }
            }

            //设置this.CGLIB$CALLBACK_0 = enhance.setCallbacks()数组中的第一个
            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

2) .net.sf.cglib.proxy.MethodProxy
methodProxy是在上述代理类中生成的:

    /**
     * 在SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$STATICHOOK1()中调用
     * c1 : SingleDog
     * c2 : pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d
     * desc: "()V",
     * name1: "eat"
     * name2: "CGLIB$eat$0"
     */
    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();

        //SingleDog.eat()
        proxy.sig1 = new Signature(name1, desc);

        //SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }


    private void init()
    {

        if (fastClassInfo == null)
        {
            synchronized (initLock)
            {
                if (fastClassInfo == null)
                {
                    CreateInfo ci = createInfo;

                    FastClassInfo fci = new FastClassInfo();
                    //真实对象:SingleDog
                    fci.f1 = helper(ci, ci.c1);

                    //代理对象:SingleDog$$EnhancerByCGLIB$$ab95b44d
                    fci.f2 = helper(ci, ci.c2);

                    //业务方法SingleDog.eat()
                    fci.i1 = fci.f1.getIndex(sig1);

                    //SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()
                    fci.i2 = fci.f2.getIndex(sig2);
                    fastClassInfo = fci;
                }
            }
        }
    }

//解释:为什么在设置的callback接口实现类中,必须使用methodproxy.invokeSuper。
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            init();
            FastClassInfo fci = fastClassInfo;

            /**
             * fci.f2 : SingleDog$$EnhancerByCGLIB$$ab95b44d
             * fci.i2 : SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()
             * 观察SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()的逻辑为:super.eat()即SingleDog.eat()
             * 得知等价于调用SingleDog.eat()
             */
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }

4.cglib动态代理小结
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理


参考文献

https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值