Java:代理模式详解

定义:为其他对象提供一种代理以控制对这个对象的访问

即通过代理对象访问目标对象,这样做的好处是,可以在目标对象实现的基础上,增强额外的功能操作,即拓展目标对象的功能。
概述

解决的问题

在不修改源代码的情况下,对目标对象进行增强。
优势就是可以在运行时进行增强(动态代理)

核心要点:

代理是为了扩展类而存在的,可以控制对目标类的服务的访问。
增加中间层,多了一层控制,多了一层拓展。
代理模式有不同的表现形式,静态代理、动态代理(JDK的动态代理、Cglib的动态代理)

类图:

类图

代码实现

需求:通过汽车代理买车的利息

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承父类
优点:在不修改目标对象的前提下,能通过代理对象对目标功能拓展
缺点:由于代理对象需要与目标对象是实现一样的接口,所以会与很多代理类
一旦接口增加方法,目标对象与代理对象都需要维护

买车接口BuyCar

/**
 * 买车的接口
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/8/18 - 17:56
 */
public interface BuyCar {
    
    

    public void buyCar();
}

真实类-目标对象

/**
 * @Author: Promsing
 * @Date: 2021/4/3 - 8:25
 * @Description: 实现类
 * @version: 1.0
 */
public class BuyCarImpl implements BuyCar {
    
    

    @Override
    public void buyCar() {
    
    
        System.out.println("我要买车~~~啦啦啦,真实对象");
    }

}

代理类

/**
 * @Author: Promsing
 * @Date: 2021/4/3 - 8:26
 * @Description: 代理类
 * @version: 1.0
 */
public class BuyCarProxy implements BuyCar  {
    
    

    //静态代理- 的实现方式
    private BuyCar buyCar;

    //注意final修饰的关键字 不可修改
    //构造函数注入,需要被代理的对象
    public  BuyCarProxy(final BuyCar buyCar) {
    
    
        this.buyCar = buyCar;
    }


    public void buyCar() {
    
    
        System.out.println("代理类!买车前的准备~~~");
        buyCar.buyCar();
        System.out.println("代理类!买完车了,出去浪~~~");
    }
}

Main方法测试

/**
 * 静态代理测试类
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/7 - 15:19
 */
public class Main {
    
    

    public static void main(String[] args) {
    
    

        //创建代理对象,并指定目标对象
        BuyCar buyCar=new BuyCarProxy(new BuyCarImpl());
        //通过代理对象,对buyCar进行增强
        buyCar.buyCar();

    }

}

动态代理

JDK动态代理

代理类所在包:java.lang.reflect.Proxy
JDK动态代理是通过JDK自带的Proxy类中的newProxyInstance()方法来动态生成代理对象的。
我们需要实现InvocationHandler接口,在其invoke()方法中编写调用目标对象的代码。

JDK 动态代理类使用步骤:

  1. 定义一个接口及其实现类;
  2. 实现 InvocationHandler接口 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;


动态代理类

/**
 * @Author: Promsing
 * @Date: 2022/4/3 - 9:09
 * @Description: 动态代理类
 * @version: 1.0
 */
public class DynamicProxy implements InvocationHandler {
    
    

    //想相当于切点
    //被代理的真实对象
    private Object object;

    public DynamicProxy( Object object) {
    
    
        this.object = object;
    }

    /**
     *
     * @param proxy  目标对象的引用
     * @param method 当前执行的方法
     * @param args 当前执行方法所需的参数
     * @return 和被代理对象方法有相同的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println(this.toString());
        System.out.println("不贷款,全款!买车前的准备~~~"); //主要增强
        Object result = method.invoke(object, args);
        System.out.println("买完车了,出去浪~~~"); //主要增强
        return result;
    }
}

Main方法测试

/**
 * 基于接口的动态代理
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/8/18 - 20:06
 */
public class Main {
    
    

    
    public static void main(String[] args) {
    
    

        System.out.println("-+-+-+使用基于接口的代理-+-+-+");
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        System.out.println("$Proxy0.class全名: "+Proxy.getProxyClass(BuyCar.class.getClassLoader(), BuyCar.class).toString());
     
        //使用DynamicProxy类
        //声明一个final修饰的对象--目标类
        final BuyCarImpl car = new BuyCarImpl();
        Object o = Proxy.newProxyInstance(
                car.getClass().getClassLoader(),
                car.getClass().getInterfaces(),
                new DynamicProxy(car));//相对于生成class文件,并加载到内存,并且获取实例
       
        BuyCar proxy2 = (BuyCar) o; //强转,这里必须使用接口

        proxy2.buyCar(); //执行代理类的方法
    }
}

运行时代理对象的类型运行时对象

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和真实类类的关系是在程序运行时确定。 动态代理根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。
其实现原理如下:由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类, 这样,就完成了在代码中,动态创建一个类的能力了。

Cglib动态代理

由于JDK动态代理是面向接口的,也就是说如果目标类没有相应的接口,JDK动态代理就无法为其创建代理。这时可以选择用CGLIB动态代理来实现。
Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象拓展。
Cglib包的底层通过字节码处理框架ASM来转换字节码并生成新的类
需要引入Jar包–cglib 本案例使用cglib3.3.0

扫描二维码关注公众号,回复: 14686379 查看本文章

CGLIB 动态代理类使用步骤

  1. 实现 MethodInterceptor接口 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  2. 通过 Enhancer 类的 create()创建代理类
  3. 客户端使用代理对象

动态代理类

/**
 * 实现方法拦截的接口
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/8/19 - 0:00
 */
public class DynamicSubClass implements MethodInterceptor {
    
    
    
    
    //真实对象
    private Object target;

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

 
    //返回一个代理对象
    public Object getProxyInstance(){
    
    
        //创建工具类
        Enhancer enhancer = new Enhancer();

        //设置父类
        enhancer.setSuperclass(target.getClass());

        //回调函数
        enhancer.setCallback(this);

        //创建代理对象
        return enhancer.create();
    }
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        System.out.println("不贷款,全款!买车前的准备~~~"); //增强的逻辑
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("买完车了,出去浪~~~");
        return result;
    }
}

Main方法测试

public class Main2 {
    
    

    public static void main(String[] args) {
    
    

        //使用基于子类的动态代理
        //需要引入Jar包--cglib 本案例使用cglib3.3.0
        System.out.println("-+-+-+使用基于子类的代理-+-+-+");

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\09  个人学习\\com\\sun\\proxy");

        final BuyCarImpl car=new BuyCarImpl(); //目标类

        //生成代理对象
        Object o = new DynamicSubClass(car).getProxyInstance();
        BuyCar buyCar=(BuyCar) o;

        buyCar.buyCar();
    }
}

运行时代理对象的类型
运行时对象

拓展

JDK生成的字节码文件

看源码得知,代理文件继承了Proxy,实现了BuyCar接口,由于Java是单继承,所以JDK的动态代理只能基于接口实现

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.promsing.porxy.v2.BuyCar;
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 BuyCar {
    
    
    private static Method m1;//equals
    private static Method m2;//tostring
    private static Method m3;//buyCar
    private static Method m0;//hascode

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

    public final boolean equals(Object var1) throws  {
    
    
        try {
    
    
            return (Boolean)super.h.invoke(this, m1, new Object[]{
    
    var1});
        } catch (RuntimeException | Error var3) {
    
    
            throw var3;
        } catch (Throwable var4) {
    
    
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
    
    
        try {
    
    
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void buyCar() throws  {
    
    
        try {
    
    //h,就是切面类(代理类)
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
    
    
        try {
    
    
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
    
    
        try {
    
    
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.promsing.porxy.v2.BuyCar").getMethod("buyCar");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
    
    
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
    
    
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Cglib生成的字节码文件

看源码得知,代理文件继承了BuyCarImpl,所以cglib是基于子类的动态代理
在这里插入图片描述

public class BuyCarImpl$$EnhancerByCGLIB$$1caed7b0 extends BuyCarImpl implements Factory {
    
    
 

    static void CGLIB$STATICHOOK1() {
    
    
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.promsing.structure.proxy.v2.BuyCarImpl$$EnhancerByCGLIB$$1caed7b0");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{
    
    "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$buyCar$0$Method = ReflectUtils.findMethods(new String[]{
    
    "buyCar", "()V"}, (var1 = Class.forName("com.promsing.structure.proxy.v2.BuyCarImpl")).getDeclaredMethods())[0];
        CGLIB$buyCar$0$Proxy = MethodProxy.create(var1, var0, "()V", "buyCar", "CGLIB$buyCar$0");
    }

    final void CGLIB$buyCar$0() {
    
    
        super.buyCar();
    }
    
    //buyCar的方法
    public final void buyCar() {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
    
    
            var10000.intercept(this, CGLIB$buyCar$0$Method, CGLIB$emptyArgs, CGLIB$buyCar$0$Proxy);
        } else {
    
    
            super.buyCar();
        }
    }

·
    final boolean CGLIB$equals$2(Object var1) {
    
    
        return super.equals(var1);
    }
    
    //equals
    public final boolean equals(Object var1) {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
    
    
            Object var2 = var10000.intercept(this, CGLIB$equals$2$Method, new Object[]{
    
    var1}, CGLIB$equals$2$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
    
    
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$3() {
    
    
        return super.toString();
    }
    //tostring
    public final String toString() {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy) : super.toString();
    }

    final int CGLIB$hashCode$4() {
    
    
        return super.hashCode();
    }
    //hashCode
    public final int hashCode() {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
    
    
            Object var1 = var10000.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
    
    
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$5() throws CloneNotSupportedException {
    
    
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? var10000.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy) : super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
    
    
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
    
    
        case -1574182249:
            if (var10000.equals("finalize()V")) {
    
    
                return CGLIB$finalize$1$Proxy;
            }
            break;
        case -1261565625:
            if (var10000.equals("buyCar()V")) {
    
    
                return CGLIB$buyCar$0$Proxy;
            }
            break;
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
    
    
                return CGLIB$clone$5$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
    
    
                return CGLIB$equals$2$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
    
    
                return CGLIB$toString$3$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
    
    
                return CGLIB$hashCode$4$Proxy;
            }
        }

        return null;
    }

    public BuyCarImpl$$EnhancerByCGLIB$$1caed7b0() {
    
    
        CGLIB$BIND_CALLBACKS(this);
    }



    public Object newInstance(Callback var1) {
    
    
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{
    
    var1});
        BuyCarImpl$$EnhancerByCGLIB$$1caed7b0 var10000 = new BuyCarImpl$$EnhancerByCGLIB$$1caed7b0();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
    
    
        CGLIB$SET_THREAD_CALLBACKS(var3);
        BuyCarImpl$$EnhancerByCGLIB$$1caed7b0 var10000 = new BuyCarImpl$$EnhancerByCGLIB$$1caed7b0;
        switch(var1.length) {
    
    
        case 0:
            var10000.<init>();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

}

jdk的动态代理与cglib动态代理的底层实现

1.JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
2.JDK和Cglib都是在运行期生成字节码,JDK是直接写成代理对象Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。
4.cglib封装了ASM这个开源框架,对字节码操作,完成对代理类的创建.主要通过集成目标对象,然后完成重写,再操作字节码

ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

FastClass 的原理简单来说就是:为不需要反射 invoke 调用的原类型生成一个 FastClass 类,然后给原类型的方法分配一个 index,在生成的 FastClass 中的 invoke 方法中,先直接把 Object 强制转换为原类型,然后根据这个 index,就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK 动态代理通过反射调用高。

在这里插入图片描述

拓展:https://blog.csdn.net/z69183787/article/details/106878203/
若动态代理要对目标类的增强逻辑进行拓展,结合策略模式,只需要新增策略类,无需修改代理类代码

Spring 中何时使用 JDK 或 CGLIB

如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP
如果目标对象实现了接口,也可以强制使用 CGLIB 实现 AOP
如果目标对象没有实现接口,必须采用 CGLIB,Spring 会自动在 JDK 动态代理和 CGLIB 之间转换
答案:DefaultAopProxyFactory类中有源码

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    
    

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    
    
		//JDK动态代理和CGlib的选择,判断是否配置,判断是否是接口
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    
    
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
    
    
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
    
    
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
    
    
			return new JdkDynamicAopProxy(config);
		}
	}

	/**
	 * Determine whether the supplied {@link AdvisedSupport} has only the
	 * {@link org.springframework.aop.SpringProxy} interface specified
	 * (or no proxy interfaces specified at all).
	 */
	private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
    
    
		Class<?>[] ifcs = config.getProxiedInterfaces();
		return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
	}

}

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏

猜你喜欢

转载自blog.csdn.net/promsing/article/details/126774641
今日推荐