Java中的动态代理——附带动态代理源码跟踪分析

  • Post author:
  • Post category:java




本文的目标

  • 了解代理模式
  • 静态代理的代码实现
  • 动态代理的代码实现
  • 深入源码跟踪动态代理的实现原理



代理的概念

代理是基本的设计模式之一,它是为了插入一些额外操作或者不同操作而插入的“实际”对象的对象,它通常在实际被访问对象与访问者之间承当着中间人的职能,代替访问者去访问真实对象。代理模式在实际运用中可以提供非常好的访问控制。代理模式的通用类图如下:

请添加图片描述

  • Subject:抽象功能角色

    此角色可以是抽象类也可以是接口,它代表着通用业务功能的定义。

  • Target:具体功能角色

    被委托类,被代理类。Subject中定义的具体业务功能逻辑的实现者。

  • Proxy:代理功能角色

    委托类,代理类。它将通过调用Target类中的具体业务逻辑实现的部分,委托Target类去实现真正的业务逻辑,而它可以在Target类处理业务逻辑的前后做些预先处理和善后处理等工作。



静态代理

接下来我们写一个代理模式的小案例,以男朋友们最讨厌、女朋友们最喜欢的SK2神仙水的销售来说吧。

SK2的厂家售卖SK2,但是我们普通人没法从厂家手中直接买到这玩意儿。它们通常由TB、PDD或者JD的商家去和厂家申请到SK2的代理商,厂家才会把货源发配给他们,我们普通顾客再从代理商手中买到SK2。这里所说的几个角色在代理模式中的角色定位如下:

  • Target类:SK2厂家

  • Subject抽象功能:售卖神仙水

  • Proxy类:TB、PDD或者JD的SK2代理商

类图如下在下面。



类图

请添加图片描述



代码实践

  • Target和Proxy都有的功能接口:IsaleSK2
public interface IsaleSK2 {
	int sale(int num);
}
  • Target:SK2Factory
public class SK2Factory implements IsaleSK2{
	//厂商价600
	@Override
	public int sale(int num) {
		return 600;
	}
}
  • Proxy:SK2TBProxy
public class SK2TBProxy implements IsaleSK2 {
	private SK2Factory mTarget;
	public SK2TBProxy(SK2Factory target) {
		this.mTarget = target;
	}
	@Override
	public int sale(int num) {
		int money = mTarget.sale(num);

		if (num < 10){
			money += 188;
		}else{
			money += 166;
		}
		//do something with  mTarget.sale()

		System.out.println("TBProxy rise the cost,money = "+money);
		return money;
	}
}
  • Client:
public class ProxyClient {

	public static void main(String[] args) {
		//static proxy ,
		// main is the Client,
		// DaMaiNet is the proxy,
		// OperaHouse is the target
		SK2Factory target = new SK2Factory();

		SK2TBProxy damai = new SK2TBProxy(target);
		int money = damai.sale(2);
		System.out.println("static TBproxy: money = "+money);
	}
}
//结果
TBProxy rise the cost,money = 788
static TBproxy: money = 788

我们可以观察到上述代理模式的代码中,我只实现了TB的代理商,如果新增JD和PDD两个售货渠道,那就需要为它们单独再写代理类。

也就是说:在静态代理模式中,代理类对象会随着SK2代理商的增加而增加,这就会导致代码冗余部分过多,有没有办法解决这种不雅观的编码呢?当然有,接下来,我将采用动态代理的方式简化代理类的代码,让一个类实现N个代理类的功能。



动态代理

JAVA中动态代理实现的方式可以通过JDK提供的Proxy类以及cglib库,本文中我主要讲解JAVA中的动态代理。

动态代理通用类图如下:

请添加图片描述

首先,我们自己给自己增加以下下需求::

  • TB渠道:少于10瓶,价格为788元/瓶,买超过10瓶,766元/瓶
  • PDD渠道:少于10瓶,价格为780元/瓶,买超过10瓶,750元/瓶
  • JD渠道:少于10瓶,价格为785元/瓶,买超过10瓶,770元/瓶



类图

请添加图片描述



代码实践

  • Target和Proxy都有的功能接口:IsaleSK2
public interface IsaleSK2 {
	int sale(int num);
}
  • Target:SK2Factory
public class SK2Factory implements IsaleSK2{
	//厂商价600
	@Override
	public int sale(int num) {
		return 600;
	}
}
  • InvocationHandler接口的实现:
public class MyInvocationHd implements InvocationHandler {
	enum SALE_CHENNEL {
		TB,
		PDD,
		JD;
	}

	private SK2Factory mTarget;
	private SALE_CHENNEL mChennel;

	public MyInvocationHd(SK2Factory target, SALE_CHENNEL chennel) {
		this.mChennel = chennel;
		this.mTarget = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		int num = (int) args[0];
		int money = (int) method.invoke(mTarget, args);
		switch (mChennel) {
			case TB:
				if (num < 10) {
					money += 188;
				} else {
					money += 166;
				}
				break;
			case PDD:
				if (num < 10) money += 180;
				else
					money += 150;
				break;
			case JD:
				if (num < 10)
					money += 185;
				else
					money += 170;
				break;
			default:
				break;
		}
		System.out.println("method " + method.getName() + ": chennel(" + mChennel.name()+"),money = "+money);

		return money;
	}
}
  • Client:
public class ProxyClient {
	public static void main(String[] args) {		

		IsaleSK2 tBProxy = (IsaleSK2) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHd(target, MyInvocationHd.SALE_CHENNEL.TB));
		money = tBProxy.sale(2);
		System.out.println("TB:money = " + money);

		IsaleSK2 pDDProxy = (IsaleSK2) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHd(target, MyInvocationHd.SALE_CHENNEL.PDD));
		money = pDDProxy.sale(15);
		System.out.println("TB:money = " + money);

		IsaleSK2 jDProxy = (IsaleSK2) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHd(target, MyInvocationHd.SALE_CHENNEL.JD));
		money = jDProxy.sale(15);
		System.out.println("TB:money = " + money);
	}
}

//结果
method sale: chennel(TB),money = 788
TB:money = 788
method sale: chennel(PDD),money = 750
TB:money = 750
method sale: chennel(JD),money = 770
TB:money = 770

OK,完成新增的需求,各种代理商价格变化的逻辑都被抽取到一个方法中完成,而且代码也很优雅。

上面的Proxy.newProxyInstance()方法就是动态生成代理类对象的关键类和关键方法,生成动态代理对象时:

  • 参数

    target.getClass().getClassLoader()

    是通过target获取的类加载器
  • 参数

    target.getClass().getInterfaces()

    是通过target获取到它的公共功能接口类的class字节码
  • 参数

    new MyInvocationHd(target, MyInvocationHd.SALE_CHENNEL.TB)

    是动态代理的重点,它是一个InvocationHandler接口的 实现类,内部实现invoke方法。

如类图,MyInvocationHd类实现类InvocationHandler接口内部的invoke方法,动态代理要实现的代理逻辑全部会在invoke方法中进行调用。



动态代理的原理

上面我讲完了动态代理的实现方法,看着的确很简单,可是,它为什么能做到这样呢?JDK内部到底做了些什么让我们能动态的拿到这个代理对象?它生成代理类了吗?如果生成了,这个代理类去哪里了呢?

接下来我们将要深入源码去看JDK中动态代理的实现原理。



深入源码

本次源码分析只跟踪动态代理类对象生成的流程,不管异常处理,权限处理等其它支线的问题。



Proxy类




Proxy.newProxyInstance()

方法:

newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
//略
				//Spet1:返回代理类的一个Constructor对象
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
        //Spet2:生成代理类对象
        return newProxyInstance(caller, cons, h);
    }

此方法返回一个代理类对象。


  • Class<?>[] interfaces

    :通用功能抽象接口的class,本例中是

    IsaleSK2.class


  • InvocationHandler h

    :我们自定义的实现了InvocationHandler接口的invoke方法的实现类,invoke方法内部实现了代理业务逻辑。

首先,看下Step2中拿到Constructor对象后怎么使用的。



Step2:cons.newInstance(new Object[]{h})方法:

private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager Constructor<?> cons, InvocationHandler h) {
//略
    return cons.newInstance(new Object[]{h});
}

Step2中,使用Constructor类对象cons生成一个代理类对象。这个代理类的构造函数中传入了参数

InvocationHandler h

,所以代理类对象内部包含InvocationHandler类型的对象(实际是我们的MyInvocationHd实现类的对象)。

现在,回到Step2,继续跟踪拿到的代理类的构造函数中为啥会有InvocationHandler对象?代理类的内部长啥样?



Spet1:getProxyConstructor(caller, loader, interfaces)方法:

private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // optimization for single interface
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            //略
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                //Step3:创建代理类
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
            // interfaces cloned
            final Class<?>[] intfsArray = interfaces.clone();
           //略
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                //Step3:ProxyBuilder类:内部实现创建代理类的逻辑
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }

Step3:

new ProxyBuilder(ld, clv.key()).build()

方法里面完成Proxy类的构建,所以跟踪Step3.



ProxyBuilder类



Step3:ProxyBuilder(ld, clv.key()).build()

Constructor<?> build() {
						//Step4:通过接口(IsaleSK2接口)的.class(字节码)生成Proxy类.
            Class<?> proxyClass = defineProxyClass(module, interfaces);
            assert !module.isNamed() || module.isOpen(proxyClass.getPackageName(), Proxy.class.getModule());

            final Constructor<?> cons;
            try {
            		//Step5:1、生成实现通用功能抽象接口的类,本例中是`IsaleSK2.class`
            		//			 2、把constructorParams作为构造函数传进实现类中的
                cons = proxyClass.getConstructor(constructorParams);
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
            return cons;
}



Step5:

proxyClass.getConstructor(constructorParams)

方法:

生成并返回Proxy类的Constructor对象。通过Step4中生成的Proxy类的字节码,生成Constructor对象,外界使用此Constructor的对象

newInstance

Proxy对象时必须传入InvocationHandler类型的参数,constructorParams的内容如下:

  • constructorParams参数:InvocationHandler.class字节码

    private static final Class<?>[] constructorParams ={ InvocationHandler.class };
    

Step5代码结束,如果对Step5内部的实现逻辑不清楚的可以找一下反射相关的文章了解一下相关的内容。

Step4中

通过interfaces(IsaleSK2接口)的字节码生成Proxy类的实现

还不清晰,继续跟踪。



Step4:

defineProxyClass(module, interfaces)

方法:

private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            boolean nonExported = false;
						//都是做各种验证的,略
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;  // non-public, final
                    String pkg = intf.getPackageName();
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                    }
                } else {
                    if (!intf.getModule().isExported(intf.getPackageName())) {
                        // module-private types
                        nonExported = true;
                    }
                }
            }

            if (proxyPkg == null) {
                // all proxy interfaces are public and exported
                if (!m.isNamed())
                    throw new InternalError("unnamed module: " + m);
                proxyPkg = nonExported ? PROXY_PACKAGE_PREFIX + "." +m.getName(): m.getName();
            } else if (proxyPkg.isEmpty() && m.isNamed()) {
                throw new IllegalArgumentException(
                        "Unnamed package cannot be added to " + m);
            }

            if (m.isNamed()) {
                if (!m.getDescriptor().packages().contains(proxyPkg)) {
                    throw new InternalError(proxyPkg + " not exist in " + m.getName());
                }
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg.isEmpty()
                                    ? proxyClassNamePrefix + num
                                    : proxyPkg + "." + proxyClassNamePrefix + num;

            ClassLoader loader = getLoader(m);
            trace(proxyName, m, loader, interfaces);

            //重点:Step6:通过ProxyGenerator类生成Proxy的Class文件。
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags);
            try {
                Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile,
                                              null, "__dynamic_proxy__");
                reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
                return pc;
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }

通过interfaces(IsaleSK2接口)的字节码生成Proxy类,返回该Proxy类的字节码。做各种验证的代码不是主线略过不要搭理。重点是

ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags)

,通过ProxyGenerator类生成Proxy的Class文件,那么这个class文件被保存在哪里?如果能看到生成的Proxy内部的实现,动态代理的谜团就解开了,继续跟踪Step6。



Step6:ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags)

通过ProxyGenerator类生成Proxy的Class文件。

static byte[] generateProxyClass(ClassLoader loader,
                                     final String name,
                                     List<Class<?>> interfaces,
                                     int accessFlags) {
        ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags);
        
        //Step7:启动Class生成器构建Proxy的Class字节码
        final byte[] classFile = gen.generateClassFile();//Step8:如果 saveGeneratedFiles == true ,那么生成的Proxy类就能保存成一个文件
        if (saveGeneratedFiles) {
            java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                            try {
                                int i = name.lastIndexOf('.');
                                Path path;
                                if (i > 0) {
                                    Path dir = Path.of(dotToSlash(name.substring(0, i)));
                                    Files.createDirectories(dir);
                                    path = dir.resolve(name.substring(i + 1) + ".class");
                                } else {
                                    path = Path.of(name + ".class");
                                }
                                //把class字节码写入文件
                                Files.write(path, classFile);
                                return null;
                            } catch (IOException e) {
                                throw new InternalError(
                                        "I/O exception saving generated file: " + e);
                            }
                        }
                    });
        }

        return classFile;
    }



Step7:gen.generateClassFile()

启动Class生成器构建Proxy的Class字节码。偏离主线,略过。



Step8:saveGeneratedFiles参数

saveGeneratedFiles是何方神圣?

 private static final boolean saveGeneratedFiles =
            java.security.AccessController.doPrivileged(
                    new GetBooleanAction(
                            "jdk.proxy.ProxyGenerator.saveGeneratedFiles"));

原来这是一个配置项,它获取的值为JVM中的参数,可以通过在代码中动态设置来让动态代理生成的Proxy类保存成文件。saveGeneratedFiles的值随JDK版本的不同而不同,目前共两种取值,设置方法如下:

  • “Dsun.misc.ProxyGenerator.saveGeneratedFiles”

    System.setProperty("Dsun.misc.ProxyGenerator.saveGeneratedFiles""true")
    
  • “jdk.proxy.ProxyGenerator.saveGeneratedFiles”

    System.getProperties().setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
    

至此,动态代理源码跟踪结束。



生成Proxy类的文件

接下来我们使用之前的动态代理部分的代码,在main方法中设置一下这个参数,看看生成的Proxy长啥样吧!



设置参数为true
public class ProxyClient {
	public static void main(String[] args) {		System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

		IsaleSK2 tBProxy = (IsaleSK2) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHd(target, MyInvocationHd.SALE_CHENNEL.TB));
		money = tBProxy.sale(2);
		System.out.println("TB:money = " + money);
	}
}


生成Proxy类文件

在项目根目录下会生成一个文件夹,java中动态代理生成的类都以$ProxyX命名,调试代码的时候看到这种命名的类,那么这个类就是动态代理生成的。

请添加图片描述

//重点1:此Proxy类实现IsaleSK2接口
public final class $Proxy0 extends Proxy implements IsaleSK2 {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;
		//重点2:动态代理类中包含一个InvocationHandler接invoke方法。口类型的对象,此例中传入的就是我们定义的MInvocationHd类,它实现了InvocationHandler接口的invoke方法。
    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }
    //...
    //重点3:实现IsaleSK2的sale方法
    public final int sale(int var1) {
        try {
        		//重点4:当调用动态代理类对象的sale方法时,真正调用的是MInvocationHd类中的invoke方法。
            return (Integer)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 }

OK,动态代理的源码跟踪结束。码字不易,如果有什么疑问欢迎评论区留言讨论,下一篇文章再见咯~



版权声明:本文为xueyingpiaoran原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。